从零天梯:一探WebSocket协议

写在前面

一次偶然的机会,我被派往北京出差,去支援在一线浴血奋战的战友。项目涉及WebSocket,用来实现WEB浏览器和打印驱动之间的双向通信。开始编码之前,我收集了有关WebSocket的资料,甚至去阅读了相关的RFC文档。阅读资料的同时,我整理了相关笔记,希望给阅读者带来一些帮助~好啦,让我们开始阅读正文吧~

1. WebSocket几问

如果你之前对WebSocket协议有些了解,那么可以尝试回答以下问题:

  1. WebSocket是什么东西?
  2. 数据传输时,WebSocket是否真的实时?
  3. WebSocket底层本质是什么?轮询?
  4. 它和HTTP长连接有什么不同?
  5. 它的传输过程是什么样子的呢?

如果你才刚接触WebSocket协议,不妨可以先通读全文~

2. WebSocket前世今生

2.1 WebSocket是什么?

RFC6455对WebSocket有如下定义:

The WebSocket Protocol enables two-way communication between a client
running untrusted code in a controlled environment to a remote host
that has opted-in to communications from that code.  

又如百度百科上的介绍,我们就有了对WebSocket的第一印象:

  1. WebSocket protocol 是HTML5一种新的协议。(是什么
  2. 它实现了浏览器与服务器全双工通信 (full-duplex)(功能
  3. 可以让服务器可以主动传送数据给客户端(优点
  4. 利用HTTP协议来完成一部分握手(与HTTP协议关系)

2.3 WebSocket背景

由于历史原因,在创建一个具有双向通信机制的 web 应用程序时,需要利用到 HTTP 轮询的方式。围绕轮询产生了 “短轮询” 和 “长轮询”。

  • 轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
  • 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。

用AJAX的long-polling来模拟WebSocket在业界已经有一段时间了。它是一个可行的技术,但它不能优化发送的信息。也就是说,它是一个解决方案,但不是最佳的技术方案。

如果您这部分感兴趣,可以移步我整理的另一篇文章->web实时通信与跨文档通信

3. WebSocket机制

以下简要介绍一下 WebSocket 的原理及运行机制。

WebSocket 是 HTML5 一种新的协议。它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大不同是:

  • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/Client Agent 都能主动的向对方发送或接收数据,就像 Socket 一样;

  • WebSocket 需要类似 TCP 的客户端和服务器端通过握手连接,连接成功后才能相互通信。

非 WebSocket 模式传统 HTTP 客户端与服务器的交互如下图所示:

图 1. 传统 HTTP 请求响应客户端服务器交互图
传统 HTTP 请求响应客户端服务器交互图

使用 WebSocket 模式客户端与服务器的交互如下图:

图 2.WebSocket 请求响应客户端服务器交互图
WebSocket 请求响应客户端服务器交互图

上图对比可以看出,相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

我们再通过客户端和服务端交互的报文看一下 WebSocket 通讯与传统 HTTP 的不同:

在客户端,new WebSocket 实例化一个新的 WebSocket 客户端对象,连接类似 ws://yourdomain:port/path 的服务端 WebSocket URL,WebSocket 客户端对象会自动解析并识别为 WebSocket 请求,从而连接服务端端口,执行双方握手过程,客户端发送数据格式类似:

清单 1.WebSocket 客户端连接报文

1
2
3
4
5
6
7
GET /webfin/websocket/ HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://localhost:8080
Sec-WebSocket-Version: 13

可以看到,客户端发起的 WebSocket 连接报文类似传统 HTTP 报文,”Upgrade:websocket”参数值表明这是 WebSocket 类型请求,“Sec-WebSocket-Key”是 WebSocket 客户端发送的一个 base64 编码的密文,要求服务端必须返回一个对应加密的“Sec-WebSocket-Accept”应答,否则客户端会抛出“Error during WebSocket handshake”错误,并关闭连接。

服务端收到报文后返回的数据格式类似:

清单 2.WebSocket 服务端响应报文

1
2
3
4
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

“Sec-WebSocket-Accept”的值是服务端采用与客户端一致的密钥计算出来后返回客户端的,“HTTP/1.1 101 Switching Protocols”表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。读者可以查阅WebSocket 协议栈了解 WebSocket 客户端和服务端更详细的交互数据格式。

在开发方面,WebSocket API 也十分简单,我们只需要实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息,在下文 WebSocket 实现及案例分析部分,可以看到详细的 WebSocket API 及代码实现。

4. WebSocket实战

只专注于客户端的API,因为每个服务器端语言有自己的API。下面的代码片段是打开一个连接,为连接创建事件监听器,断开连接,消息时间,发送消息返回到服务器,关闭连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 创建一个Socket实例
var socket = new WebSocket('ws://localhost:8080');

// 打开Socket
socket.onopen = function(event) {

// 发送一个初始化消息
socket.send('I am the client and I\'m listening!');

// 监听消息
socket.onmessage = function(event) {
console.log('Client received a message',event);
};

// 监听Socket的关闭
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};

// 关闭Socket....
//socket.close()
};

让我们来看看上面的初始化片段。参数为URL,ws表示WebSocket协议。onopen、onclose和onmessage方法把事件连接到Socket实例上。每个方法都提供了一个事件,以表示Socket的状态。

onmessage事件提供了一个data属性,它可以包含消息的Body部分。消息的Body部分必须是一个字符串,可以进行序列化/反序列化操作,以便传递更多的数据。

5. 参考文档

认识HTML5的WebSocket
维基:WebSocket
RFC6455
WebSocket 协议 1~4 节
WebSocket 协议 5~10 节
基于WEB的实时通信方案
刨根问底HTTP和WebSocket协议
理解WebKit和Chromium: Chromium网络栈
WebSocket 实战
知乎:长连接与长轮询分别如何实现的?各有哪些优势和劣势?
知乎:WebSocket 是什么原理?为什么可以实现持久连接?