WebSocket协议简介

WebSocket协议有两部分:握手和数据传输

协议预览

来自客户端的握手

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

来自服务器的握手

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

来自客户端的引导行遵从Request-Line格式,来自服务器的引导行遵从Status-Line格式。Request-Line和Status-Line在[RFC2616]定义。

两种情况下,引导行后面跟着一组未排序的头域。这些头域的意义在本文档第4章指定。额外的头域也可能出现,如cookie[RFC6265]。头格式和解析在[RFC2616]定义。

一旦客户端和服务器都发送了他们的握手。如果握手成功,传输数据部分开始。这是一个双向传输通道,每个端都能独立、随意发送数据。

握手成功后,客户端和服务器来回传输的数据是以消息message为概念单位。在传输介质上(on the wire),一个消息由一个或多个帧frame组成。WebSocket消息不需要对应到特定网络层的帧,业务分帧后的消息可能被中间设施合并或分拆。

一个帧都有一个关联的类型。属于同一个消息的帧拥有相同的数据类型,广义的说,有文本数据(解析为utf-8文本)、二进制数据(他的解析留给了应用程序)和控制帧(不打算携带应用数据,携带的是协议层的信号,如连接关闭信号)类型。

客户端握手

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
  • 必须是有效的http request格式
  • HTTP request method必须是GET, 协议不小于1.1。如:GET /chat HTTP/1.1
  • 必须包含Upgrade头域,并且其值为"websocket"
  • 必须包含Connect头域,并且其值为"Upgrade"
  • 必须包括"Sec-WebSocket-Key"头域,其值采用base64编码的随机16字节长的字符序列, 服务器端根据该域来判断client 确实是websocket请求而不是冒充的,如http。响应方式是,首先要获取到请求头中的Sec-WebSocket-Key的值,再把这一段GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"加到获取到的Sec-WebSocket-Key的值的后面,然后拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,就得到了返回给客户端的Sec-WebSocket-Accept的http响应头的值。
  • 如果请求来自浏览器客户端,还必须包括Origin头域。该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接。
  • 必须包括"Sec-webSocket-Version" 头域,当前值必须是13.
  • 可能包括"Sec-WebSocket-Protocol",表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之。
  • 可能包括"Sec-WebSocket-Extensions", 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强。
  • 可能包括任意其他域,如cookie。

服务端握手

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
  • 处理请求包括处理GET方法
  • 验证Upgrade头域
  • 验证Connection头域
  • 处理Sec-WebSocket-Key头域
  • 处理Sec-WebSocket-Version头域
  • 处理Origin头域,可选,浏览器必须发送该头域
  • 处理Sec-WebSocket-Protocol头域,可选
  • 处理Sec-WebSocket-Extensions头域,可选
  • Server发送握手响应,这里只介绍服务器接受该连接情况下,包括:
    • http Status-Line
    • Upgrade头域,值必须是”websocket“
    • Connection头域,值必须是”Upgrade“
    • Sec-WebSocket-Accept头域,该头域的值即处理Sec-WebSocket-Key域后的结果。
    • 可选的Sec-WebSocket-Protocol头域
    • 可选的Sec-WebSocket-Extensions头域

WebSocket 数据帧格式

WebSocket数据帧格式
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
  • FIN:1位,是否是消息的结束帧(分片)
  • RSV1, RSV2, RSV3: 分别都是1位,预留,用于约定自定义协议。如果双方之间没有约定自定义协议,那么这几位值都必须为0,否则必须断掉WebSocket连接。
  • Opcode: 4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接必须断掉,以下是定义的操作码:
    • %x0 表示消息连接分片
    • %x1 表示文本消息分片
    • %x2 表示二进制消息分片
    • %x3-7 为将来的非抗震消息片段保留的操作码
    • %x8 表示连接关闭
    • %x9 表示心跳检查的ping
    • %xA 表示心跳检查的pong
    • %xB-F 为将来的控制消息片段保留的操作码
  • Mask:定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;
  • Payload length:传输数据的长度,以字节的形式表示:7位,7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度。注意Payload length不包括Masking-key在内。
  • **Masking-key: **0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。 数据Mask方法是,第 i byte 数据 = orig-data ^ (i % 4) .
  • **Payload data: **(x+y)位,负载数据为扩展数据及应用数据长度之和。
  • Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。
  • Applicatoin data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。
ws-frame                = frame-fin  
                          frame-rsv1  
                          frame-rsv2  
                          frame-rsv3  
                          frame-opcode  
                          frame-masked  
                          frame-payload-length  
                          [ frame-masking-key ]  
                          frame-payload-data  
  
frame-fin               = %x0 ; 表示这不是当前消息的最后一帧,后面还有消息  
                        / %x1 ; 表示这是当前消息的最后一帧  
  
frame-rsv1              = %x0  
                          ; 1 bit, 如果没有扩展约定,该值必须为0  
  
frame-rsv2              = %x0  
                          ; 1 bit, 如果没有扩展约定,该值必须为0  
  
frame-rsv3              = %x0  
                          ; 1 bit, 如果没有扩展约定,该值必须为0  
  
frame-opcode            = %x0 ; 表示这是一个连续帧消息  
                        / %x1 ; 表示文本消息  
                        / %x2 ; 表示二进制消息  
                        / %x3-7 ; 保留  
                        / %x8 ; 表示客户端发起的关闭  
                        / %x9 ; ping(用于心跳)  
                        / %xA ; pong(用于心跳)  
                        / %xB-F ; 保留  
  
frame-masked            = %x0 ; 数据帧没有加掩码,后面没有掩码key  
                        / %x1 ; 数据帧加了掩码,后面有掩码key  
  
frame-payload-length    = %x00-7D  
                        / %x7E frame-payload-length-16  
                        / %x7F frame-payload-length-63  
   ; 表示数据帧的长度  
  
frame-payload-length-16 = %x0000-FFFF  
   ; 表示数据帧的长度  
  
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF  
   ; 表示数据帧的长度  
  
frame-masking-key       = 4( %0x00-FF ) ; 掩码key,只有当掩码位为1时出现  
  
frame-payload-data      = (frame-masked-extension-data  
                           frame-masked-application-data)   ; 当掩码位为1时,这里的数据为带掩码的数据,扩展数据及应用数据都带掩码  
                        / (frame-unmasked-extension-data  
                           frame-unmasked-application-data) ; 当掩码位为0时,这里的数据为不带掩码的数据,扩展数据及应用数据都不带掩码  
  
frame-masked-extension-data     = *( %x00-FF ) ; 目前保留,以后定义  
  
frame-masked-application-data   = *( %x00-FF )  
  
frame-unmasked-extension-data   = *( %x00-FF ) ; 目前保留,以后定义  
  
frame-unmasked-application-data = *( %x00-FF ) 

引用以下文章

WebSocket Protocol
https://github.com/zhangkaitao/websocket-protocol
WebSocket握手协议
RFC-6455 WebSocket Git-Book

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

推荐阅读更多精彩内容