ちょっとした同期オンラインを試してみたかったので書いてみました。
PHPでソケットサーバーを立てる
Unityからsocket通信するプログラム試したくて、socket-server立てるのに久しぶりにPHP書いてる。PHP便利!
— Fumiya Ichikawa (@LET__IT__RIDE) December 28, 2019
PHP側でソケットサーバを立てる。下記の例は複数のクライアントからの接続を許可し、受け取ったメッセージを接続済みすべてのクライアントへechoするサーバー。
socketserver.php
<?php //接続プール $streams = array(); //サーバーソケットの作成 $socket = stream_socket_server('tcp://0.0.0.0:50765', $errno, $errstr); while(true){ //クライアント接続ソケット(timeout(1)秒間接続を待つ = 待機する) //接続があろうがなかろうがtimeout秒分待つ $stream = @stream_socket_accept($socket, 1); if($stream !== false){ $clientName = stream_socket_get_name($stream, true); echo "connect" . $clientName."\n"; //接続先へメッセージを送信 fwrite($stream, "ようこそ\n", strlen("ようこそ\n")); $streams[$clientName] = $stream; } if (count($streams) === 0) { continue; } $read = $streams; $write = $streams; $error = $streams; //streamの状態に変更があった場合、対象のstreamを検出する(引数に指定した[stream]で参照できる) $number = stream_select($read, $write, $error, 1); if( $number > 0){ foreach($read as $key => $read_stream){ $message = fread($read_stream, 1024); //0byteの文字列が送信された場合、クライアントから切断されたとみなす if($message === ""){ fclose( $read_stream ); unset( $sockets[$key] ); } $send_message = "$key, read: $message"; echo $send_message; //各クライアントへechoする foreach ($write as $wkey => $write_stream) { fwrite($write_stream, $send_message, strlen($send_message)); } } } }
stream_select()
を使用して複数のクライアントからの接続に対して処理するようにした。
んー。stream_selectからのread streamの読み出しがうまく出来ない。streamの通知はきているみたいだけど。
— Fumiya Ichikawa (@LET__IT__RIDE) December 28, 2019
stream_socket_accept()
クライアントとの接続ソケットを作成するんだけど、引数timeoutで指定した秒数分まで待機する。これは例えクラアントからの接続があったとしても、指定の秒数分待機しつづけるのですぐにレスポンスを返すのであれば0を指定すればよい。
よって、外側のwhile loopと併せて、timeout秒数分、sleepしてloopするような間隔になる。
stream_socket_acceptのtimeoutはクライアントからの接続を待つ時間だから、その分、発生検知が遅れる。という話しだった。
— Fumiya Ichikawa (@LET__IT__RIDE) December 29, 2019
stream_select
に指定した各read,write,errorソケットは、参照渡しになっていて関数実行後に読み取り、書き込み可能になっているソケットが参照できるようになる。
基本、参照渡しで渡したソケットを確認するようにすれば良いと思う。
https://www.php.net/manual/ja/function.stream-select.php では、引数:tv_sec
は、関数実行から戻るまでの時間の上限とあるが、僕が試した時は、"待つ"ような挙動はせず、stream_socket_acceptのtimeoutに引きずられるような挙動となった。
サーバの起動
起動は
$ php socketserver.php
とすれば良い。
Unity C#の実装
using UnityEngine; using System.Collections; using System.Net; using System.Net.Sockets; public class SocketSampleTCP : MonoBehaviour { //接続ソケット. private Socket m_socket = null; void Start () { m_socket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); m_socket.NoDelay = true; m_socket.Connect ("localhost", 50765); //ようこそ と出力 Read(); } void Read () { byte [] buffer = new byte [1400]; int recvSize = m_socket.Receive (buffer, buffer.Length, SocketFlags.None); if (recvSize > 0) { string message = System.Text.Encoding.UTF8.GetString (buffer); Debug.Log (message); } } void Send () { byte [] buffer = System.Text.Encoding.UTF8.GetBytes ("send data"); m_socket.Send (buffer, buffer.Length, SocketFlags.None); } void Update () { Send (); //エコーサーバからのレスポンスを読み込む Read (); // 切断 //m_socket.Shutdown (SocketShutdown.Both); //m_socket.Close (); } }
こちらは取り敢えず試してみたかったのでUpdate()
メソッドの中に送信とレスポンスの処理を書いた。きちんと書く場合はどこで接続を切断するかは考えよう。
上記の実装でサーバへ送信Send ()
し、サーバから送信されたデータをRead ()
で読み取ることができる。
他のクライアントから送信されたデータももちろんRead()
読み取ることができる。
尚、C#のSocket.Receive
は、ブロッキングモードで動作するようで読み取り可能になるまで待機し続けるみたい。なので、Send()
を先においっちゃった(笑)
多分、スレッド立てて待つとか、非ブロッキングモードで使用するとかの方法になるんじゃないかな?と思いつつ実験を終了。