websocket 协议解析 [RFC6455]

近期工作忙碌,为了赶SegmentFault for Android 4.0版,到了发疯的程度。
我来汇报一个进度,已经实现基于websocket的私信系统了,多亏了70大大不懈的努力,在不久的将来,我们使用web给手机发送私信的愿望很快就可以达成了。

那么在这之余,因为个人对各个通信协议都有颇有兴趣,便顺便去看了下ietf写的关于websocket的文章,原文链接在这:

https://tools.ietf.org/html/rfc6455

当然如果你对实现websocket协议并没什么兴趣的话,本文可以直接略过,因为你大概知道怎么用就可以了= =

概念与背景

websocket 目前仍未有标准化的文档,所以目前我的理解是基于RFC6455草案。

websocket的诞生场景是因为我们在浏览器上缺少一种与服务器保持长连接的标准化的技术,因为HTTP 1.1(以下简称为HTTP)协议只是一个标准的无状态协议,并不存在除了requestresponse生命周期之外的通信场景。如果我们需要实现在线聊天的功能的话,那么基于HTTP,我们只能使用丑陋的long pollingajax 轮询等非正常的技术实现我们需要的功能。这些方案虽然解决了我们的使用场景,但并不是最好的方案,我们的HTTP服务器软件并不是设计来保持长连接的。

基于以上场景,websocket诞生了。

  1. 它是一种真正的长连接,能让服务端和客户端进行持久的通信,轻松的实现服务器推送技术。
  2. 它和HTTP并没有特别多的关系,但是它的握手 (handshake) 是利用HTTP来完成。
  3. 它的本质是基于TCP的连接,可以说和HTTP属于平级的应用层协议。

草案阐述了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

其中比较特殊的是UpgradeConnection,和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是服务端利用KeyUUID拼接后再进行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帧结构就这么简单。

操作码 (opcode)

定义
%x0 标明这一个数据包是上一个数据包的延续,它是一个延长帧 (continuation frame)
%x1 标明这个数据包是一个字符帧 (text frame)
%x2 标明这个数据包是一个字节帧 (binary frame)
%x3-7 保留值,供未来的非控制帧使用
%x8 标明这个数据包是用来告诉对方,我方需要关闭连接
%x9 标明这个数据包是一个心跳请求 (ping)
%xA 标明这个数据包是一个心跳响应 (pong)
%xB-F 保留至,供未来的控制帧使用

在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 保留至,供未来的控制帧使用

关于掩码 (Mask)

如果是客户端发送到服务端的数据包,我们需要使用掩码对payload的每一个字节进行异或运算,生成masked payload 才能被服务器读取。
具体的运算其实很简单。

假设payload长度为pLenmask-key长度为mLeni作为payload的游标,j作为mask-key的游标,伪代码如下:

for (i = 0; i < pLen; i++){
    int j = i % mLen;
    maskedPayload[j] = payload[j] ^ maskKey[j];
}

Payload长度

Payload Length位占用了可选的7bit或者7 + 16bit 或者 7 + 64bit,这里是什么意思呢? MDN上有文章也是对websocket协议进行了很好的阐述,先贴原文:

编写websocket服务器

引用其中关于Payload length定义的一段文字:

读解负载数据长度

读取负载数据,需要知道读到那里为止。因此获知负载数据长度很重要。这个过程稍微有点复杂,要以下这些步骤:

  1. 读取9-15位 (包括9和15位本身),并转换为无符号整数。如果值小于或等于125,这个值就是长度;如果是 126,请转到步骤 2。如果它是 127,请转到步骤 3。
  2. 读取接下来的 16 位并转换为无符号整数,并作为长度。
  3. 读取接下来的 64 位并转换为无符号整数,并作为长度。

当然我们这边所使用的都是网络字节序

关闭连接时的握手

关闭连接的时候,只用发送一个opcode为0x08的帧,payload中前2个字节写入定义的code,后续写入关闭连接的reason,那么一个关闭流程就握手就开始,此处不再赘述。

总结

websocket协议整体看下来其实还是很简单,也为我们的工作节省了很多不必要的麻烦。也许我们并不需要了解它实现的细节(除非你正在开发非浏览器的客户端或者服务端)

总而言之,想要学习一个技术,看原始规范果然会让人畅快淋漓啊~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • <!DOCTYPE html> 查看源 window.WRM=window.WRM||{};window....
    SMSM阅读 846评论 1 0
  • 导演要带女演员去宾馆,女演员有些不愿意,导演说:“我和你爸妈是好朋友,难道还会拿你怎么样吗!”女演员便从了。到了宾...
    Junee_e阅读 7,251评论 1 2
  • 有一位伙伴来咨询: 他在一家新媒体创业公司,去年毕业加入到现在快一年时间了。突然有点迷茫了。因为这一年来,公司变化...
    陈sir阅读 1,235评论 2 12
  • 首先要有静 有静一样的,河 有静一样的,钓鱼人 然后才有突然而起的风暴 突然而起。两只野鸭子 翅膀拍击空气,激...
    西雨2016阅读 396评论 0 1