質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.55%

サーバー(Java)からUnity(C#)の複数IPへデータの送信ができない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 348

reishisu

score 37

現在、ラズパイをサーバー(Java)としてローカルでUnity同士でチャットする機能を開発しようとしております。

まず、以下のコードでUnityからメッセージをラズパイへ向けて送信し、ラズパイからもUnityへ送信する事はできました。

Javaのコード
C#のコード

その後、Java側のコードをUnity側から受信があれば今まで接続があったIPアドレスに対してブロードキャストするためのsendメソッド、C#側のコードにはTcpListenerとブロードキャストされたデータを受信するためのListenerメソッドを追加し、送信してすぐ受信する処理を削除しました。

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

/**
 * ラズパイ上で動かすチャットのサーバークラスです
 * @author reishisu
 */
public class ChatServer {

    /**
     * ソケット通信を行うポート
     */
    public static final int PORT = 25252;
    /**
     * チャットサーバーの窓口
     */
    private static ServerSocket server_socket;
    /**
     * 接続してきたIPアドレス
     */
    private static ArrayList<String> client_list = new ArrayList<>();

    /**
     * メイン関数
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        try {
            // 既にサーバーが建っていない時サーバーソケットの生成
            if (server_socket == null) {
                server_socket = new ServerSocket(PORT);
                System.out.println("サーバー稼働中...");
            }

            // 受信カウンタ
            int count = 1;

            // サーバー稼働
            while (true) {
                // クライアントからの接続が来るまで待機
                Socket client_socket = server_socket.accept();

                // 新規か確認
                boolean is_new = true;
                for(String ip : client_list) {
                    if (ip == client_socket.getInetAddress().toString()) {
                        is_new = false;
                    }
                }
                if (is_new) client_list.add(client_socket.getInetAddress().toString());

                // 計測開始
                long start_time = System.currentTimeMillis();

                // クライアントから文字列をバイト列で受信
                InputStream input_stream = client_socket.getInputStream();
                DataInputStream data_input_stream = new DataInputStream(input_stream);

                // 文字列を受け取るバッファー
                byte[] buffer = new byte[4096];
                int byte_count = 0;
                String message = "";
                try {
                    byte_count = data_input_stream.read(buffer);
                    if (byte_count == 0) return;
                    String view_string = new String(buffer, "UTF-8");
                    long end_time = System.currentTimeMillis();
                    SimpleDateFormat time_stamp = new SimpleDateFormat("yyyy年MM月dd日HH時mm分ss秒");
                    message += count + "  IPアドレス:" + ("" + client_socket.getInetAddress()).split("/")[1] + ":" +
                               time_stamp.format(new Date()) + " [" + (end_time - start_time) + "ms]" + "\n" + view_string + "\n";
                    System.out.println(message);
                    count++;

                    send(message, client_socket.getInetAddress().toString());
                }
                catch (Exception e) {
                    long end_time = System.currentTimeMillis();
                    SimpleDateFormat time_stamp = new SimpleDateFormat("yyyy年MM月dd日HH時mm分ss秒");
                    message += count + "  IPアドレス:" + ("" + client_socket.getInetAddress()).split("/")[1] + ":" +
                               time_stamp.format(new Date()) + " [" + (end_time - start_time) + "ms]" + "\n受信失敗...\n";
                    System.out.println(message);
                    count++;

                    send(message, client_socket.getInetAddress().toString());
                }
            }
        }
        catch (Exception e) {
            System.err.println("エラー発生!!");
            e.printStackTrace();
        }
    }

    public static void send(String message, String now_ip) {
        byte[] buffer = new byte[4096];
        DataOutputStream dataOutputStream = null;
        try {
            buffer = message.getBytes("UTF-8");

            for (String ip : client_list) {
                if (ip == now_ip) continue;
                dataOutputStream = new DataOutputStream(new Socket(ip.split("/")[1], PORT).getOutputStream());
                dataOutputStream.write(buffer);
                dataOutputStream.flush();
            }
            dataOutputStream.close();

        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Threading.Tasks;

public class ChatClient : MonoBehaviour {

    // ゲームオブジェクト
    public InputField input_field;
    public GameObject content;
    public GameObject chat_log;

    // サーバー情報
    private string server_ip_address = "192.168.0.25";
    private int port = 25252;

    // クライアントの送信窓口
    NetworkStream network_stream;

    // 送信内容
    private string send_text;

    // 受信用
    private TcpListener listener;
    private float request_span = 10f;
    private byte[] buffer = new byte[4096];


    // Start is called before the first frame update
    void Start() {

        network_stream = new TcpClient(server_ip_address, port).GetStream();
        Debug.Log(network_stream.Read(buffer, 0, buffer.Length));
        network_stream.Close();

        //Componentを扱えるようにする
        input_field = input_field.GetComponent<InputField>();
        // 受信用
        listener = new TcpListener(IPAddress.Parse(server_ip_address), port);

        Task.Run(Listener);
    }

    public void InputText() {
        //テキストにinputFieldの内容を反映
        send_text = input_field.text;
    }

    // ボタンクリック時
    public void NetworkRequest() {
        // 接続要求
        network_stream = new TcpClient(server_ip_address, port).GetStream();

        // 送信の内容をセット
        if (send_text.Length == 0) return;
        byte[] send_string = Encoding.UTF8.GetBytes(send_text);

        // データを送信
        network_stream.Write(send_string, 0, send_string.Length);

        network_stream.Close();
    }


    public void Listener() {
        listener = new TcpListener(IPAddress.Parse(server_ip_address), port);
        Debug.Log("監視開始1");

        listener.Start();
        Debug.Log("監視開始2");
        while (true) {
            //接続要求があったら受け入れる
            TcpClient client = listener.AcceptTcpClient();

            Debug.Log("きた!");

            // データを受信
            network_stream.Read(buffer, 0, buffer.Length);
            string message = Encoding.UTF8.GetString(buffer);
            GameObject instance = Instantiate(chat_log);
            instance.transform.parent = content.transform;
            instance.transform.localScale = new Vector3(1, 1, 1);
            instance.GetComponent<Text>().text = message;

            // バッファリセット
            buffer = new byte[4096];
        }
    }

}


そして、修正したコード動かしてみるとUnityから送信まではできたのですが、Javaからブロードキャストする際に以下のエラーメッセージが出てきて送信が出来ず、Unity側でも勿論反応がありません。
また、Unity側では起動時に"監視開始1"は表示されているのですが、その後"監視開始2"は表示されません。

サーバー稼働中...
1  IPアドレス:192.168.0.520190709031343秒 [6ms]
[壁]*´・ω・`)ノ コンニチハ.:゚+

java.net.ConnectException: 接続を拒否されました
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)
    at java.net.Socket.<init>(Socket.java:434)
    at java.net.Socket.<init>(Socket.java:211)
    at ChatServer.send(ChatServer.java:109)
    at ChatServer.main(ChatServer.java:81)

色々調べてみましたが原因がわからず困っております...
あまりネットワークの通信に詳しくないのでわかる方がいらっしゃいましたら、どうかご教授いただきたいです。

補足情報(FW/ツールのバージョンなど)

ー 開発環境 ー
MacBook Pro (15-inch, 2016)
OS : macOS mojave
バージョン : Unity 2018.2.2f1
IPアドレス : 192.168.0.5

Raspberry Pi 3 Model B+
OS : Raspbian GNU/Linux 9
バージョン : Java SE 8
IPアドレス : 192.168.0.25

追記

Unity側で全くエラーが出てなかったので心配していなかったのですが、回答して頂いた内容を元にlistener.Start()にtry-catchを入れてみるとまさかのエラーが表示されておりました。。。
エラーを確認したばかりなので、今から原因を調べてみますが取り急ぎ追記させていただきました。

e.Messageの結果

The requested address is not valid in this context
UnityEngine.Debug:Log(Object)
ChatClient:Listener() (at Assets/Scripts/ChatClient.cs:80)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()

e.TargetSiteの結果

Void Bind(System.Net.EndPoint)
UnityEngine.Debug:Log(Object)
ChatClient:Listener() (at Assets/Scripts/ChatClient.cs:81)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()

e.StackTraceの結果

  at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <3845a180c26b4889bc2d47593a665814>:0 
  at System.Net.Sockets.TcpListener.Start (System.Int32 backlog) [0x00044] in <3845a180c26b4889bc2d47593a665814>:0 
  at System.Net.Sockets.TcpListener.Start () [0x00000] in <3845a180c26b4889bc2d47593a665814>:0 
  at ChatClient.Listener () [0x00029] in /Users/reishisu/Documents/Unity/SocketTest/Assets/Scripts/ChatClient.cs:78 
UnityEngine.Debug:Log(Object)
ChatClient:Listener() (at Assets/Scripts/ChatClient.cs:80)
System.Threading._ThreadPoolWaitCallback:PerformWaitCallback()
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

Unity側は知りませんのでjava側から見たのみのお話です.
Unity側が接続待ち(監視接続2が表示される状態?)になっていなければ, java側の接続が拒否されたと言ってくるのは当然かと思います.
また, チャットでしたら最初のUnity→javaの接続を維持してメッセージのやり取りをすれば, わざわざ新たにjava→Unityの接続をしなくて済むのではないでしょうか.

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/09 22:28

    そうですね. より正確には, Socket(OutputStream)をリストとして必要なのは送信側ですので, 受信側は「クライアント毎のスレッド」が1つずつ持てば(そしてInputStream.readし続ければ)良いかと思います.

    キャンセル

  • 2019/07/09 22:35

    なお, 私の回答はかなり「イメージ」でお伝えしています. コードで示せれば『百聞は一見に…』なのですが.
    マルチスレッドはjava内でも色々修正・変更が繰り返されている機能ですので, 本来のマルチスレッドの難しさと共にjavaでの難しさもあります. 無責任ですが, ドキュメントやネットの記事をよくお探しになってお使いください(_ _;

    キャンセル

  • 2019/07/09 22:54

    なるほどです><
    送信の際は、メインスレッドのみでforループを利用して必要に応じ適宜送信するのでマルチスレッドの必要性低く、クライアントからの受信の際は同時に1つのサーバーにほぼ同じタイミングでアクセスする可能性があるのでThreadで同時アクセスに対応し、さらにサーバー自体を排他制御する必要があるのですね。

    一応、Webアプリの構築で知り合いからDOS攻撃まがいの大量アクセスをされたことがありMySQLを利用する際にSynchronizedにかなりお世話になった記憶があるのでもう少し深掘りしながら、慎重に実装していこうと思います((+_+))

    私はお世辞にも優秀とは言えない学生なのですが、まさかここまで進展できるとは思っておらずかなり驚いております。
    本当にありがとうございました(>_<;)

    キャンセル

check解決した方法

0

あまり、気持ちよくないのですがC#の
listener = new TcpListener(IPAddress.Parse(server_ip_address), port);
を全てから受け取れるように
listener = new TcpListener(IPAddress.Any, port);
と変更するとJavaの方からもメッセージを受け取ることができました。
もしかして、ルーターを経由して使っているのでラズパイではなくmacに一番近いルーターのIPアドレスが必要だと推察してルーターのIPを設定してみるとエラーのままだったので、Anyでできたのが少し気持ち悪い感じでした。

自己解決したコードは以下になります。
(今回は通信だけの質問だったので、別スレッドで実行しているので受け取ったメッセージをメインスレッドにデータの送信ができていないので完全に完成はしておりません。)

C#のコード
回答して頂いたjimbe様、解決のヒントを下さり本当にありがとうございました。

追記

一応、目的のとりあえず動くコードは(多分)作れたので追記させて頂きます。
Javaのコード
C#のコード

追記(2019年7月10日)

Javaのコードを2つに分けることでうまく、排他制御することに成功しました!!
サーバー側でアクセスされる度にクライアントのIPアドレスを確認し、クライアントのIPアドレスを管理しているリストにあればそのままThreadを非同期で実行させており、なければリストに追加して同じくThreadを非同期で実行させております。
Threadの中では、メインスレッドにあるbroadcast関数をsynchronized修飾子をつけ排他制御しながら実行しているのでうまくスレッドの生成順に実行させることができ、上手くできたかなぁと思っております。
また、アプリを落とした時の切断エラーに関してもエラーをcatchした瞬間に当該IPをリストから削除することで他のスレッドに干渉せずに出来ました!

package raspi_server;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

/**
 * ラズパイ上で動かすチャットのサーバークラスです
 * @author reishisu
 */
public class ChatServer {

    /**
     * ソケット通信を行うポート
     */
    private static final int PORT = 25252;
    /**
     * チャットサーバーの窓口
     */
    private static ServerSocket server_socket;


    /**
     * 接続してきたIPアドレス
     */
    private static ArrayList<Socket> socket_list = new ArrayList<>();
    /**
     * チャットのナンバー
     */
    private static int chat_number = 1;

    /**
     * メイン関数
     * @param args コマンドライン引数
     */
    public static void main(String[] args) {
        try {
            // 既にサーバーが建っていない時サーバーソケットの生成
            if (server_socket == null) {
                server_socket = new ServerSocket(PORT);
                System.out.println("サーバー稼働中...");
            }
            // サーバー稼働
            while (true) {
                // クライアントからの接続が来るまで待機
                Socket client_socket = server_socket.accept();
                // 新規か確認
                boolean is_new = true;
                // 新規のIPがないかを走査する
                for(Socket client : socket_list) {
                    if ( client.getInetAddress().toString().equals(client_socket.getInetAddress().toString())) {
                        System.out.println("同じとこからきてる!");
                        new ClientReciever(client_socket).start();
                        is_new = false;
                    }
                }
                // 新規の場合
                if (is_new) {
                    // ソケットリストに追加
                    socket_list.add(client_socket);
                    // 新規ソケット用のスレッドを実行
                    new ClientReciever(client_socket).start();
                    System.out.println("新規さん入ります!\nIPアドレス:" + client_socket.getInetAddress().toString() + "\n合計接続数:" + socket_list.size());
                }
            }
        }
        catch (Exception e) {
            System.err.println("エラー発生!!");
            e.printStackTrace();
        }
    }

    /**
     * クライアントにメッセージをブロードキャストするための関数
     * @param message 送信文字列
     */
    public static synchronized void broadcast(String message) {
        byte[] buffer = new byte[4096];
        DataOutputStream dataOutputStream = null;
        try {
            buffer = message.getBytes("UTF-8");

            for (Socket client : socket_list) {
                try {
                    dataOutputStream = new DataOutputStream(new Socket(client.getInetAddress().toString().split("/")[1], PORT).getOutputStream());
                    dataOutputStream.write(buffer);
                    dataOutputStream.flush();
                } catch (Exception e) {
                    System.out.println(client.getInetAddress().toString().split("/")[1] + "との接続が切れました");
                    socket_list.remove(client);
                }
            }
        } catch (IOException e) { e.printStackTrace(); }
    }

    /**
     * チャットナンバーを返す
     * @return 現在のチャットナンバー
     */
    public static synchronized int getChat_number() {
        chat_number++;
        return chat_number-1;
    }
}
package raspi_server;

import java.io.DataInputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * チャットのアプリケーションクラスです
 * @author reishisu
 */
public class ClientReciever extends Thread {

    /**
     * 受信する対象のクライアント
     */
    private Socket client;
    /**
     * クライアントのデータ受信窓口
     */
    private DataInputStream data_input_stream;
    /**
     * 無限ループなのでフラグを使って意図的にスレッドを終了させる
     */
    public boolean do_thread_finish;
    /**
     * // タイムスタンプのテンプレート
     */
    SimpleDateFormat time_stamp = new SimpleDateFormat("yyyy年MM月dd日HH時mm分ss秒");


    /**
     * コンストラクタ
     * @param client 受信の対応をするクライアント用の窓口
     */
    public ClientReciever(Socket client) {
        try {
            System.out.println("ClientReciever Called!!");
            this.client = client;
            this.data_input_stream = new DataInputStream(client.getInputStream());
        }
        catch (Exception e) {
            System.out.println("スレッド生成時にエラーが発生しました...");
            e.printStackTrace();
        }
    }

    /**
     * ひたすらデータの受信を行う
     */
    public void run() {
        // 文字列を受け取るバッファ
        byte[] buffer = new byte[4096];
        // 受信文字列
        String send_message = "";
        // スレッド終了の合図が出るまで実行
        while(!do_thread_finish) {
            try {
                int read_resuld = data_input_stream.read(buffer);
                // データの受信が確認されなかった時は実行されない
                if (read_resuld == 0 || read_resuld == -1) continue;
                // 受信時間の計測を開始
                long start_time = System.currentTimeMillis();
                // 受信した文字列
                String recieve_string = new String(buffer, "UTF-8");
                // 計測終了
                long end_time = System.currentTimeMillis();
                // 受信した文字列に付加情報をつけて送信文字列を作成
                send_message = new StringBuilder().append(ChatServer.getChat_number()).append("  IPアドレス:").append(client.getInetAddress().toString().split("/")[1])
                                                  .append(":").append(time_stamp.format(new Date())).append(" [").append((end_time - start_time))
                                                  .append("ms]\n").append(recieve_string).append("\n").toString();
                // 送信文字列を表示
                System.out.println(send_message);
                // 登録リストへ送信文字列をブロードキャストする
                ChatServer.broadcast(send_message);
                do_thread_finish = !do_thread_finish;
            }
            catch (Exception e) {
                e.printStackTrace();
                // 受信した文字列に付加情報をつけて送信文字列を作成
                send_message = new StringBuilder().append(ChatServer.getChat_number()).append("  IPアドレス:").append(client.getInetAddress().toString().split("/")[1])
                                                  .append(":").append(time_stamp.format(new Date())).append(" 9999[ms]\n受信失敗...\n").toString();
                // 送信文字列を表示
                System.out.println(send_message);
                // 登録リストへ送信文字列をブロードキャストする
                ChatServer.broadcast(send_message);
                do_thread_finish = !do_thread_finish;
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using System.Net.Sockets;
using System.Text;
using System.Net;
using System.Threading.Tasks;
using System.Threading;

public class ChatClient : MonoBehaviour {

    // ゲームオブジェクト
    public InputField input_field;
    public GameObject content;
    public GameObject chat_log;

    public static string a;

    //おまじない
    private SynchronizationContext context;

    // サーバー情報
    private string server_ip_address = "192.168.0.25";
    private int port = 25252;

    // クライアントの送信窓口
    NetworkStream network_stream;

    // 送信内容
    private string send_text;

    // 受信用
    private TcpListener listener;
    private float request_span = 10f;
    private byte[] buffer = new byte[4096];


    // Start is called before the first frame update
    void Start() {

        context = SynchronizationContext.Current;

        //Componentを扱えるようにする
        input_field = input_field.GetComponent<InputField>();

        // 監視
        Task.Run(Listener);
    }

    private void Update() {
        if (a != "") {
            GameObject instance = Instantiate(chat_log);
            instance.transform.parent = content.transform;
            instance.transform.localScale = new Vector3(1, 1, 1);
            instance.GetComponent<Text>().text = a;
            a = "";
        }
    }

    public void InputText() {
        //テキストにinputFieldの内容を反映
        send_text = input_field.text;
    }

    // ボタンクリック時
    public void NetworkRequest() {
        // 接続要求
        network_stream = new TcpClient(server_ip_address, port).GetStream();

        // 送信の内容をセット
        if (send_text.Length == 0) return;
        byte[] send_string = Encoding.UTF8.GetBytes(send_text);

        // データを送信
        network_stream.Write(send_string, 0, send_string.Length);

        network_stream.Close();
    }


    public void Listener() {

        SynchronizationContext.SetSynchronizationContext(context);

        listener = new TcpListener(IPAddress.Any, port);

        Debug.Log("監視開始1");

        try {
            listener.Start();
        }
        catch (Exception e) {
            Debug.Log(e.Message);
            Debug.Log(e.TargetSite);
            Debug.Log(e.StackTrace);
        }

        Debug.Log("監視開始2");
        while (true) {
            //接続要求があったら受け入れる
            TcpClient client = listener.AcceptTcpClient();

            Debug.Log("きた!");

            network_stream = client.GetStream();

            // データを受信
            network_stream.Read(buffer, 0, buffer.Length);
            string message = Encoding.UTF8.GetString(buffer);
            Debug.Log(message);
            a = message;

            // バッファリセット
            buffer = new byte[4096];

            network_stream.Close();
        }
    }

}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.55%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る