--- 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(System.Net.IPAddress localaddr, int port)
localaddr はリスナーの IP を指定し、port はポートを指定します。
string からIPAddress オブジェクトを作成するには、IPAddress の静的 Parse メソッドを使用します。
メソッド:
Start()System.Net.Sockets.TcpClient AcceptTcpClient()ベアボーンサーバーの実装は次のとおりです。
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.");
}
}
メソッド:
System.Net.Sockets.NetworkStream GetStream()プロパティ:
int AvailableNetworkStream.DataAvailable が true になるまでゼロです。メソッド:
Write(Byte[] buffer, int offset, int size)Read(Byte[] buffer, int offset, int size)私たちの例を拡張してみましょう。
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 にあります。私たちの目的のために、簡単なレスポンスを作成します。
やらなければならないのは:
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]);
}