热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[网络基础知识]WebSocket协议

WebSocket的出现,使得浏览器具备了实时双向通信的能力。什么是WebSocketHTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,

WebSocket的出现,使得浏览器具备了实时双向通信的能力。


什么是WebSocket

HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道
对网络应用层协议的学习来说,最重要的往往就是连接建立过程、数据交换过程。当然,数据的格式是逃不掉的,因为它直接决定了协议本身的能力。好的数据格式能让协议更高效、扩展性更好。


如何建立连接

WebSocket复用了HTTP的握手通道。具体指的是,客户端通过HTTP请求与WebSocket服务端协商升级协议。协议升级完成后,后续的数据交换则遵照WebSocket的协议。


客户端:申请协议升级

首先,客户端发起协议升级请求。可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。

GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==

可以看到,前两行跟HTTP的Request的起始行一模一样,而真正在WS的握手过程中起到作用的是下面几个header域:


  • Upgrade:upgrade是HTTP1.1中用于定义转换协议的header域。它表示,如果服务器支持的话,客户端希望使用现有的「网络层」已经建立好的这个「连接(此处是TCP连接)」,切换到另外一个「应用层」(此处是WebSocket)协议;
  • Connection:HTTP1.1中规定Upgrade只能应用在「直接连接」中,所以带有Upgrade头的HTTP1.1消息必须含有Connection头,因为Connection头的意义就是,任何接收到此消息的人(往往是代理服务器)都要在转发此消息之前处理掉Connection中指定的域(不转发Upgrade域)。如果客户端和服务器之间是通过代理连接的,那么在发送这个握手消息之前首先要发送CONNECT消息来建立直接连接;
  • Sec-WebSocket-Version:13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
  • Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接
  • Origin:作安全使用,防止跨站攻击,浏览器一般会使用这个来标识原始域。

服务端:响应协议升级

服务端返回内容如下,状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=

备注:每个header都以\r\n结尾,并且最后一行加上一个额外的空行\r\n。此外,服务端回应的HTTP状态码只能在握手阶段使用。过了握手阶段后,就只能采用特定的错误码。


对于客户端握手信息的一些小要求


  • 此Request消息的方法必须是GET,HTTP版本必须大于1.1 :
  •   - 以下是某WS的Uri对应的Request消息:
  •   - ws://example.com/chat
  •   - GET /chat HTTP/1.1
  • 此Request消息中Request-URI部分所定义的资型必须和WS协议的Uri中定义的资源相同。
  • 此Request消息中必须含有Host头域,其内容必须和WS的Uri中定义的相同。
  • 此Request消息必须包含Upgrade头域,其内容必须包含websocket关键字。
  • 此Request消息必须包含Connection头域,其内容必须包含Upgrade指令。
  • 此Request消息必须包含Sec-WebSocket-Key头域,其内容是一个Base64编码的16位随机字符。
  • 如果客户端是浏览器,此Request消息必须包含Origin头域,其内容是参考RFC6454。
  • 此Request消息必须包含Sec-WebSocket-Version头域,在此协议中定义的版本号是13。
  • 此Request消息可能包含Sec-WebSocket-Protocol头域,其意义如上文中所述。
  • 此Request消息可能包含Sec-WebSocket-Extensions头域,客户端和服务器可以使用此header来进行一些功能的扩展。
  • 此Request消息可能包含任何合法的头域。如RFC2616中定义的那些。

在客户端接收到Response握手消息之后要做的一些事情

接收到Response握手消息之后:


  • 如果返回的返回码不是101,则按照RFC2616进行处理。如果是101,进行下一步,开始解析header域,所有header域的值不区分大小写;
  • 判断是否含有Upgrade头,且内容包含websocket;
  • 判断是否含有Connection头,且内容包含Upgrade;
  • 判断是否含有Sec-WebSocket-Accept头;
  • 如果含有Sec-WebSocket-Extensions头,要判断是否之前的Request握手带有此内容,如果没有,则连接失败;
  • 如果含有Sec-WebSocket-Protocol头,要判断是否之前的Request握手带有此协议,如果没有,则连接失败。

数据帧格式

WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。
发送端:将消息切割成多个帧,并发送给服务端;接收端:接收消息帧,并将关联的帧重新组装成完整的消息;

客户端和服务器连接成功后,就可以进行通信了,通信协议格式是WebSocket格式,服务器端采用Tcp Socket方式接收数据,进行解析,协议格式如下


熟悉TCP/IP协议的同学对这样的图应该不陌生。从左到右,单位是比特。比如FIN、RSV1各占据1比特,opcode占据4比特。
内容包括了标识、操作代码、掩码、数据、数据长度等。

FIN:1个比特。如果是1,表示这是消息(message)的最后一个分片(fragment),如果是0,表示不是是消息(message)的最后一个分片(fragment)。
RSV1, RSV2, RSV3:各占1个比特。一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。
Opcode: 4个比特。操作代码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection)。可选的操作代码如下:


  • 1)Opcode == 0 继续:表示此帧是一个继续帧,需要拼接在上一个收到的帧之后,来组成一个完整的消息。由于这种解析特性,非控制帧的发送和接收必须是相同的顺序。
  • 2)Opcode == 1 文本帧。
  • 3)Opcode == 2 二进制帧。
  • 4)Opcode == 3 - 7 未来使用(非控制帧)。
  • 5)Opcode == 8 关闭连接(控制帧):此帧可能会包含内容,以表示关闭连接的原因。通信的某一方发送此帧来关闭WebSocket连接,收到此帧的一方如果之前没有发送此帧,则需要发送一个同样的关闭帧以确认关闭。如果双方同时发送此帧,则双方都需要发送回应的关闭帧。理想情况服务端在确认WebSocket连接关闭后,关闭相应的TCP连接,而客户端需要等待服务端关闭此TCP连接,但客户端在某些情况下也可以关闭TCP连接。
  • 6)Opcode == 9 Ping:类似于心跳,一方收到Ping,应当立即发送Pong作为响应。
  • 7)Opcode == 10 Pong:如果通信一方并没有发送Ping,但是收到了Pong,并不要求它返回任何信息。Pong帧的内容应当和收到的Ping相同。可能会出现一方收到很多的Ping,但是只需要响应最近的那一次就可以了。
  • 8)Opcode == 11 - 15 未来使用(控制帧)。

Mask:1个比特。表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。如果Mask是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask都是1。
Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。
假设数Payload length === x,如果


  • x为0~126:数据的长度为x字节。
  • x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
  • x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。

此外,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(big endian,重要的位在前)。
Masking-key:0或4字节(32位)所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为1,且携带了4字节的Masking-key。如果Mask为0,则没有Masking-key。备注:载荷数据的长度,不包括mask key的长度。
Payload data:(x+y) 字节


  • 载荷数据:包括了扩展数据、应用数据。其中,扩展数据x字节,应用数据y字节。
  • 扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
  • 应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

与HTTP比较

同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方。
其实Http和WebSocket的关系通过下图就能简单的理解了


相同点


  1. 都是基于TCP的应用层协议;
  2. 都使用Request/Response模型进行连接的建立;
  3. 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码;
  4. 都可以在网络中传输数据。

不同点


  1. WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用;
  2. WS的连接不能通过中间人来转发,它必须是一个直接连接;
  3. WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据;
  4. WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息;
  5. WS的数据帧有序。

原来你是这样的Websocket--抓包分析


推荐阅读
author-avatar
听海的音_104
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有