近期工做忙碌,为了赶SegmentFault for Android 4.0
版,到了发疯的程度。
我来汇报一个进度,已经实现基于websocket
的私信系统了,多亏了70大大不懈的努力,在不久的未来,咱们使用web给手机发送私信的愿望很快就能够达成了。html
那么在这之余,由于我的对各个通讯协议都有很有兴趣,便顺便去看了下ietf
写的关于websocket
的文章,原文连接在这:java
https://tools.ietf.org/html/r...nginx
固然若是你对实现websocket
协议并没什么兴趣的话,本文能够直接略过,由于你大概知道怎么用就能够了= =web
websocket
目前仍未有标准化的文档,因此目前个人理解是基于RFC6455
草案。ajax
websocket
的诞生场景是由于咱们在浏览器上缺乏一种与服务器保持长链接的标准化的技术,由于HTTP 1.1
(如下简称为HTTP
)协议只是一个标准的无状态协议,并不存在除了request
和response
生命周期以外的通讯场景。若是咱们须要实如今线聊天的功能的话,那么基于HTTP
,咱们只能使用丑陋的long polling
和ajax 轮询
等非正常的技术实现咱们须要的功能。这些方案虽然解决了咱们的使用场景,但并非最好的方案,咱们的HTTP
服务器软件并非设计来保持长链接的。跨域
基于以上场景,websocket
诞生了。浏览器
它是一种真正的长链接,能让服务端和客户端进行持久的通讯,轻松的实现服务器推送技术。安全
它和
HTTP
并无特别多的关系,可是它的握手 (handshake) 是利用HTTP
来完成。服务器它的本质是基于
TCP
的链接,能够说和HTTP
属于平级的应用层协议。websocket
草案阐述了websocket
的设计目标,除了创建起基于TCP的一层链接之外,还有如下几个特色
为浏览器增长一个基于域名的安全模型(也就是跨域安全模型)
增长一个基于域名和地址的机制,用来支持在一个IP地址上的一端口多域名的多服务模型(相似nginx在同一个ip上使用不一样的域名来路由到不一样的服务上)
在流式的TCP创建一个帧数据包模型,而且没有长度限制
增长了一个额外的关闭链接握手,给代理和其余中间件使用。
websocket
的握手实际上就是给服务器发送一个GET
请求,里面带上指定的header
便可。
request
例子以下
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
其中比较特殊的是Upgrade
,Connection
,和Sec
开头的几个字段,那么若是请求握手的话,
Upgrade: websocket Connection: Upgrade
是固定的要填写的两个键值对。Sec-WebSocket-Key
是一个16位的随机值,通过base64编码后生成,给服务器进行UUID链接再编码后由客户端检查用。Sec-WebSocket-Version
是使用的版本号。Sec-WebSocket-Protocol
是选用的子协议,此字段为可选字段,由服务器选择一个子协议与客户端通讯,子协议是由websocket
承载的协议。
response
例子以下
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
咱们能够看到这是一个状态码为101
的响应,响应的头内容基本和request
能够对应,Sec-WebSocket-Accept
是服务端利用Key
和UUID
拼接后再进行base64编码产生的一个值,由客户端进行验证。
这样,咱们的链接时握手就完成了。
由于一些安全的缘由,从客户端发送到服务端的帧所有要与掩码
进行异或运算过才有效,而服务端发送到客户端的帧不须要进行异或运算。
咱们来看下官方的一幅帧结构定义图
接下来逐一解释。
名称 | 长度 | 注释 |
---|---|---|
FIN | 1bit | 标明这一帧是不是整个消息体的最后一帧 |
RSV1 RSV2 RSV3 | 1bit | 保留位,必须为0,若是不为0,则标记为链接失败 |
opcode | 4bit | 操做位,定义这一帧的类型 |
Mask | 1bit | 标明承载的内容是否须要用掩码进行异或 |
Masking-key | 0 or 4bytes | 掩码异或运算用的key |
Payload length | 7bit or 7 +16bit or 7 + 64bit | 承载体的长度(后续会解释为何会有3种长度) |
若是从结构角度讲,那么websocket
帧结构就这么简单。
在websocket中,咱们定义了几种操做类型,也就是代表了数据包的行为,数据包大致可分为两种,一种是字符数据包 (string)
,一种是字节数据包 (byte)
不一样的数据包使用不一样的opcode
来传输,opcode
定义以下:
首先Opcode
占用4bit
的长度
值 | 定义 |
---|---|
%x0 | 标明这一个数据包是上一个数据包的延续,它是一个延长帧 (continuation frame) |
%x1 | 标明这个数据包是一个字符帧 (text frame) |
%x2 | 标明这个数据包是一个字节帧 (binary frame) |
%x3-7 | 保留值,供将来的非控制帧使用 |
%x8 | 标明这个数据包是用来告诉对方,我方须要关闭链接 |
%x9 | 标明这个数据包是一个心跳请求 (ping) |
%xA | 标明这个数据包是一个心跳响应 (pong) |
%xB-F | 保留至,供将来的控制帧使用 |
若是是客户端发送到服务端的数据包,咱们须要使用掩码对payload
的每个字节进行异或运算,生成masked payload
才能被服务器读取。
具体的运算其实很简单。
假设payload
长度为pLen
,mask-key
长度为mLen
,i
做为payload
的游标,j
做为mask-key
的游标,伪代码以下:
for (i = 0; i < pLen; i++){ int j = i % mLen; maskedPayload[i] = payload[i] ^ maskKey[j]; }
Payload Length
位占用了可选的7bit
或者7 + 16bit
或者 7 + 64bit
,这里是什么意思呢? MDN上有文章也是对websocket
协议进行了很好的阐述,先贴原文:
引用其中关于Payload length
定义的一段文字:
读解负载数据长度
读取负载数据,须要知道读到那里为止。所以获知负载数据长度很重要。这个过程稍微有点复杂,要如下这些步骤:
读取9-15位 (包括9和15位自己),并转换为无符号整数。若是值小于或等于125,这个值就是长度;若是是 126,请转到步骤 2。若是它是 127,请转到步骤 3。
读取接下来的 16 位并转换为无符号整数,并做为长度。
读取接下来的 64 位并转换为无符号整数,并做为长度。
固然咱们这边所使用的都是网络字节序
。
关闭链接的时候,只用发送一个opcode
为0x08的帧,payload
中前2个字节写入定义的code
,后续写入关闭链接的reason
,那么一个关闭流程就握手就开始,此处再也不赘述。
websocket
协议总体看下来其实仍是很简单,也为咱们的工做节省了不少没必要要的麻烦。也许咱们并不须要了解它实现的细节(除非你正在开发非浏览器的客户端或者服务端)
总而言之,想要学习一个技术,看原始规范果真会让人畅快淋漓啊~