WebSocket(13)とC#通信

9507 ワード

(1)WebSocketとは
WebSocketは、HTML 5が提供し始めたブラウザとサーバ間のフルデュプレクス通信のネットワーク技術である.
WebSocket APIでは、ブラウザとサーバが握手をするだけで、ブラウザとサーバの間に高速チャネルが形成されます.両者の間で直接データを互いに転送することができる.
メリット:
a、サーバとクライアントの間で交換されるヘッダ情報は小さく、約2バイトしかありません
b、サーバーは自発的にクライアントにデータを転送できる
 
(2)WebSocket(13)握手
WebSocket握手はクライアントによって開始され、サンプルを報告します.
 
   GET/chat HTTP/1.1
   Host: server.example.com
   Upgrade: websocket
   Connection: Upgrade
   Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
   Origin: http://example.com
   Sec-WebSocket-Protocol: chat, superchat
   Sec-WebSocket-Version: 13
 
ここでSec-WebSocket-Versionはバージョン番号が13であることを示しています.注意Sec-WebSocket-Key、これはクライアントが送信した鍵で、サービス側はこの鍵を処理し、クライアントにフィードバックする必要があります.クライアントは鍵が正しいことを検証してから通信を開始します.その後、この鍵は役に立ちません.
サービス側フィードバックサンプル:
 
   HTTP/1.1 101 Switching Protocols
   Upgrade: websocket
   Connection: Upgrade
   Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
   Sec-WebSocket-Protocol: chat
 
C#サービス側はまず、Sec-WebSocket-Keyの文字列を抽出し、「258 EAFA 5-E 914-47 DA-95 CA-C 5 AB 0 DC 85 B 11」(固定GUID)を加え、SHA 1でハッシュコードを計算し、base 64で暗号化し、最終的にSec-WebSocket-Acceptの内容を生成する.握手コード:
 
private void handShake(byte[] recBytes, int recByteLength)
        {
            string recStr = Encoding.UTF8.GetString(recBytes, 0, recByteLength);
            string[] ss = recStr.Split(Environment.NewLine.ToCharArray());

            string key = ss[10].Replace("Sec-WebSocket-Key: ", "");
            key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
            SHA1 sha1 = SHA1.Create();
            byte[] sha1bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(key));
            string acceptStr = Convert.ToBase64String(sha1bytes);

            string sendStr = "HTTP/1.1 101 Switching Protocols" + NewLine +
                 "Upgrade: websocket" + NewLine +
                "Connection: Upgrade" + NewLine +
                "Sec-WebSocket-Accept: " + acceptStr + NewLine +
                "Sec-WebSocket-Protocol: chat" + NewLine + NewLine;
            client.Send(System.Text.Encoding.UTF8.GetBytes(sendStr));

            isHandshaked = true;
        }

 
(3)クライアントデータの受信
クライアントはsendメソッドを呼び出して、文字をサービス側に送信します.サービス側はframeの最初の2つのbyteをバイナリ(bit)で解析します.プロセスは次のとおりです.
   1byte
1 bit:frame-fin、x 0はこのmessageの後続にframeがあることを示す.x 1はメッセージの最後のフレームを表す
3 bit:それぞれframe-rsv 1、frame-rsv 2、frame-rsv 3で、通常はx 0
4 bit:frame-opcode、x 0はframeの継続を表す.x 1はテキストframeを表す.x 2はバイナリフレームを表す.x 3-7は非制御frameに保持される.x 8は接続を閉じることを示す.x 9はpingを表す.xAはpongを表す.xB-Fは制御frameに保持
   2byte
1 bit:Mask,1はこのframeがマスクを含むことを示す;0、マスクなし
7 bit、7 bit+2 byte、7 bit+8 byte:7 bitは整数値をとり、0-125の間であれば負荷データ長である.126で表される場合、後の2つのbyteは符号なし16ビットの整数値をとり、負荷長である.127は後の8 byteを表し、64ビットの符号なし整数値をとり、負荷長である
3-6 byte:ここで負荷長が0-125の間であり、Maskが1であると仮定すると、この4つのbyteはマスクである
7-end byte:長さは上から取り出した負荷長であり、拡張データとアプリケーションデータの2つの部分を含み、通常は拡張データがない.Maskが1の場合、このデータは復号する必要があり、復号規則は1-4 byteマスクサイクルとデータbyteが異種または動作する.
C#受信データコードは以下の通りです.
private bool recData(byte[] recBytes, int recByteLength)
        {
            if (recByteLength < 2)
                return false;

            bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1      
            if (!fin)
            {
                Console.WriteLine("recData exception:     "); //         
                return false;
            }

            bool mask_flag = (recBytes[1] & 0x80) == 0x80; //       
            if (!mask_flag)
            {
                Console.WriteLine("recData exception:   Mask"); //           
                return false;
            }

            int payload_len = recBytes[1] & 0x7F; //     

            byte[] masks = new byte[4];
            byte[] payload_data;
            if (payload_len == 126)
            {
                Array.Copy(recBytes, 4, masks, 0, 4);
                payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
                payload_data = new byte[payload_len];
                Array.Copy(recBytes, 8, payload_data, 0, payload_len);
            }
            else if (payload_len == 127)
            {
                Array.Copy(recBytes, 10, masks, 0, 4);
                byte[] uInt64Bytes = new byte[8];
                for (int i = 0; i < 8; i++)
                {
                    uInt64Bytes[i] = recBytes[9 - i];
                }
                UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);

                payload_data = new byte[len];
                for (UInt64 i = 0; i < len; i++)
                    payload_data[i] = recBytes[i + 14];
            }
            else
            {
                Array.Copy(recBytes, 2, masks, 0, 4);
                payload_data = new byte[payload_len];
                Array.Copy(recBytes, 6, payload_data, 0, payload_len);
            }

            for (var i = 0; i < payload_len; i++)
                payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);

            string content = Encoding.UTF8.GetString(payload_data);
            Console.WriteLine("client: {0}", content);

            return true;
        }

 
(3)クライアントへのデータ送信
サーバから送信されたデータは0 x 81で始まり、直後に送信されるコンテンツの長さ(長さが0-125の場合、1つのbyteが長さを表し、長さが0 x FFFFを超えない場合、後の2つのbyteが符号なし16ビット整数として長さを表し、0 x FFFFを超える場合、後の8つのbyteが符号なし64ビット整数として長さを表す)、最後にコンテンツのbyte配列である.
C#送信コード:
private void sendData(string content)
        {
            byte[] contentBytes = null;
            byte[] temp = Encoding.UTF8.GetBytes(content);
            if (temp.Length < 126)
            {
                contentBytes = new byte[temp.Length + 2];
                contentBytes[0] = 0x81;
                contentBytes[1] = (byte)temp.Length;
                Array.Copy(temp, 0, contentBytes, 2, temp.Length);
            }
            else if (temp.Length < 0xFFFF)
            {
                contentBytes = new byte[temp.Length + 4];
                contentBytes[0] = 0x81;
                contentBytes[1] = 126;
                contentBytes[2] = (byte)(temp.Length & 0xFF);
                contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
                Array.Copy(temp, 0, contentBytes, 4, temp.Length);
            }
            else
            {
                //         
            }

            client.Send(contentBytes);
        }

 
(4)htmlクライアントコード
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>WebSocket</title>
    <script type="text/javascript">
        if (!window.WebSocket)
            alert("WebSocket not supported by this browser!");

        var ws;
        function connectWS() {
            if (ws == null) {
                ws = new WebSocket("ws://127.0.0.1:5001");
                ws.onmessage = function (evt) {
                    alert("     :" + evt.data);
                };

                ws.onclose = function () {
                    alert("     。");
                    ws = null;
                };

                ws.onerror = function (evt) {
                    alert("    :" + evt.data);
                    ws = null;
                }

                ws.onopen = function (evt) {
                    alert("     。");
                };
            }
        }
        function sendWS() {
            if (ws != null) {
                try {
                    var sendstr = document.getElementById("txtSend").value;
                    if (sendstr == "")
                        return;
                    ws.send(sendstr);
                } catch (err) {
                    alert(err.Data);
                }
            } else {
                alert("    。");
            }
        }
        function closeWS() {
            ws.send("exit");
            ws.close();
        }
    </script>
</head>
<body style="text-align: center;">
    <input type="button" value="  " onclick="connectWS()" />
    <input type="button" value="  " onclick="closeWS()" />
    <br />
    <input type="text" id="txtSend" /><input type="button" id="btnSend" value="  " onclick="sendWS()" />
</body>
</html>

 
添付ファイルはサービス側コード、htmlテストページです.
 
以上