--- title: C# で WebSocket サーバーを記述する slug: Web/API/WebSockets_API/Writing_WebSocket_server tags: - HTML5 - NeedsMarkupWork - WebSockets - チュートリアル translation_of: Web/API/WebSockets_API/Writing_WebSocket_server ---

イントロダクション

WebSocket API を使用したい場合は、サーバーを持っている場合に便利です。この記事では、C# で記述する方法を説明します。どんなサーバーサイドの言語でも行うことができますが、わかりやすく理解しやすいように、Microsoft の言語を選択しました。

このサーバーは RFC 6455 に準拠しているため、Chrome バージョン16、Firefox 11、IE 10 以上の接続のみを処理します。

ファーストステップ

WebSocket は TCP (伝送制御プロトコル) 接続を介して通信します。幸いにも、C# には TcpListener クラスがあり、その名前が示すようにします。これは System.Net.Sockets 名前空間にあります。

少なく書くためには名前空間を using キーワードに含めることをお勧めします。毎回完全な名前空間を入力することなく、名前空間のクラスを使用できます。

TcpListener

コンストラクタ:

TcpListener(System.Net.IPAddress localaddr, int port)

localaddr はリスナーの IP を指定し、port はポートを指定します。

string からIPAddress オブジェクトを作成するには、IPAddress の静的 Parse メソッドを使用します。

メソッド:

ベアボーンサーバーの実装は次のとおりです。

​using System.Net.Sockets;
using System.Net;
using System;

class Server {
    public static void Main() {
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);

        server.Start();
        Console.WriteLine("Server has started on 127.0.0.1:80.{0}Waiting for a connection...", Environment.NewLine);

        TcpClient client = server.AcceptTcpClient();

        Console.WriteLine("A client connected.");
    }
}

TcpClient

メソッド:

プロパティ:

NetworkStream

メソッド:

私たちの例を拡張してみましょう。

TcpClient client = server.AcceptTcpClient();

Console.WriteLine("A client connected.");

NetworkStream stream = client.GetStream();

//enter to an infinite cycle to be able to handle every change in stream
while (true) {
    while (!stream.DataAvailable);

    Byte[] bytes = new Byte[client.Available];

    stream.Read(bytes, 0, bytes.Length);
}

ハンドシェイキング

クライアントがサーバーに接続すると、単純な HTTP リクエストから WebSocket への接続をアップグレードするための GET リクエストが送信されます。これはハンドシェイキングと呼ばれます。

このサンプルコードはクライアントから GET を検出できます。これは、メッセージの最初の 3 バイトが利用可能になるまでブロックされることに注意してください。運用環境では、代替ソリューションを検討する必要があります。

using System.Text;
using System.Text.RegularExpressions;

while(client.Available < 3)
{
   // wait for enough bytes to be available
}

Byte[] bytes = new Byte[client.Available];

stream.Read(bytes, 0, bytes.Length);

//translate bytes of request to string
String data = Encoding.UTF8.GetString(bytes);

if (Regex.IsMatch(data, "^GET")) {

} else {

}

リクエストは簡単に作成できますが、理解するのは少し難しいかもしれません。サーバーのハンドシェイクの完全な説明は RFC 6455、セクション4.2.2 にあります。私たちの目的のために、簡単なレスポンスを作成します。

やらなければならないのは:

  1. 先行または後続空白なしで "Sec-WebSocket-Key" リクエストヘッダーの値を取得します
  2. それを "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455 で指定された特別な GUID)
  3. 新しい値の SHA-1 および Base64 ハッシュを計算します
  4. HTTP レスポンスの "Sec-WebSocket-Accept" レスポンスヘッダの値としてハッシュを書き戻します

if (new System.Text.RegularExpressions.Regex("^GET").IsMatch(data))
{
    const string eol = "\r\n"; // HTTP/1.1 defines the sequence CR LF as the end-of-line marker

    Byte[] response = Encoding.UTF8.GetBytes("HTTP/1.1 101 Switching Protocols" + eol
        + "Connection: Upgrade" + eol
        + "Upgrade: websocket" + eol
        + "Sec-WebSocket-Accept: " + Convert.ToBase64String(
            System.Security.Cryptography.SHA1.Create().ComputeHash(
                Encoding.UTF8.GetBytes(
                    new System.Text.RegularExpressions.Regex("Sec-WebSocket-Key: (.*)").Match(data).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
                )
            )
        ) + eol
        + eol);

    stream.Write(response, 0, response.Length);
}

メッセージのデコード

ハンドシェイクが成功すると、クライアントはエンコードされたメッセージをサーバーに送信します。

"MDN" を送信すると、次のバイトが得られます。

129 131 61 84 35 6 112 16 109

これらのバイトの意味を見てみましょう。

現在 129 の値を持つ最初のバイトは、次のように分解するビットフィールドです。

FIN (Bit 0) RSV1 (Bit 1) RSV2 (Bit 2) RSV3 (Bit 3) Opcode (Bit 4:7)
1 0 0 0 0x1=0001

現在 131 の値を持つ 2 番目のバイトは、次のように分解する別のビットフィールドです。

MASK (Bit 0) ペイロードの長さ (Bit 1:7)
1 0x83=0000011

最初のビットはクライアントからサーバーへのメッセージでは常に 1 なので、このバイトから 128 を引いて MASK ビットを取り除くことができます。

メッセージに MASK ビットが設定されていることに注意してください。これは次の4バイト (61、84、35、および6) がメッセージのデコードに使用されるマスクバイトであることを意味します。これらのバイトはすべてのメッセージとともに変化します。

残りのバイトはエンコードされたメッセージペイロードです。

アルゴリズムのデコード

Di = Ei XOR M(i mod 4)

D は復号されたメッセージ配列、E は符号化されたメッセージ配列、M はマスクバイト配列、i はデコードするメッセージバイトのインデックスです。

C# の例です:

Byte[] decoded = new Byte[3];
Byte[] encoded = new Byte[3] {112, 16, 109};
Byte[] mask = new Byte[4] {61, 84, 35, 6};

for (int i = 0; i < encoded.Length; i++) {
    decoded[i] = (Byte)(encoded[i] ^ mask[i % 4]);
}

関連