之前总结了关于Websocket协议的握手连接方式等其他细节,现在对socket连接建立后的数据帧传输和关闭细节总结。

一、数据帧格式

数据传输使用的是一系列数据帧,出于安全考虑和避免网络截获,客户端发送的数据帧必须进行掩码处理后才能发送到服务器,不论是否是在TLS安全协议上都要进行掩码处理。服务器如果没有收到掩码处理的数据帧时应该关闭连接,发送一个1002的状态码。服务器不能将发送到客户端的数据进行掩码处理,如果客户端收到掩码处理的数据帧必须关闭连接。

基本的数据帧为一个opcode、一个payload长度和发送的应用数据,根据ABNF的定义,详细信息如下图

这里使用的是数据存储的位(bit),当进行加密的时候,最终要的一位就是最左边的第一个。

  • FIN :1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧。
  • RSV1,RSV2,RSV3:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。
  • Opcode:4bit,解释Payload数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:0x0(附加数据帧)    0x1(文本数据帧)   0x2(二进制数据帧)    0x3-7(保留为之后非控制帧使用)  0xB-F(保留为后面的控制帧使用)    0x8(关闭连接帧)  0x9(ping)  0xA(pong)
  • Mask:1bit,掩码,定义payload数据是否进行了掩码处理,如果是1表示进行了掩码处理。
    Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
  • Payload length:7位,7 + 16位,7+64位,payload数据的长度,如果是0-125,就是真实的payload长度,如果是126,那么接着后面的2个字节对应的16位无符号整数就是payload数据长度;如果是127,那么接着后面的8个字节对应的64位无符号整数就是payload数据的长度。
  • Masking-key:0到4字节,如果MASK位设为1则有4个字节的掩码解密密钥,否则就没有。
  • Payload data:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。

二、客户端到服务器端掩码处理

前面说过客户端发送到服务器端的数据必须进行掩码处理,掩码的密钥是一个32位的随机值,客户端随机选取密钥必须是不可猜测的。这个掩码处理后并不影响Payload数据的长度。服务器收到掩码处理后的数据后,解码需要使用如下的算法进行:
j = i mod 4(i 是传输数据中的十进制的索引下标)
转换后的数据 d = original  ^ mask[j]
也就是将Payload原始数据的每个字符的顺序下标与4去摸,然后将此原始数据字符与掩码的前面去摸后的相应位置的字符进行异或操作即可。这个算法对于加密和解密的操作都是一样的。

三、消息分片

分片目的是发送长度未知的消息。如果不分片发送,即一帧,就需要缓存整个消息,计算其长度,构建frame并发送;使用分片的话,可使用一个大小合适的buffer,用消息内容填充buffer,填满即发送出去。

分片规则:

1.一个未分片的消息只有一帧(FIN为1,opcode非0)

2.一个分片的消息由起始帧(FIN为0,opcode非0),若干(0个或多个)帧(FIN为0,opcode为0),结束帧(FIN为1,opcode为0)。

3.控制帧可以出现在分片消息中间,但控制帧本身不允许分片。

4.分片消息必须按次序逐帧发送。

5.如果未协商扩展的情况下,两个分片消息的帧之间不允许交错。

6.能够处理存在于分片消息帧之间的控制帧

7.发送端为非控制消息构建长度任意的分片

8.client和server兼容接收分片消息与非分片消息

9.控制帧不允许分片,中间媒介不允许改变分片结构(即为控制帧分片)

10.如果使用保留位,中间媒介不知道其值表示的含义,那么中间媒介不允许改变消息的分片结构

11.如果协商扩展,中间媒介不知道,那么中间媒介不允许改变消息的分片结构,同样地,如果中间媒介不了解一个连接的握手信息,也不允许改变该连接的消息的分片结构

12.由于上述规则,一个消息的所有分片是同一数据类型(由第一个分片的opcode定义)的数据。因为控制帧不允许分片,所以一个消息的所有分片的数据类型是文本、二进制、opcode保留类型中的一种。

需要注意的是,如果控制帧不允许夹杂在一个消息的分片之间,延迟会较大,比如说当前正在传输一个较大的消息,此时的ping必须等待消息传输完成,才能发送出去,会导致较大的延迟。为了避免类似问题,需要允许控制帧夹杂在消息分片之间。

数据帧示例:

未掩码处理的文本单数据帧:  0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello")

掩码处理的文本单数据帧:      0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58

分片未掩码处理的文本消息:   0x01 0x03 0x48 0x65 0x6c (contains "Hel")

0x80 0x02 0x6c 0x6f (contains "lo")

未掩码处理的Ping请求和掩码处理的响应:

0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains a body of "Hello", but the contents of the body are arbitrary)

0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains a body of "Hello", matching the body of the ping)

64K的二进制数据:0x82 0x7F 0x0000000000010000 [65536 bytes of binary data]

四、发送和接收数据

1、发送

  • 端点必须确保WebSocket连接处于OPEN状态。如果在任何时刻WebSocket连接的状态改变了,端点必须终止以下步骤。
  • 端点必须封装/data/到一个WebSocket帧。如果要发送的数据太大或如果在端点想要开始发生数据时数据作为一个整体不可用,端点可以交替地封装数据到一系列的帧中。
  • 第一个包含数据的帧的操作码(帧-opcode)必须设置为适当的值用于接收者解释数据是文本还是二进制数据。
  • 包含数据的最后帧的FIN位(帧-fin)必须设置位1。
  • 如果数据正由客户端发送,帧被掩码。
  • 如果任何扩展已经协商用于WebSocket连接,额外的考虑可以按照这些扩展定义来应用。
  • 已成形的帧必须在底层网络连接之上传输。

2、接收

为了接收WebSocket数据,端点监听底层网络连接。传入数据必须解析为WebSocket帧。当接收到一个数据帧时,端点必须注意由操作码(帧-opcode)定义的数据的/type/。这个帧的“应用数据”被定义为消息的/data/。如果帧由一个未分片的消息组成,这是说已经接收到一个WebSocket消息,其类型为/type/且数据为/data/。如果帧是一个分片消息的一部分,随后数据帧的“应用数据”连接在一起形成/data/。当接收到由FIN位(帧-fin)指示的最后的片段时,这是说已经接收到一个WebSocket消息,其数据为/data/(由连续片段的“应用数据”组成)且类型为/type/(分配消息的第一个帧指出)。随后的数据帧必须被解释为属于一个新的WebSocket消息。

扩展可以改变数据如何读的语义,尤其包括什么组成一个消息的边界。扩展,除了在负载中的“应用数据”之前添加“扩展数据”外,也可以修改“应用数据”(例如压缩它)。服务器必须为从客户端接收到的数据帧移除掩码。

五、Websocket关闭

通信的两端中任意一端关闭都可以关闭socket连接,关闭时应该清楚所有的TCP连接资源和TLS回话的资源,同时要丢弃所有的可能接收的字节数据。首先关闭的一方一般都应该是服务器端,然后处于TIME_WAIT状态。

为了使用一个状态码关闭websocket,一端必须发送一个关闭的控制帧,当两端都发送了关闭数据帧时,双方都要关闭所有的连接资源。控制帧为一个“状态码”和一个“原因说明”,当关闭之后,双方处于CLOSED状态。

Websocket协议数据帧传输和关闭连接的更多相关文章

  1. Websocket协议之php实现

    前面学习了HTML5中websocket的握手协议.打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信.在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问 ...

  2. webSocket协议与Socket的区别

    WebSocket介绍与原理WebSocket protocol 是HTML5一种新的协议.它实现了浏览器与服务器全双工通信(full-duplex).一开始的握手需要借助HTTP请求完成. ——百度 ...

  3. WebSocket协议-基础篇

    本篇文章主要讲述以下几点: WebSocket协议出现的背景 WebSocket与HTTP WebSocket API WebSocket协议出现的背景 我们在上网过程中经常用到的是HTTP和HTTP ...

  4. 阿里云负载不支持 WebSocket 协议与 WSS 和 Nginx 配置问题

    WebSocket 是 HTML5 下一种新的协议.它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的.它与HTTP一样通过已建立的TCP连接来传输数据,但是它和HTT ...

  5. WebSocket协议-原理篇

    本篇文章主要讲述以下几点: WebSocket的原理与机制 WebSocket与Socket.io WebSocket兼容性 WebSocket的原理与机制 WebSocket协议分为两部分:握手和数 ...

  6. Websocket协议之握手连接

    Websocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信的问题而设计的,是完全意义上的Web应用端的双向通信技术,可以取代之前使用半双工HTTP协议而模拟全双工通信,同时克服了带 ...

  7. 从websocket协议出发,了解应用层协议,传输层协议,网络的7层协议

    其他关联连接 :TCP的三次握手(建立连接)和四次挥手(关闭连接) 1.websocket是全双工,不同于传统半双工通信 传统的Web应用中,浏览器与服务器交互都是半双工通信(但并不完全是半双工通信, ...

  8. HTTP协议学习笔记---HTTP持久连接和如何正确地关闭HTTP连接

    一,持久连接 什么是持久连接?对于HTTP协议而言,它是基于请求响应模型,Client向Server发请求时,先建立一条HTTP连接,Server给Client响应数据后,连接关闭. 当Client发 ...

  9. 为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?

    看到了一道面试题:"为什么TCP建立连接协议是三次握手,而关闭连接却是四次握手呢?为什么不能用两次握手进行连接?",想想最近也到金三银四了,所以就查阅了相关资料,整理出来了这篇文章 ...

随机推荐

  1. ios开发——笔记篇

    :开关 BOOL isopen = !isopen; //View @property (nonatomic, assign) BOOL open;//模型属性 self.group.open = ! ...

  2. android报错及解决2--Sdcard进行文件的读写操作报的异常

    报错描述: 对Sdcard进行文件的读写操作的时候,报java.io.FileNotFoundException: /sdcard/testsd.txt (Permission denied),在往S ...

  3. python 源码解析

    http://blog.donews.com/lemur/archive/category/cpython%E6%BA%90%E7%A0%81%E5%89%96%E6%9E%90/

  4. swfupload操作手册

    SWFUpload SWFUpload 最初是Vinterwebb.se 开发的客户端文件上传工具.它联合javascript和flash,在浏览器中提供一个优于传统上传标签 <input ty ...

  5. Maven项目中如何添加日志

  6. 基础:c++中引用与java中的引用

    using namespace std; class Point { public: double x; double y; Point(){} void setPoint(double x,doub ...

  7. Android_Intent_passValue(4)

    xml布局文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns ...

  8. jquery循环遍历radio单选按钮,并设置选中状态

    背景:自己在做项目过程中遇到的问题,现在记录一下. 需求:在ajax获取后台数据的之后,需要根据获取的数据对页面中的radio单选按钮进行选中状态设置 因为自身js功底欠佳,所以耽误了点时间,现在把方 ...

  9. nginx笔记----安装

    nginx的安装 ./configure make && make install (一)准备条件: 1.GCC---gun编译器集合 Nginx是一个由C语言编写的程序,因此首先需要 ...

  10. javascript开发中的封装模式(转)

    var bgAuido={ audio : pingfan.$$('audio'), audioBtn : pingfan.$$('audioBtn'), init : function(){ var ...