WebSocket

WebSocket协议还很年轻,RFC文档相比HTTP的发布时间也很短,它的诞生是为了创建一种「双向通信」的协议,来作为HTTP协议的一个替代者。那么首先看一下它和HTTP(或者HTTP的长连接)的区别。

为什么要用 WebSocket 来替代 HTTP

上一篇中提到WebSocket的目的就是解决网络传输中的双向通信的问题,HTTP1.1默认使用持久连接(persistent connection),在一个TCP连接上也可以传输多个Request/Response消息对,但是HTTP的基本模型还是一个Request对应一个Response。这在双向通信(客户端要向服务器传送数据,同时服务器也需要实时的向客户端传送信息,一个聊天系统就是典型的双向通信)时一般会使用这样几种解决方案:

  1. 轮询(polling),轮询就会造成对网络和通信双方的资源的浪费,且非实时。
  2. 长轮询,客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response,相比#1,占用的网络带宽少了,其他类似。
  3. 长连接,其实有些人对长连接的概念是模糊不清的,我这里讲的其实是HTTP的长连接(1)。如果你使用Socket来建立TCP的长连接(2),那么,这个长连接(2)跟我们这里要讨论的WebSocket是一样的,实际上TCP长连接就是WebSocket的基础,但是如果是HTTP的长连接,本质上还是Request/Response消息对,仍然会造成资源的浪费、实时性不强等问题。

HTTP的长连接模型

协议基础

WebSocket的目的是取代HTTP在双向通信场景下的使用,而且它的实现方式有些也是基于HTTP的(WS的默认端口是80和443)。现有的网络环境(客户端、服务器、网络中间人、代理等)对HTTP都有很好的支持,所以这样做可以充分利用现有的HTTP的基础设施,有点向下兼容的意味。

简单来讲,WS协议有两部分组成:握手和数据传输。

握手(handshake)

出于兼容性的考虑,WS的握手使用HTTP来实现(此文档中提到未来有可能会使用专用的端口和方法来实现握手),客户端的握手消息就是一个「普通的,带有Upgrade头的,HTTP Request消息」。所以这一个小节到内容大部分都来自于RFC2616,这里只是它的一种应用形式,下面是RFC6455文档中给出的一个客户端握手消息示例:

 
GET /chat HTTP/1.1 //1
Host: server.example.com //2
Upgrade: websocket //3
Connection: Upgrade //4
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== //5
Origin: http://example.com //6
Sec-WebSocket-Protocol: chat, superchat //7
Sec-WebSocket-Version: 13 //8
1
2
3
4
5
6
7
8
GET /chat HTTP/1.1            //1
    Host: server.example.com   //2
    Upgrade: websocket            //3
    Connection: Upgrade            //4
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==            //5
    Origin: http://example.com            //6
    Sec-WebSocket-Protocol: chat, superchat            //7
    Sec-WebSocket-Version: 13            //8

可以看到,前两行跟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-*:第7行标识了客户端支持的子协议的列表(关于子协议会在下面介绍),第8行标识了客户端支持的WS协议的版本列表,第5行用来发送给服务器使用(服务器会使用此字段组装成另一个key值放在握手返回信息里发送客户端)。

Origin:作安全使用,防止跨站攻击,浏览器一般会使用这个来标识原始域。

如果服务器接受了这个请求,可能会发送如下这样的返回信息,这是一个标准的HTTP的Response消息。101表示服务器收到了客户端切换协议的请求,并且同意切换到此协议。RFC2616规定只有切换到的协议「比HTTP1.1更好」的时候才能同意切换。

 
HTTP/1.1 101 Switching Protocols //1
Upgrade: websocket. //2
Connection: Upgrade. //3
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= //4
Sec-WebSocket-Protocol: chat. //5
1
2
3
4
5
HTTP/1.1 101 Switching Protocols //1
    Upgrade: websocket. //2
    Connection: Upgrade. //3
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  //4
    Sec-WebSocket-Protocol: chat. //5

WebSocket 协议 Uri

ws协议默认使用80端口,wss协议默认使用443端口。

 
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

host = <host, defined in [RFC3986], Section 3.2.2>
port = <port, defined in [RFC3986], Section 3.2.3>
path = <path-abempty, defined in [RFC3986], Section 3.3>
query = <query, defined in [RFC3986], Section 3.4>

1
2
3
4
5
6
7
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
      wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
 
      host = <host, defined in [RFC3986], Section 3.2.2>
      port = <port, defined in [RFC3986], Section 3.2.3>
      path = <path-abempty, defined in [RFC3986], Section 3.3>
      query = <query, defined in [RFC3986], Section 3.4>

在客户端发送握手之前要做的一些小事

在握手之前,客户端首先要先建立连接,一个客户端对于一个相同的目标地址(通常是域名或者IP地址,不是资源地址)同一时刻只能有一个处于CONNECTING状态(就是正在建立连接)的连接。从建立连接到发送握手消息这个过程大致是这样的:

  1. 客户端检查输入的Uri是否合法。
  2. 客户端判断,如果当前已有指向此目标地址(IP地址)的连接(A)仍处于CONNECTING状态,需要等待这个连接(A)建立成功,或者建立失败之后才能继续建立新的连接。
    PS:如果当前连接是处于代理的网络环境中,无法判断IP地址是否相同,则认为每一个Host地址为一个单独的目标地址,同时客户端应当限制同时处于CONNECTING状态的连接数。
    PPS:这样可以防止一部分的DDOS攻击。
    PPPS:客户端并不限制同时处于「已成功」状态的连接数,但是如果一个客户端「持有大量已成功状态的连接的」,服务器或许会拒绝此客户端请求的新连接。
  3. 如果客户端处于一个代理环境中,它首先要请求它的代理来建立一个到达目标地址的TCP连接。
    例如,如果客户端处于代理环境中,它想要连接某目标地址的80端口,它可能要收现发送以下消息:
 
CONNECT example.com:80 HTTP/1.1
Host: example.com
1
2
CONNECT example.com:80 HTTP/1.1
       Host: example.com

如果客户端没有处于代理环境中,它就要首先建立一个到达目标地址的直接的TCP连接。

4.如果上一步中的TCP连接建立失败,则此WebSocket连接失败。
5.如果协议是wss,则在上一步建立的TCP连接之上,使用TSL发送握手信息。如果失败,则此WebSocket连接失败;如果成功,则以后的所有数据都要通过此TSL通道进行发送。

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

  1. 握手必须是RFC2616中定义的Request消息
  2. 此Request消息的方法必须是GET,HTTP版本必须大于1.1 。
    以下是某WS的Uri对应的Request消息:
 
ws://example.com/chat
GET /chat HTTP/1.1
1
2
ws://example.com/chat
GET /chat HTTP/1.1

3.此Request消息中Request-URI部分(RFC2616中的概念)所定义的资型必须和WS协议的Uri中定义的资源相同。
4.此Request消息中必须含有Host头域,其内容必须和WS的Uri中定义的相同。
5.此Request消息必须包含Upgrade头域,其内容必须包含websocket关键字。
6.此Request消息必须包含Connection头域,其内容必须包含Upgrade指令。
7.此Request消息必须包含Sec-WebSocket-Key头域,其内容是一个Base64编码的16位随机字符。
8.如果客户端是浏览器,此Request消息必须包含Origin头域,其内容是参考RFC6454。
9.此Request消息必须包含Sec-WebSocket-Version头域,在此协议中定义的版本号是13。
10.此Request消息可能包含Sec-WebSocket-Protocol头域,其意义如上文中所述。
11.此Request消息可能包含Sec-WebSocket-Extensions头域,客户端和服务器可以使用此header来进行一些功能的扩展。
12.此Request消息可能包含任何合法的头域。如RFC2616中定义的那些。

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

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

服务端的概念

服务端指的是所有参与处理WebSocket消息的基础设施,比如如果某服务器使用Nginx(A)来处理WebSocket,然后把处理后的消息传给响应的服务器(B),那么A和B都是这里要讨论的服务端的范畴。

接受了客户端的连接请求,服务端要做的一些事情

如果请求是HTTPS,则首先要使用TLS进行握手,如果失败,则关闭连接,如果成功,则之后的数据都通过此通道进行发送。

之后服务端可以进行一些客户端验证步骤(包括对客户端header域的验证),如果需要,则按照RFC2616来进行错误码的返回。

如果一切都成功,则返回成功的Response握手消息。

服务端发送的成功的 Response 握手

此握手消息是一个标准的HTTP Response消息,同时它包含了以下几个部分:

  1. 状态行(如上一篇RFC2616中所述)
  2. Upgrade头域,内容为websocket
  3. Connection头域,内容为Upgrade
  4. Sec-WebSocket-Accept头域,其内容的生成步骤:
    a.首先将Sec-WebSocket-Key的内容加上字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11(一个UUID)。
    b.将#1中生成的字符串进行SHA1编码。
    c.将#2中生成的字符串进行Base64编码。
  5. Sec-WebSocket-Protocol头域(可选)
  6. Sec-WebSocket-Extensions头域(可选)

一旦这个握手发出去,服务端就认为此WebSocket连接已经建立成功,处于OPEN状态。它就可以开始发送数据了。

WebSocket 的一些扩展

Sec-WebSocket-Version可以被通信双方用来支持更多的协议的扩展,RFC6455中定义的值为13,WebSocket的客户端和服务端可能回自定义更多的版本号来支持更多的功能。其使用方法如上文所述。

发送数据

WebSocket中所有发送的数据使用帧的形式发送。客户端发送的数据帧都要经过掩码处理,服务端发送的所有数据帧都不能经过掩码处理。否则对方需要发送关闭帧。

一个帧包含一个帧类型的标识码,一个负载长度,和负载。负载包括扩展内容和应用内容。

帧类型

帧类型是由一个4位长的叫Opcode的值表示,任何WebSocket的通信方收到一个位置的帧类型,都要以连接失败的方式断开此连接。
RFC6455中定义的帧类型如下所示:

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 未来使用(控制帧)

帧的格式

具体的每一项代表什么意思在这里就不做详细的阐述了。

 
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 ... |
+---------------------------------------------------------------+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 ...                |
+---------------------------------------------------------------+

与HTTP比较

同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方,这里将它们简单的做一个纯个人、非权威的比较:

相同点

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

不同点

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

HTTP协议和WebSocket协议(二)的更多相关文章

  1. HTTP协议和WebSocket协议(一)

    转自:https://www.jianshu.com/p/0e5b946880b4# HTTP HTTP的地址格式如下: http_URL = "http:" "//&q ...

  2. HTTP协议和SOCKS5协议

    HTTP协议和SOCKS5协议 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 我们平时上网的时候基本上是离不开浏览器的,尤其是搜索资料的时候,那么这个浏览器是如何工作的呢?用的又是 ...

  3. python 全栈开发,Day33(tcp协议和udp协议,互联网协议与osi模型,socket概念,套接字(socket)初使用)

    先来回顾一下昨天的内容 网络编程开发架构 B/S C/S架构网卡 mac地址网段 ip地址 : 表示了一台电脑在网络中的位置 子网掩码 : ip和子网掩码按位与得到网段 网关ip : 内置在路由器中的 ...

  4. http协议及http协议和tcp协议的区别

    http是应用层的协议,并且无连接,无状态的协议. http协议的特点: 1.支持c/s模式 2.简单快速:客户端向服务器端传送数据的时候,只需要发送请求方法和路径,请求方法有:post,get,he ...

  5. Java基础之UDP协议和TCP协议简介及简单案例的实现

    写在前面的废话:马上要找工作了,做了一年的.net ,到要找工作了发现没几个大公司招聘.net工程师,真是坑爹呀.哎,java就java吧,咱从头开始学呗,啥也不说了,玩命撸吧,我真可怜啊. 摘要: ...

  6. 网络协议学习笔记(八)DNS协议和HttpDNS协议

    概述 上一篇主要讲解了流媒体协议和p2p协议,现在我给大家讲解一下关于DNS和HttpDNS的相关知识. DNS协议:网络世界的地址簿 在网络世界,也是这样的.你肯定记得住网站的名称,但是很难记住网站 ...

  7. 网络协议学习笔记(七)流媒体协议和P2P协议

    概述 上一篇讲解了http和https的协议的相关的知识,现在我们谈一下流媒体协议和P2P协议. 流媒体协议:如何在直播里看到美女帅哥 最近直播比较火,很多人都喜欢看直播,那一个直播系统里面都有哪些组 ...

  8. Bytom BIP-32协议和BIP-44协议

    我们知道HD(分层确定性)钱包,基于 BIP-32:多币种和多帐户钱包,基于 BIP-44:最近比原社区的钱包开发者对比原的BIP-32和BIP-44协议有疑问,所以我今天就专门整理了一下该协议的内容 ...

  9. TCP协议和UDP协议基础介绍

    TCP协议和UDP协议区别 标签(空格分隔): TCP,udp TCP的三次握手 TCP被称为可靠的数据传输协议,主要是通过许多机制来实现的其中最主要的就是三次握手的功能,当然,TCP传送数据的机制非 ...

随机推荐

  1. CSS3图片以中心缩放,放大超出隐藏实现

    首页,重点是有一个包裹img标签的容器,这里我们给该容器设置一个class为selfScale <div class="selfScale"> <img sr=& ...

  2. CSS——盒子模型

    一.盒子模型: 模型如下: 如图:盒子模型包括:margin.padding.border.content四部分. margin:外边距,透明,能够显示父级的背景颜色等.表示元素与元素之间的间隔或者说 ...

  3. CentOS6.5安装完没有网络的解决办法

    昨天下了个CentOS 6.5 Minimal 版,在VMware 10下安装好了之后,发现上不了网,PING外网也PING不通. 在网上搜了一下,发现Linux安装好了之后,网卡默认是没有启动的,下 ...

  4. mysql使用存储过程插入数据后,参数为中文的为?或乱码

    最近了解了一下mysql存储过程,之前版本的mysql不支持存储过程,5.0版本后就可以支持存储过程的使用:恰好笔者下载使用版本为5.6.20: 做了一个给表插入数据的简单存储过程,发现打开表后汉字全 ...

  5. Android编译系统产品线

    1.Android源码中的产品线解析 通常产品厂商在拿到Android源码后会在Android源码基础上进行定制修改,以匹配适应自己的产品.这就引入了产品线的概念.Android系统源码中,产品相关的 ...

  6. 我积累的Java实用代码

    1.解压zip文件 /** * 解压输入的zip流,Java默认的解压只能处理UTF-8编码的文件或者目录名,否则会报MALFORMED异常 * * @param is 输入流 * @param ou ...

  7. [bzoj2460] [BeiJing2011]元素(线性基+贪心)

    题目大意: 有一些矿石,每个矿石有一个a和一个b值,要求选出一些矿石,b的和最大且不存在某个矿石子集它们的a的异或和为0. 解题关键:对魔力进行由大到小排序,依次加入线性基,统计即可. #includ ...

  8. Ajax02 什么是json、json语法、json的使用、利用jQuery实现ajax

    目录 1什么是json 2json语法 3json的使用 4利用jQuery实现ajax编程 1 什么是json JavaScript Object Notation(JavaScript 对象表示法 ...

  9. 【转】PEAR安装、管理及使用

    PEAR安装   linux下只要你安装的是PHP 4.3.0以上的版本,默认安装都是支持PEAR的,除非你使用了”--WITHOUT-PEAR”选项,修改PHP.INI文件,在INCLUDE_PAT ...

  10. ZROI2018普转提day7t1

    传送门 分析 一道有意思的小题... 我们发现如果$(1,1)$为白色,则将其变为白色需要偶数次操作,而如果为黑色则需要奇数次操作 我们知道要让A赢需要奇数次操作,所以我们只需要判断$(1,1)$的颜 ...