とあるコンテストで WebSocket を使うサーバープログラムが必要になったので、 ASP.NET Core ではどう作るのかを調べてみました。 SignalR を使うプログラムではないです。全体のソースコード
準備
まずは Visual Studio 2015 で “ASP.NET Core Web Application” からプロジェクトを作成します。今回は空のテンプレートを選びました。
次に、 WebSocket に関する ASP.NET Core のミドルウェア Microsoft.AspNetCore.WebSockets.Server
を NuGet からインストールします。
最後に、 WebSocket のミドルウェアを使うことが確認できれば準備完了です。
34 | var options = new WebSocketOptions { ReceiveBufferSize = 4096 }; |
WebSocket の接続開始リクエストは下のように受け取ります。そうでなかった場合は await next()
で下流のパイプラインに処理を流します(表現がムズカシイ)。
42 | app.Use(async (context, next) => |
使うメソッドと注意点
接続開始リクエストが来たら、普通はリクエストを受け付けて接続を開始すると思います。
1 | if (context.WebSockets.IsWebSocketRequest) |
WebSocket のやり取りは受信と送信の繰り返しと終了処理となります。それぞれ下のように書きます。 CancellationToken
も必要ならしっかり設定したいところ。なお、 byte[]
でのやり取りになるので、文字列などはしっかり変換しなければなりません。 Encoding.UTF8.GetString()
や Encoding.UTF8.GetBytes()
を利用しましょう。
1 | // 受信 |
必要なメソッドは揃いましたが、 JavaScript ( Node.js ) での WebSocket の感覚で書いてはいけません。(下は擬似コードですが、これで永遠にオウム返しのやりとりができるはずです。)
1 | server.on('connection', function(socket) { |
これに似せたコードが Startup.cs の 60 行目から 94 行目ですが、こうすると 1 度送信して同じメッセージが返ってきたあとは無反応になります。
ASP.NET Core での WebSocket に関して誤解を恐れずに言えば、最初の方に出たリクエストというのは、コネクションの確立のためのリクエストであって、メッセージの受け渡しに関するリクエストではないということです。
接続を維持して通信するためには、永久ループの形をとり、終了時にループを出る構造にすると良いと思います。(受信待ち時は await
で止まっているので暴走の心配はない)
1 | var received = await socket.ReceiveAsync(receiveBuffer, receiveToken); |
Startup.cs の 101 行目から 142 行目にそのままありますが、形を抜き出すと上の感じです。繰り返しになりますが、ループによってやり取りを繰り返し、終了の合図でループを出て終了処理を行います。今回はテキストデータだけを受送信する仕様にしているので種類が Text
であるかを確かめていますが、バイナリデータも受信する場合は種類がCloseでないことをwhileの条件にすれば良いはずです。
ブロードキャスト
今のままでは、よくあるチャットプログラムの例のようには全員宛のメッセージが送れないので、接続中のクライアント全部に送ることができるよう拡張します。
具体的には Startup.cs の 151 行目から 197 行目に書いてありますが、コレクションを用意して接続されたものを追加しておくという、特にサプライズの無いコードを追加すれば OK です。そして、送る時にはコレクションの中身を列挙してそれぞれ SendAsync()
メソッドを実行すればよいのです。まだ詳しくわかりませんが、 ConcurrentBag
クラスはスレッドセーフなコレクションらしいです。
1 | // send message to all connections |
ちなみに Azure にデプロイして WebSocket のサーバーを動かす際には、ポータル上の設定で WebSocket の使用をオンにしないと、ポートが開かないので接続できないはず。