0
点赞
收藏
分享

微信扫一扫

【整理】html5知识点5

============================================HTML5 WebSocket



 什么是websocket



  WebSocket 协议是html5引入的一种新的协议,其目的在于实现了浏览器与服务器全双工通信。看了上面链接的同学肯定对过去怎么低效率高消耗(轮询或comet)的做此事已经有所了解了,而在websocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。同时这么做有两个好处



 1.通信传输字节减少:比起以前使用http传输数据,websocket传输的额外信息很少,据百度说只有2k



 2.服务器可以主动向客户端推送消息,而不用客户端去查询



 握手



  除了TCP连接的三次握手,websocket协议中客户端与服务器想建立连接需要一次额外的握手动作,在最新版的协议中是这个样子的



 客户端向服务器发送请求



 GET / HTTP/1.1

 Upgrade: websocket

 Connection: Upgrade

 Host: 127.0.0.1:8080

 Origin: http://test.com

 Pragma: no-cache

 Cache-Control: no-cache

 Sec-WebSocket-Key: OtZtd55qBhJF2XLNDRgUMg==

 Sec-WebSocket-Version: 13

 Sec-WebSocket-Extensions: x-webkit-deflate-frame

 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36

 服务器给出响应



 HTTP/1.1 101 Switching Protocols

 Upgrade: websocket

 Connection: Upgrade

 Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA=



  在请求中的“Sec-WebSocket-Key”是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用 SHA-1 加密,之后进行 BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端



 websocket API



 经过握手之后浏览器与服务器建立连接,两者就可以互相通信了。websocket的API真心很简单,看看 W3C 的定义



 tring url, optional (DOMString or DOMString[]) protocols)]

 interface WebSocket : EventTarget {

   readonly attribute DOMString url;



   // ready state

   const unsigned short CONNECTING = 0;

   const unsigned short OPEN = 1;

   const unsigned short CLOSING = 2;

   const unsigned short CLOSED = 3;

   readonly attribute unsigned short readyState;

   readonly attribute unsigned long bufferedAmount;



   // networking

            attribute EventHandler onopen;

            attribute EventHandler onerror;

            attribute EventHandler onclose;

   readonly attribute DOMString extensions;

   readonly attribute DOMString protocol;

   void close([Clamp] optional unsigned short code, optional DOMString reason);



   // messaging

            attribute EventHandler onmessage;

            attribute BinaryType binaryType;

   void send(DOMString data);

   void send(Blob data);

   void send(ArrayBuffer data);

   void send(ArrayBufferView data);

 };



 ==================================================

 创建websocket



 ws=new WebSocket(address); //ws://127.0.0.1:8080

 调用其构造函数,传入地址,就可以创建一个websocket了,值得注意的是地址协议得是ws/wss



 关闭socket



 ws.close();

 调用webservice实例的close()方法就可以关闭webservice,当然也可以传入一个code和string说明为什么关了



 几个回调函数句柄



 由于其异步执行,回调函数自然少不了,有四个重要的



 onopen:连接创建后调用

 onmessage:接收到服务器消息后调用

 onerror:出错时调用

 onclose:关闭连接的时候调用

 看名字就知道是干什么的了,每个回调函数都会传入一个Event对象,可以通过event.data访问消息



 使用API



 我们可以在创建socket成功后为其回调函数赋值



 ws=new WebSocket(address);

             ws.onopen=function(e){

                 var msg=document.createElement('div');

                 msg.style.color='#0f0';

                 msg.innerHTML="Server > connection open.";

                 msgContainer.appendChild(msg);

                 ws.send('{<'+document.getElementById('name').value+'>}');

             };

  也可以通过事件绑定的方式



 ws=new WebSocket(address);

             ws.addEventListener('open',function(e){

                 var msg=document.createElement('div');

                 msg.style.color='#0f0';

                 msg.innerHTML="Server > connection open.";

                 msgContainer.appendChild(msg);

                 ws.send('{<'+document.getElementById('name').value+'>}');

             });

 ============================================================================

 客户端实现



 其实客户端的实现比较简单,除了websocket相关的几句就是一些自动focus、回车键事件处理、消息框自动定位到底部等简单功能,不一一说明了



 【demo】已验证

 页面

 <!DOCTYPE html>

 <html>

 <head>

     <title></title>

     <script type="text/javascript">

         var ws;

         function ToggleConnectionClicked() {

             try {

                 var SOCKECT_ADDR = "ws://localhost:1818/chat";

                 ws = new WebSocket(SOCKECT_ADDR);
  



                 ws.onopen = function (event) { alert("已经与服务器建立了连接\r\n当前连接状态:" + this.readyState); };

                 ws.onmessage = function (event) { alert("接收到服务器发送的数据:\r\n" + event.data); };

                 ws.onclose = function (event) { alert("已经与服务器断开连接\r\n当前连接状态:" + this.readyState); };

                 ws.onerror = function (event) {alert("WebSocket异常!" + event.toString());};

             } catch (ex) {

                 alert(ex.message);

             }

         };



         function SendData() {

             try {

                 ws.send("success");

             } catch (ex) {

                 alert(ex.message);

             }

         };



         function seestate() {

             alert(ws.readyState);

         }

        

     </script>

 </head>

 <body>

     <button id='ToggleConnection' type="button" οnclick='ToggleConnectionClicked();'>

         连接服务器</button><br />

     <br />

     <button id='ToggleConnection' type="button" οnclick='SendData();'>

         发送我的名字:beston</button><br />

     <br />

     <button id='ToggleConnection' type="button" οnclick='seestate();'>

         查看状态</button><br />

     <br />

 </body>

 </html>



 服务器端控制台程序

 using System;

 using System.Text;

 using System.Net;

 using System.Net.Sockets;

 using System.Text.RegularExpressions;

 using System.Security.Cryptography;



 namespace WebSocketServer

 {

     class Program

     {

         static void Main(string[] args)

         {

             int port = 1818;

             byte[] buffer = new byte[1024];



             IPEndPoint localEP = new IPEndPoint(IPAddress.Any, port);

             Socket listener = new Socket(localEP.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);



             try

             {

                 listener.Bind(localEP);

                 listener.Listen(10);



                 Console.WriteLine("等待客户端连接....");

                 Socket sc = listener.Accept();//接受一个连接

                 Console.WriteLine("接受到了客户端:" + sc.RemoteEndPoint.ToString() + "连接....");



                 //握手

                 int length = sc.Receive(buffer);//接受客户端握手信息

                 sc.Send(PackHandShakeData(GetSecKeyAccetp(buffer, length)));

                 Console.WriteLine("已经发送握手协议了....");



                 //接受客户端数据

                 Console.WriteLine("等待客户端数据....");

                 length = sc.Receive(buffer);//接受客户端信息

                 string clientMsg = AnalyticData(buffer, length);

                 Console.WriteLine("接受到客户端数据:" + clientMsg);



                 //发送数据

                 string sendMsg = "您好," + clientMsg;

                 Console.WriteLine("发送数据:“" + sendMsg + "” 至客户端....");

                 sc.Send(PackData(sendMsg));



                 Console.WriteLine("演示Over!");

                 Console.Read();

             }

             catch (Exception e)

             {

                 Console.WriteLine(e.ToString());

             }



         }



         /// <summary>

         /// 打包握手信息

         /// </summary>

         /// <param name="secKeyAccept">Sec-WebSocket-Accept</param>

         /// <returns>数据包</returns>

         private static byte[] PackHandShakeData(string secKeyAccept)

         {

             var responseBuilder = new StringBuilder();

             responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);

             responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);

             responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);

             responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);

             //如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白!

             //responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine);

             //responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine);



             return Encoding.UTF8.GetBytes(responseBuilder.ToString());

         }



         /// <summary>

         /// 生成Sec-WebSocket-Accept

         /// </summary>

         /// <param name="handShakeText">客户端握手信息</param>

         /// <returns>Sec-WebSocket-Accept</returns>

         private static string GetSecKeyAccetp(byte[] handShakeBytes, int bytesLength)

         {

             string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);

             string key = string.Empty;

             Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");

             Match m = r.Match(handShakeText);

             if (m.Groups.Count != 0)

             {

                 key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();

             }

             byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));

             return Convert.ToBase64String(encryptionString);

         }



         /// <summary>

         /// 解析客户端数据包

         /// </summary>

         /// <param name="recBytes">服务器接收的数据包</param>

         /// <param name="recByteLength">有效数据长度</param>

         /// <returns></returns>

         private static string AnalyticData(byte[] recBytes, int recByteLength)

         {

             if (recByteLength < 2) { return string.Empty; }



             bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧  

             if (!fin)

             {

                 return string.Empty;// 超过一帧暂不处理 

             }



             bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码  

             if (!mask_flag)

             {

                 return string.Empty;// 不包含掩码的暂不处理

             }



             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]);

             }



             return Encoding.UTF8.GetString(payload_data);

         }





         /// <summary>

         /// 打包服务器数据

         /// </summary>

         /// <param name="message">数据</param>

         /// <returns>数据包</returns>

         private static byte[] PackData(string message)

         {

             byte[] contentBytes = null;

             byte[] temp = Encoding.UTF8.GetBytes(message);



             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

             {

                 // 暂不处理超长内容  

             }



             return contentBytes;

         }

     }

 }




举报

相关推荐

0 条评论