.NET Micro FrameworkでNetduinoに非同期TCPサーバーを実装する
.NET Micro Framework には TcpListener や TcpClient などがなく、非同期メソッドもありませんので Socket を使って自力で実装する必要があります。
今回はTCPで非同期通信する汎用のサーバークラス AsyncTcpServer を紹介します。少し改造すれば Web サーバーになります。
.NET Micro Framework(以下、MF)のバージョンは 4.1 、テスト環境は Netduino Plus です。
サンプルコード
機能としては非常にシンプルです。クライアントの接続要求を受け付けて、データを受信して、それに応答したら切断するだけ。受信バッファなどをラッピングしてあるので、使う側のコードは下記のように非常にシンプルです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static void Main() { // ネットワーク設定 var ni = Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0]; ni.EnableStaticIP(ipAddress, subnetMask, gatewayAddress); ni.DnsAddresses[0] = dnsAddress; // サーバーの初期化 AsyncTcpServer server = new AsyncTcpServer(); server.Start(50100); // Listen するポート server.DataArrival += new DataArrivalEventHandler(server_DataArrival); // 受信したときのイベント // メインスレッドは待機 while (true) { Thread.Sleep(1000); } } static void server_DataArrival(object sender, string receivedString) { // 受信した文字列に Re: をつけて返してみる (sender as AsyncTcpServer).SendResponse("Re: " + receivedString); } |
クライアントから送受信テストしたところ Accept~Receive~Send~Close まで、ごく短い文字列(10文字程度)で、約 3ms ぐらいでした。
テスト環境は固定 IP 設定でしたので、はじめの 3 行で IP 設定をしています。
サーバーの初期化はその下の 3 行だけです。
メインスレッドが終了してしまわないように無限ループにスリープをいれています。ちなみにこのスリープがないと Netduino が応答しなくなりますので、ご注意を。
AsyncTcpServer クラス
ソースコードは最後につけてあります。
基本的な流れは、クライアントからの接続を待機し、接続があれば Accept し、データを受信したら Received イベントを発生させ、Send メソッドでなんらかのデータを返して、接続を切る。という感じです。
Accept がブロッキングメソッド(スレッドの流れをブロックするメソッド)ですのでこれを別スレッドに移すことでメインスレッドのほうでは別の処理を続けることができます。
ただし、一つの接続がきれたら、次の接続を待つ。という流れですので、受信・送信中に別のクライアントが接続してきたときは反応できません。複数同時接続が必要なときはさらに受信ルーチンを別スレッドにする必要があります。
Received イベントの中で数十 ms 以上の重い処理をする場合は受信ルーチンも別スレッドにしたほうがいいと思います。
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
public delegate void DataArrivalEventHandler(object sender, string receivedString); /// <summary> /// 非同期で受信を待機できる TCP サーバーです。 /// </summary> /// <example> /// <code> /// AsyncTcpServer server = new AsyncTcpServer(); /// server.Start(50100); /// server.DataArrival += new DataArrivalEventHandler(server_DataArrival); /// </code> /// </example> public class AsyncTcpServer { private const int BUFFER_SIZE = 128; private Socket newSocket = null; private Socket client = null; private Thread thread = null; private Encoding _Encoding = Encoding.UTF8; /// <summary> /// データのエンコードを指定します。 /// </summary> public Encoding Encoding { get { return _Encoding; } set { _Encoding = value; } } /// <summary> /// クライアントからテキストを受信したときに発生します。 /// </summary> public event DataArrivalEventHandler DataArrival; /// <summary> /// AsyncTcpServer の新しいインスタンスを初期化します。 /// </summary> /// <param name="port"></param> public AsyncTcpServer() { } /// <summary> /// 受信ポートを指定して、サーバーを開始します。 /// </summary> /// <param name="port"></param> public void Start(int port) { newSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); newSocket.Bind(new IPEndPoint(IPAddress.Any, port)); newSocket.Listen(10); thread = new Thread(worker); thread.Start(); } /// <summary> /// AsyncTcpServer を停止します。 /// </summary> public void Stop() { if (thread.IsAlive) thread.Abort(); if (client != null) client.Close(); if (newSocket != null) newSocket.Close(); } ~AsyncTcpServer() { Stop(); } // 接続待機~受信イベントの発生までを行うワーカースレッド private void worker() { int counter = 0; while (true) { counter++; int totalBytesReceived = 0; client = newSocket.Accept(); long tstart = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; int availableByteCount = 0; string receivedStr = ""; while (true) { if (client.Available > 0) break; long t2 = Microsoft.SPOT.Hardware.Utility.GetMachineTime().Ticks; if (((t2 - tstart) / System.TimeSpan.TicksPerMillisecond) > 100) { Debug.Print(counter.ToString() + " Timeout"); goto TIMEOUT; } Thread.Sleep(0); } do { availableByteCount = client.Available; if (availableByteCount > 0) { totalBytesReceived += availableByteCount; byte[] buffer = new byte[BUFFER_SIZE]; int readByteCount = client.Receive(buffer, buffer.Length, SocketFlags.None); receivedStr += new string(Encoding.GetChars(buffer)); } } while (availableByteCount > 0); if (totalBytesReceived > 0) { if (DataArrival != null) { DataArrival(this, receivedStr); } } TIMEOUT: ; Thread.Sleep(0); } } /// <summary> /// 現在のクライアントに対してレスポンスを返します。 /// </summary> /// <param name="responseStr"></param> public void SendResponse(string responseStr) { if (client != null) { Byte[] responseBytes = Encoding.GetBytes(responseStr); try { client.Send(responseBytes, responseStr.Length, SocketFlags.None); } catch (Exception e) { Debug.Print("AsyncTcpServer.SendResponse:" + e.Message); } finally { client.Close(); client = null; } } } } |