Websocket轻量级消息推送 & 浏览器socket通信

来源:互联网 发布:mac本地播放器 编辑:程序博客网 时间:2024/05/22 13:58

摘要:WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。WebSocket通信协议于2011年被IETF定为标准RFC6455,并被RFC7936所补充规范。


简单的说,Websocket协议之前,双工通信是通过多个HTTP链接来实现,这导致了效率低下(轮询)。另外就是,一般浏览器和tomcat通信十分方便,那么当一个电脑上没有服务器框架的时候,该如何与浏览器通信呢?只能建立socket进行通信。而浏览器是不能建立普通socket的,只有最近几年开始才有了一种叫做Websocket的协议。

长久以来,创建实现客户端和用户端之间双工通讯的Web App都会造成HTTP轮询的滥用:客户端向主机不断发送不同的HTTP呼叫来进行询问。

这会导致一系列的问题:

  1. 服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。
  2. 有线协议有很高的开销,每一个客户端和服务器之间都有HTTP头。
  3. 客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复。
    一个更简单的解决方案是使用单个TCP连接双向通信。 这就是Websocket协议所提供的功能。 结合Websocket API ,Websocket协议提供了一个用来替代HTTP轮询实现网页到远程主机的双向通信的方法。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

WebSocket借用HTTP请求进行握手,相比正常的HTTP请求,多了一些内容。 浏览器请求,一般不太需要关心,这里主要讲一下服务器如何回应Websocket请求。

Sec-WebSocket-Accept是将请求包Sec-WebSocket-Key的值,与258EAFA5-E914-47DA-95CA-C5AB0DC85B11这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码得到的。

首先是与浏览器完成握手,一些具体的信息可以参见《WebSocket》一文。

/// <summary>/// 打包握手信息/// </summary>/// <param name="secKeyAccept"></param>/// <returns></returns>private static byte[] packHandShakeData(string secKeyAccept){    Console.WriteLine("Package hand shake.");    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);    return Encoding.UTF8.GetBytes(responseBuilder.ToString());}/// <summary>/// 生成Sec-WebSocket-Accept/// </summary>/// <param name="handShakeText">客户端握手信息</param>/// <returns>Sec-WebSocket-Accept</returns>private static string getSecKeyAccept(byte[] handShakeBytes, int bytesLength){    Console.WriteLine("Get secure key accept.");    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);}

握手完成之后,即刻进入数据的传输之中,Wesocket有一套自己的数据封装协议,下图摘自RFC6455,文中开头处已给出文献地址,

这里写图片描述

这里需要根据以上协议,完成对数据流的解析。这里有几个重要的部分,

  • 监听断开TCP链接;
  • 127表示——每个数据包PB级别以上(每包数据2^64bit),由于目前网络状态下,暂不需要,故未解析,
/// <summary>/// 解析Websocket字节流/// </summary>/// <param name="recvBytes">接收到的数据包</param>/// <param name="recvLength">数据包长度</param>/// <returns></returns>private static int decodeWebsocket(byte[] recvBytes, int recvLength, /*out*/ref string decodeMessage){    if (recvLength < 2) { //data error        decodeMessage = "Maybe websocket data length error.";        return Config.WEBSOCRT_ERROR_LENGTH;    }    //decode fin    if ((recvBytes[0] & 0x80) != 0x80) {        //TODO:fin=1 为末尾帧        decodeMessage = "The packet is not finished.";        return Config.WEBSOCRT_FIN_DELAY;    }    //decode rsv    if ((recvBytes[0] & 0x40) == 0x40) {         //rsv1 = 1    }    if ((recvBytes[0] & 0x20) == 0x20){        //rsv2 = 1    }    if ((recvBytes[0] & 0x10) == 0x10){        //rsv3 = 1    }    //decode opcode    switch (recvBytes[0] & 0x0F){        case 0x00: break; //连续帧        case 0x01: break; //文本帧        case 0x02: break; //二进制帧        case 0x03: case 0x04: case 0x05: case 0x06:        case 0x07: break; //非控制帧保留        case 0x08:            {                decodeMessage = "The websocket is broken.";                return Config.WEBSOCRT_DISCONNECT; //关闭连接            }        case 0x09: break; //ping        case 0x0A: break; //pong        case 0x0B: case 0x0C: case 0x0D: case 0x0E:         case 0x0F: break; //控制帧保留    }    if (recvLength < 3) {//data error        decodeMessage = "Maybe payload length lost.";        return Config.WEBSOCRT_ERROR_LENGTH;    }    bool mask = false;    //decode mask    if ((recvBytes[1] & 0x80) == 0x80) {         mask = true;    }    int payloadLen = (recvBytes[1] & 0x7F);    byte[] maskValue = null;    byte[] payloadValue = null;    //decode 0-125    if (payloadLen < 126) {         //0-125        if (recvLength < payloadLen + 6) {//data error            decodeMessage = "Maybe payload content lost(0-125).";            return Config.WEBSOCRT_ERROR_CONTENT;        }        if (mask) {            maskValue = new byte[4];            //mask-key            Array.Copy(recvBytes, 2, maskValue, 0, 4);        }        payloadValue = new byte[payloadLen];        Array.Copy(recvBytes, 6, payloadValue, 0, payloadLen);    }    //decode 126    if (payloadLen == 126) {        if (recvLength < 8) {//data error            decodeMessage = "Maybe payload length lost(126).";            return Config.WEBSOCRT_ERROR_LENGTH;        }        payloadLen = (UInt16)(recvBytes[2] << 8 | recvBytes[3]);        if (recvLength < payloadLen + 8) {//data error            decodeMessage = "Maybe payload content lost(126).";            return Config.WEBSOCRT_ERROR_CONTENT;        }        if (mask) {            maskValue = new byte[4];            //mask-key            Array.Copy(recvBytes, 4, maskValue, 0, 4);        }        payloadValue = new byte[payloadLen];        Array.Copy(recvBytes, 8, payloadValue, 0, payloadLen);    }    //decode 127    if (payloadLen == 127)    {//超级大包,暂不处理(64bit二进制长度,每次传输TB级别以上数据包)        decodeMessage = "A super big packet(127).";        return Config.WEBSOCRT_BIG127;    }    for (int i = 0; i < payloadLen; i++)    {//mask op        payloadValue[i] = (byte)(payloadValue[i] ^ maskValue[i % 4]);    }    decodeMessage = Encoding.UTF8.GetString(payloadValue);    return Config.WEBSOCRT_OK;}

对服务端发送的数据,进行Websocket编码,以便于浏览器能顺利识别数据包…

private static byte[] encodeWebsocket(string msg) {    byte[] sendBytes = null;    byte[] tmpBytes = Encoding.UTF8.GetBytes(msg);    //encode 0-125    if (tmpBytes.Length < 126)    {        sendBytes = new byte[tmpBytes.Length + 2];        sendBytes[0] = 0x81;//1000 0001,fin,mask        sendBytes[1] = (byte)tmpBytes.Length;//length        Array.Copy(tmpBytes, 0, sendBytes, 2, tmpBytes.Length);//data    }    else if (tmpBytes.Length < 0xFFFF)    {//encode 126        sendBytes = new byte[tmpBytes.Length + 4];        sendBytes[0] = 0x81;//fin,mask        sendBytes[1] = 126; //126        sendBytes[3] = (byte)(tmpBytes.Length & 0xFF);//length        sendBytes[2] = (byte)(tmpBytes.Length >> 8 & 0xFF);//length continue...        Array.Copy(tmpBytes, 0, sendBytes, 4, tmpBytes.Length);//data    }    else if (tmpBytes.Length >= 0xFFFF)    { //encode 127,超级大包(每包数据,TB级别以上),不处理    }    return sendBytes;}

关于本文DEMO,请转至《Git:Websocket》自取。主要有浏览器客户端Js实现,服务端并发实现。