リアルタイムな通信を行う際に何かと活躍するプロトコル WebSocket。 .NETには System.Net.WebSockets として組み込まれており、簡単に使い始めることができる。
しかし、WebSocketにおいて「切断」という概念は取り扱いに注意が必要だ。 本記事ではSystem.Net.WebSocketsが切断をどのように扱っているか見ていく。
WebSocketにおける正しい切断
WebSocket は Socket という名前からも想像できるように、基となるのはTCP Socketだ。 よってTCP Socketを閉じれば切断できるが、これは正しい切断ではない。 RFC 6455 - The WebSocket Protocol の "Section 1.4 Closing Handshake" によると切断時にもHandshake処理があり、流れは次のようになる。
- 切断したい側から Close Frame を送信する
- 受け取った側は Close Frame を応答として返す
- Close Frame の送信・受信がどちらも終わったらTCP socketを閉じる
System.Net.WebSocketsにおける切断処理
では、.NETでのClosing Handshakeの実装はどうなっているか。 関わるメソッドがいくつかあるので表にまとめた。
メソッド | 処理内容 |
---|---|
WebSocket.CloseAsync | こちら側からClose Frameを送り、相手からClose Frameの応答が返ってくるのを待つ。その後TCPを切断する。 |
WebSocket.CloseOutputAsync | 相手からClose Frameの応答が返ってくるのを待つ。その後TCPを切断する。 |
WebSocket.Dispose | TCPを切断する。 |
一見似た名前のメソッドが3つもあるが、それぞれの処理内容を把握すれば使い所がわかる。 Active-close(こちら側からの能動的な切断)であればCloseAsyncが正常に終われば正しい切断となる。
// Close Frameの送信を行い、Close Frame応答の受信を待機する。 await webSocket.CloseAsync();
そして、Passive-close(Close Frameを受け取った側)はメッセージ受信処理に次のような対応をすればよい。
var result = webSocket.ReceiveAsync(buffer, ct); switch (result.MessageType) { // ... case WebSocketMessageType.Close: // Close Frameの応答を返す await client.CloseOutputAsync(result.CloseStatus!.Value, result.CloseStatusDescription, ct); break; }
最後に残ったのはDisposeだが、これはCloseAsyncまたはCloseOutputAsyncの中で呼ばれるため基本は呼ぶ必要がない。 Closing Handshakeをすっ飛ばして強制的にTCP Closeをしたい場合は使えるが…そんな機会はあまりないだろう。
ReceiveとCloseの並列性
WebSocketはリアルタイム性のある通信処理に利用されるため、Receive (ReceiveAsync)を継続的に呼び出し相手からの受信メッセージを処理することが多い。 その場合、Receiveを待機しつつ他のスレッドからCloseを呼ぶ、または相手からCloseが飛んでくるケースが考えられる。
CloseAsyncまたはCloseOutputAsync内では「相手からのClose Frameの受信を待機する」処理があるため、Receive と並列で呼んで大丈夫なのか?と気になるところだ。
しかし、結論から言うとこれは問題ない。ManagedWebSocketのxmldocに次のように書かれている。
- It's acceptable to have a pending ReceiveAsync while CloseOutputAsync or CloseAsync is called.
実際にReceiveしながらCloseAsyncを呼んだところ、CloseAsyncは正常に終わり、ReceiveAsyncでは Close Frame を受け取ることができた。
Close完了後の例外
Closing Handshakeが完了したあとにSend/Receiveをはじめとする通信メソッドを呼ぶとWebSocketException例外が投げられる。
エラーコードは WebSocketError.InvalidState
となる。
不正な切断
正常なClosing Handshakeを経ずにTCP接続が突然切断された場合は、Receiveメソッドの中でWebSocketException例外が投げられ
エラーコードは WebSocketError.ConnectionClosedPrematurely
となる。