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呼叫来进行询问。
这会导致一系列的问题:
- 服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。
- 有线协议有很高的开销,每一个客户端和服务器之间都有HTTP头。
- 客户端脚本被迫维护从传出连接到传入连接的映射来追踪回复。
一个更简单的解决方案是使用单个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实现,服务端并发实现。
- Websocket轻量级消息推送 & 浏览器socket通信
- WebSocket即时通信、web消息推送
- WebSocket点对点通信-Tomcat websocket点对点通信-实时通信-服务器消息推送
- Websocket消息推送平台
- websocket消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- java websocket消息推送
- webSocket 消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- 消息推送之Websocket
- WebSocket与消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- WebSocket与消息推送
- 梯度及最小二乘估计器
- 用LinkedList实现自己的Stack
- Kotlin笔记(一):AndroidStduio配置Kotlin环境
- spark hive执行树
- 机器学习之回归简介
- Websocket轻量级消息推送 & 浏览器socket通信
- CF689D:Friends and Subsequences(ST表 + 二分)
- CentOS6.5下Redis安装与配置
- springsecurity和shiro权限控制
- 机器学习之特征工程简介
- 我的数据库大作业——学生选课系统实现(准备)
- 自定义View(一)之初识自定义View
- Hibernate & mybatis
- CSS巧妙实现分隔线的几种方法