websocket
Websocket借用了HTTP的协议来完成一部分握手,是一个持久化的协议,是相对于HTTP这种非持久(长连接,循环连接不算)的协议来说,更像是HTTP协议上的一种补充。Web Socket是一种HTML 5为Web定制的全双工通讯协议,没有“请求 - 响应”的概念,浏览器与服务器完全平等,连接一旦建立就一直开放,双方可随时向对方发送任意数据,没有推拉之分。
WebSocket在2011年被IETF定为标准RFC 6455,并被RFC 7936补充规范,WebSocket API被W3C定为标准。WebSocekt 是 HTML5 规范中的一部分,其借鉴了 socket 的思想,为 client 和 server 之间提供了类似的双向通信机制。同时,WebSocket 又是一种新的应用层协议,包含一套标准的 API;而 socket 并不是一个协议,而是一组接口,其主要方便大家直接使用更底层的协议(比如 TCP 或 UDP)
ws最大的优势:在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息,数据的传输使用帧来传递,并且允许跨域通信。
-
socket.io
网络应用中,两个应用程序同时需要向对方发送消息的能力(即全双工通信),所利用到的技术就是 socket,其能够提供端对端的通信。对于程序员而言,其需要在 A 端创建一个 socket 实例,并为这个实例提供其所要连接的 B 端的 IP 地址和端口号,而在 B 端创建另一个 socket 实例,并且绑定本地端口号来进行监听。当 A 和 B 建立连接后,双方就建立了一个端对端的 TCP 连接,从而可以进行双向通信。
目前主流的浏览器都支持WebSocket,并且有第三方的API:Guillermo Rauch创建了一个Socket.IO,遵循Engine.IO协议Engine.IO protocol。socket.io 底层是 engine.io,这个库实现了跨平台的双向通信,使用了 WebSocket 和 XMLHttprequest(或JSONP) 封装了一套自己的 Socket 协议,在低版本浏览器里面使用长轮询替代 WebSocket。一个完整的 Engine.IO Socket 包括多个 XHR 和 WebSocket 连接。
为了在每一种浏览器上支持实时通讯,Socket.IO 在运行中选择最佳的传输方式,而不影响API。支持的方式为:WebSocket, Adobe Flash Socket, AJAX long polling, AJAX multipart streaming, Forever Iframe, JSONP Polling。
// socket.io 流程 client request xhr ---->> server return { open: 0, sid, upgrades: [websocket/longpoll] } client start WebSocket -- ping probe -->> server return pong probe -->> stop longpoll
-
websocket vs http
http WebSocket 单向通信,client请求 全双工双向通信 基于http,支持性好 新的协议,兼容http 简单,轻量,断线重连 较复杂,重连需额外部署 少几个属性 Upgrade: websocket / Connection: Upgrade / Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== websocket与服务端进行握手,是通过http协议完成的,请求->响应,最为关键的请求报文字段Sec-WebSocket-Key, 它是一个Base64 encode的值,这个是浏览器随机生成的。服务端收到报文后,如果支持 WebSocket 协议,那么就会将自己的通信协议切换到 WebSocket,并且会返回Sec-WebSocket-Accept,这个则是经过服务端加密过后的 Sec-WebSocket-Key。
-
发送包类型:
- 0: open transport is opened(传输打开)
- 1: close
- 2: ping Send by client. Server should answer with a pong packet containing the same data
- 3: pong Sent by the server to respond to ping packets.
- 4: message actual message, client and server should call their callbacks with the data.(callback - 42, not - 41)
- 5: upgrade polling or websoket
- 6: noop 等待
-
数据成帧
WebSocket 使用了自定义的二进制分帧格式,把每个应用消息切分成一或多个帧,发送到目的地之后再组装起来,等到接收到完整的消息后再通知接收端。基本的成帧协议定义了帧类型有操作码、有效载荷的长度,指定位置的Extension data和Application data,统称为Payload data,保留了一些特殊位和操作码供后期扩展。在打开握手完成后,终端发送一个关闭帧之前的任何时间里,数据帧可能由客户端或服务器的任何一方发送。具体的帧格式如下所示:
-
FIN: 1 bit 。FIN是FINAL的缩写,它为1时表示一个数据传输结束。此位标示此帧是否是消息的最后帧,第一帧也可能是最后帧。
-
RSV1,RSV2,RSV3: 各1 bit 。必须是0,除非协商了扩展定义了非0的意义。
-
opcode:4 bit。表示被传输帧的类型:x0 表示一个后续帧;x1 表示一个文本帧;x2 表示一个二进制帧;x3-7 为以后的非控制帧保留;x8 表示一个连接关闭;x9 表示一个ping;xA 表示一个pong;xB-F 为以后的控制帧保留。同一个数据即使分片传输,它的每个数据帧的Opcode也应该相同。
-
Mask: 1 bit。表示净荷是否有掩码(只适用于客户端发送给服务器的消息)。
-
Payload length: 7 bit, 7 + 16 bit, 7 + 64 bit。 净荷长度由可变长度字段表示: 如果是 0~125,就是净荷长度;如果是 126,则接下来 2 字节表示的 16 位无符号整数才是这一帧的长度; 如果是 127,则接下来 8 字节表示的 64 位无符号整数才是这一帧的长度。
-
Masking-key:0或4 Byte。 用于给净荷加掩护,客户端到服务器标记。
-
Extension data: x Byte。默认为0 Byte,除非协商了扩展。
-
Application data: y Byte。 在”Extension data”之后,占据了帧的剩余部分。
-
Payload data: (x + y) Byte。”extension data” 后接 “application data”。
-
-
生成(封装)数据帧
待续…
-
解析数据帧
function decodeDataFrame(e) { var i = 0, j, s, frame = { //解析前两个字节的基本数据 FIN: e[i]>>7, Opcode: e[i++]&15, Mask: e[i]>>7, PayloadLength: e[i++]&0x7F }; //处理特殊长度126和127 if(frame.PayloadLength == 126) frame.length = (e[i++]<<8)+e[i++]; if(frame.PayloadLength==127) frame.length = (e[i++]<<24)+(e[i++]<<16)+(e[i++]<<8)+e[i++]; //判断是否使用掩码 if(frame.Mask){ //获取掩码实体 frame.MaskingKey = [e[i++],e[i++],e[i++],e[i++]]; //对数据和掩码做异或运算 for(j = 0, s = []; j < frame.PayloadLength; j++) s.push(e[i+j]^frame.MaskingKey[j%4]); } else { s = e.slice(i,frame.PayloadLength); //否则直接使用数据 } //数组转换成缓冲区来使用 s = new Buffer(s); //如果有必要则把缓冲区转换成字符串来使用 if(frame.Opcode == 1) s = s.toString(); //设置上数据部分 frame.PayloadData = s; //返回数据帧 return frame; }; function onmessage(e){ e = decodeDataFrame(e); //解析数据帧 console.log(e); //把数据帧输出到控制台 };
-
example:
client connects through new transport
client sends 2probe
server receives and sends 3probe
client receives and sends 5
server flushes and closes old transport and switches to new.