`
独自等待戈多
  • 浏览: 35388 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

WebSocket(13)和C#通信

阅读更多

(1)什么是WebSocket

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。

在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的字符串,加上“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”(是一个固定的GUID),用SHA1计算哈希码,用base64加密,最终生成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方法将字符窜发送到服务端。服务端要以二进制(bit)解析frame的前两个byte,过程如下:

   1byte

      1bit: frame-fin,x0表示该message后续还有frame;x1表示是message的最后一个frame

      3bit: 分别是frame-rsv1、frame-rsv2和frame-rsv3,通常都是x0

      4bit: frame-opcode,x0表示是延续frame;x1表示文本frame;x2表示二进制frame;x3-7保留给非控制frame;x8表示关闭连接;x9表示ping;xA表示pong;xB-F保留给控制frame

   2byte

      1bit: Mask,1表示该frame包含掩码;0,表示无掩码

      7bit、7bit+2byte、7bit+8byte: 7bit取整数值,若在0-125之间,则是负载数据长度;若是126表示,后两个byte取无符号16位整数值,是负载长度;127表示后8个byte,取64位无符号整数值,是负载长度

   3-6byte: 这里假定负载长度在0-125之间,并且Mask为1,则这4个byte是掩码

   7-end byte: 长度是上面取出的负载长度,包括扩展数据和应用数据两部分,通常没有扩展数据;若Mask为1,则此数据需要解码,解码规则为1-4byte掩码循环和数据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)发送数据到客户端

服务器发送的数据以0x81开头,紧接发送内容的长度(若长度在0-125,则1个byte表示长度;若长度不超过0xFFFF,则后2个byte作为无符号16位整数表示长度;若超过0xFFFF,则后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测试网页

 

以上

分享到:
评论
2 楼 sanrenxing_1 2017-03-17  
我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方便,GoEasy就挺不错的,我昨天试了一下,代码简洁易懂,几分钟我就洗了一个自己的实时推送功能;官网: http://goeasy.io/
1 楼 qnfng 2012-08-23  
兄弟没成啊,握手失败!没反应啊

相关推荐

Global site tag (gtag.js) - Google Analytics