概要
WebSocketとは、HTTPベースでクライアント、サーバー相互通信を実現する技術です
従来であれば、サーバからのレスポンスはクライアント起点(ボタン押下、定期ポーリング等)でしか発生させることしかできませんでした。
しかし、WebSocketを使用することにより、サーバー起点でレスポンスを返せるため、サーバでのイベント発生時に情報をクライアントに即時通知する用途で使用されることが多いと思います。
今回、C#でWebSocketを動かしてみたく探していたら、やりたいことそのままの記事があったので試してみました。
MSDNの以下の記事のサンプルを実行しました
https://msdn.microsoft.com/ja-jp/magazine/jj863133.aspx?f=255&MSPPError=-2147217396
サンプルを実行してみる
クライアントからリクエストを送り、サーバー側から現在時刻を受け取るサンプルを動かします。
WebSocketのプロトコルを知らなくても、専用ライブラリをそのまま使用するだけ
クライアント側の実装
クライアントアプリに以下を記述し呼び出し
static async Task getWebTest()
{
//クライアント側のWebSocketを定義
ClientWebSocket ws = new ClientWebSocket();
//接続先エンドポイントを指定
var uri = new Uri("ws://localhost:8000/ws/");
//サーバに対し、接続を開始
await ws.ConnectAsync(uri, CancellationToken.None);
var buffer = new byte[1024];
//情報取得待ちループ
while (true)
{
//所得情報確保用の配列を準備
var segment = new ArraySegment<byte>(buffer);
//サーバからのレスポンス情報を取得
var result = await ws.ReceiveAsync(segment, CancellationToken.None);
//エンドポイントCloseの場合、処理を中断
if (result.MessageType == WebSocketMessageType.Close)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "OK",
CancellationToken.None);
return;
}
//バイナリの場合は、当処理では扱えないため、処理を中断
if (result.MessageType == WebSocketMessageType.Binary)
{
await ws.CloseAsync(WebSocketCloseStatus.InvalidMessageType,
"I don't do binary", CancellationToken.None);
return;
}
//メッセージの最後まで取得
int count = result.Count;
while (!result.EndOfMessage)
{
if (count >= buffer.Length)
{
await ws.CloseAsync(WebSocketCloseStatus.InvalidPayloadData,
"That's too long", CancellationToken.None);
return;
}
segment = new ArraySegment<byte>(buffer, count, buffer.Length - count);
result = await ws.ReceiveAsync(segment, CancellationToken.None);
count += result.Count;
}
//メッセージを取得
var message = Encoding.UTF8.GetString(buffer, 0, count);
Console.WriteLine("> " + message);
}
}
サーバ側の実装
サーバアプリに以下を記述、こちらも専用ライブラリをそのまま使用するだけ
今回はサンプルにあるテスト用の簡易的なサーバーで実施しているため
本来ならば実装予定のサーバサイドに同等の記述が必要
.NETでない場合は、ライブラリに頼れないため、プロトコルを理解し実装が必要とおもわれます
static async Task Run()
{
//Httpリスナーを立ち上げ、クライアントからの接続を待つ
HttpListener s = new HttpListener();
s.Prefixes.Add("http://localhost:8000/ws/");
s.Start();
var hc = await s.GetContextAsync();
//クライアントからのリクエストがWebSocketでない場合は処理を中断
if (!hc.Request.IsWebSocketRequest)
{
//クライアント側にエラー(400)を返却し接続を閉じる
hc.Response.StatusCode = 400;
hc.Response.Close();
return;
}
//WebSocketでレスポンスを返却
var wsc = await hc.AcceptWebSocketAsync(null);
var ws = wsc.WebSocket;
//10回のレスポンスを返却
for (int i = 0; i != 10; ++i)
{
//1回のレスポンスごとに2秒のウエイトを設定
await Task.Delay(2000);
//レスポンスのテストメッセージとして、現在時刻の文字列を取得
var time = DateTime.Now.ToLongTimeString();
//文字列をByte型に変換
var buffer = Encoding.UTF8.GetBytes(time);
var segment = new ArraySegment<byte>(buffer);
//クライアント側に文字列を送信
await ws.SendAsync(segment, WebSocketMessageType.Text,
true, CancellationToken.None);
}
//接続を閉じる
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure,
"Done", CancellationToken.None);
}
実行してみる
上記ができたら、サーバを事前に動かしておき、クライアント側からサーバへ要求を実行
成功すると、以下のようにサーバ側の現在時刻が2秒間隔で10回クライアントに送られる
> 15:42:08
> 15:42:10
> 15:42:12
> 15:42:14
> 15:42:16
> 15:42:18
> 15:42:20
> 15:42:22
以上