WebSocketを利用したアプリケーションの作成 - C#

WebSocketを利用したアプリケーションを作成します。

事前準備

サーバーにWebSocketの機能をインストールします。今回サーバーはIIS (Internet Information Services) を利用します。インストールの手順はこちらの記事を参照してください。

プログラム例:シンプルなリピーター

サーバーからリピートでメッセージが送信されるプログラムを作成します。

コード (サーバー側)

ジェネリックハンドラを作成し、下記のコードを実装します。
サーバー側 : RepeatHandler.ashx
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;
using System.Net.WebSockets;

namespace SimpleWebSocket
{
  /// <summary>
  /// RepeatHandler の概要の説明です
  /// </summary>
  public class RepeatHandler : IHttpHandler
  {

    public void ProcessRequest(HttpContext context)
    {
      if (context.IsWebSocketRequest) {
        context.AcceptWebSocketRequest(RepeatWebSocket);
      }
    }

    public async Task RepeatWebSocket(AspNetWebSocketContext context)
    {
      WebSocket socket = context.WebSocket;
      while (true) {
        if (socket.State == WebSocketState.Open) {
          string returnMessage = string.Format("{0}:やあ、こんにちは", DateTime.Now);
          ArraySegment<byte> returnBuffer 
            = new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(returnMessage));
          System.Threading.Thread.Sleep(3000);
          await socket.SendAsync(returnBuffer, WebSocketMessageType.Text, 
            true, System.Threading.CancellationToken.None);
        }
        else {
          break;
        }
      }
    }

    public bool IsReusable {
      get {
        return false;
      }
    }
  }
}

解説

ProcessRequestメソッドでは、下記のコードにより、AcceptWebSocketRequestメソッドを呼び出し、WebSocketの処理を実行します。第一引数にはWebSocketの処理を実行するTaskを与えます。
  if (context.IsWebSocketRequest) {
    context.AcceptWebSocketRequest(RepeatWebSocket);
  }

AcceptWebSocketRequestからの呼び出しで処理されるメソッドが下記コードです。
asyncが記述されており、非同期のメソッドとして実行されます。メソッド内では whileループにより接続を維持したまま処理を実行します。RepeatWebSocket()メソッドを抜けるとWebSocketとの接続が閉じられ処理が完了します。
Whileループ内ではawait socket.SendAsync()メソッドを呼び出しクライアントにデータを送信します。SendAsyncメソッドの第一引数には返信するデータのバッファを与えます。第二引数にはデータのタイプを指定します。第三引数はバッファ内のデータがメッセージの最後になるかを指定します。今回は1回の送信で1つのメッセージを送るため、trueとします。第4引数が操作を取り消す必要があることを示す通知トークンを指定します。今回はキャンセル動作はないため、Noneを与えます。
送信バッファはArraySegment<byte>オブジェクトで作成します。
  public async Task RepeatWebSocket(AspNetWebSocketContext context)
  {
    WebSocket socket = context.WebSocket;
    while (true) {
      if (socket.State == WebSocketState.Open) {
        string returnMessage = string.Format("{0}:やあ、こんにちは", DateTime.Now);
        ArraySegment<byte> returnBuffer 
          = new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(returnMessage));
        System.Threading.Thread.Sleep(3000);
        await socket.SendAsync(returnBuffer, WebSocketMessageType.Text, 
          true, System.Threading.CancellationToken.None);
      }
      else {
        break;
      }
    }
  }

WebSocketMessageType の値

名称値の意味
Binaryバイナリーデータ
Close接続が閉じられた
Textテキストデータ
今回は、SendAsyncのため、与える値はTextかBinaryのどちらかになります。

コード (クライアント側)

クライアント側のHTMLファイルを作成します。下記のコードを記述します。
RepeatClient.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
  <script type="text/javascript">
    var socket;

    function appendInfo(msg) {
      var textBox = document.getElementById("TextBoxReceive");
      textBox.value = textBox.value + msg + "\n";
    }

    function init() {
      var host = "ws://localhost:60524/RepeatHandler.ashx"

      try {
        socket = new WebSocket(host);

        socket.onopen = function () {
          appendInfo("socket onOpen イベントを受信");
        };
        socket.onclose = function () {
          appendInfo("socket onclose イベントを受信");
        };
        socket.onmessage = function (msg) {
          appendInfo("socket onmessage イベントを受信");
          appendInfo(msg.data);
        };
      } catch (ex) {
        alert(ex);
      }
    }
  </script>
</head>
<body onload="init();">
  <textarea id="TextBoxReceive" rows="20" cols="80"></textarea>
</body>
</html>

解説

ページの表示時に onloadイベントが実行されinit関数が呼び出されます。

init関数は下記のコードとなっています。
WebSocketオブジェクトを作成します。コンストラクタに接続するURLを与えます。WebSocketのURLのため、プロトコルが ws://になります。
WebSocketのイベントにイベントハンドラを設定して、受信時や接続時の処理を実装します。
今回の例では、ソケットが開かれたタイミング、ソケットが閉じられたタイミング、メッセージを受信したタイミングで画面にメッセージを表示します。また、メッセージ受信時は受信したメッセージも表示します。
function init() {
  var host = "ws://localhost:60524/RepeatHandler.ashx"

  try {
    socket = new WebSocket(host);

    socket.onopen = function () {
      appendInfo("socket onOpen イベントを受信");
    };
    socket.onclose = function () {
      appendInfo("socket onclose イベントを受信");
    };
    socket.onmessage = function (msg) {
      appendInfo("socket onmessage イベントを受信");
      appendInfo(msg.data);
    };
  } catch (ex) {
    alert(ex);
  }
}

実行結果

プロジェクトを実行します。Webブラウザが起動し下図の画面が表示されます。


"socket onOpen"のメッセージが表示され、サーバーとWebSocketの接続ができたメッセージが表示されます。その後、約3秒ごとに "socket onmessage イベントを受信" と 「やあ、こんにちは」のメッセージがテキストエリアに表示されます。


3秒に1回のペースでメッセージが送られ続けます。


WebSocketを利用したメッセージ受信プログラムができました。

プログラム例:双方向でやり取りをするリピーター

続いて、クライアント側からの送信により、サーバー側で切断処理をするリピーターを作成します。

コード (サーバー側)

ジェネリックハンドラを作成し、下記のコードを実装します。
RepeatCloseHandler.ashx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;
using System.Net.WebSockets;

namespace SimpleWebSocket
{
  /// <summary>
  /// RepeatCloseHandler の概要の説明です
  /// </summary>
  public class RepeatCloseHandler : IHttpHandler
  {

    public void ProcessRequest(HttpContext context)
    {
      if (context.IsWebSocketRequest) {
        context.AcceptWebSocketRequest(RepeatCloseWebSocket);
      }
    }

    public bool IsReusable
    {
      get
      {
        return false;
      }
    }

    public async Task RepeatCloseWebSocket(AspNetWebSocketContext context)
    {
      WebSocket socket = context.WebSocket;
      while (true) {
        if (socket.State == WebSocketState.Open) {
          string returnMessage = string.Format("{0}:やあ、こんにちは", DateTime.Now);
          ArraySegment<byte> returnBuffer = new ArraySegment<byte>(System.Text.Encoding.UTF8.GetBytes(returnMessage));
          await socket.SendAsync(returnBuffer, WebSocketMessageType.Text, true, System.Threading.CancellationToken.None);

          ArraySegment<byte> receiveBuffer = new ArraySegment<byte>(new byte[1024]);
          WebSocketReceiveResult result = await socket.ReceiveAsync(receiveBuffer, System.Threading.CancellationToken.None);

          if (result.MessageType == WebSocketMessageType.Text) {
              byte[] receive_byte = receiveBuffer.Take(result.Count).ToArray();
              string receiveText = System.Text.Encoding.UTF8.GetString(receive_byte);

            if (receiveText[0] == 'C') {
              break;
            }
          }
        }
        else {
          break;
        }
      }
    }
  }
}

解説

基本の動作は先のプログラムと同様ですが、System.Threading.Thread.Sleep(3000);せずに、socket.ReceiveAsync()メソッドを呼び出しクライアントとからのメッセージを待つ動作になります。
クライアントからのレスポンスを受け取るコードが下記です。ReceiveAsync()メソッドでクライアントからのメッセージを待ちます。ReceiveAsyncメソッドは非同期メソッドですが、awaitキーワードを利用して呼び出しているため、レスポンスが戻るまで以降の行は実行されず、処理が停止した状態になります。クライアントからのメッセージを受信すると以降の行が実行されます。
MessageTypeを判定し、テキストのメッセージで先頭の一文字目が'C'であれば、break文でwhileループを抜け接続を終了します。メッセージの一文字目が'C'でない場合は処理を続行し、再度クライアントにメッセージを送信します。
  ArraySegment<byte> receiveBuffer = new ArraySegment<byte>(new byte[1024]);
  WebSocketReceiveResult result = await socket.ReceiveAsync(receiveBuffer, System.Threading.CancellationToken.None);

  if (result.MessageType == WebSocketMessageType.Text) {
    byte[] receive_byte = receiveBuffer.Take(result.Count).ToArray();
    string receiveText = System.Text.Encoding.UTF8.GetString(receive_byte);

    if (receiveText[0] == 'C') {
      break;
    }
  }

コード (クライアント側)

クライアント側のHTMLファイルを作成します。下記のコードを記述します。
RepeatCloseClient.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title></title>
  <script type="text/javascript">
    var socket;

    function appendInfo(msg) {
      var textBox = document.getElementById("TextBoxReceive");
      textBox.value = textBox.value + msg + "\n";
    }

    function init() {
      var host = "ws://localhost:60524/RepeatCloseHandler.ashx"

      try {
        socket = new WebSocket(host);

        socket.onopen = function () {
          appendInfo("socket onOpen イベントを受信");
        };
        socket.onclose = function () {
          appendInfo("socket onclose イベントを受信");
        };
        socket.onmessage = function (msg) {
          appendInfo("socket onmessage イベントを受信");
          appendInfo(msg.data);
        };
      } catch (ex) {
        alert(ex);
      }
    }

    function ButtonSend_Click() {
      var message = document.getElementById("TextBoxSend").value;
      socket.send(message);
    }

  </script>

</head>
<body onload="init();">
  <textarea id="TextBoxReceive" rows="20" cols="80"></textarea><br />
  <input id="TextBoxSend" type="text" /><input id="ButtonSend" type="button" value="送信" onclick="ButtonSend_Click();" />
</body>
</html>

解説

先のプログラムと同様、ページの表示時に onloadイベントが実行されinit関数が呼び出されます。init関数の処理は先のプログラムと同様で、ソケットが開いたとき、閉じたとき、メッセージを受信したときにメッセージをテキストエリアに表示する動作です。

ボタンをクリックしたときのイベントハンドラは下記になります。テキストボックスに入力された文字列を取得し、sendメソッドを利用して送信します。
function ButtonSend_Click() {
  var message = document.getElementById("TextBoxSend").value;
  socket.send(message);
}

実行結果

プロジェクトを実行します。Webブラウザが表示されます。クライアントのHTML(RepeatCloseClient.html)を開きます。
WebSocketの接続メッセージとサーバーからのメッセージが表示されます。今回のプログラムでは1回メッセージが表示されるとクライアントからの受信待ちとなります。


画面下部のテキストボックスに"ABC"の文字列を入力し、[送信]ボタンをクリックします。


1文字目が'C'でないメッセージのため、切断されず再度メッセージが受信されテキストエリアに表示されます。テキストボックスに"DEF"の文字列を入力し、[送信]ボタンをクリックします。


メッセージが表示され続けます。


テキストボックスに"C"の文字列を入力し、[送信]ボタンをクリックします。


サーバー側から接続が閉じられ、WebSocketが閉じられた旨のメッセージがテキストエリアに表示されます。


WebSocketを利用したシンプルなプログラムを作成できました。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2024-01-06
作成日: 2018-03-01
iPentec all rights reserverd.