WebSocket 协议介绍及 WebSocket API 应用

WebSocket 是由HTML5提出的一个独立的协议标准。WebSocket可以分为协议(Protocol)和API两部分,分别由IETF和W3C制定了标准。它跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而对 HTTP 协议的一种补充。更加确切的说 WebSocket 利用了 HTTP 协议来建立连接,仅此而已。

WebSocket 协议介绍

现在 WebSocket 协议已经成了标准协议,所有主流浏览器都已经很好的支持其基础功能。

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

WebSocket是一种双向通信协议。在建立连接后,WebSocket服务器端和客户端都能主动向对方发送或接收数据,就像Socket一样;

WebSocket需要像TCP一样,先建立连接,连接成功后才能相互通信。

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

WebSocket 协议的建立过

接下来我们来了解一下 WebSocket 协议的建立过程:

WebSocket 连接必须由浏览器发起,请求协议是一个标准的HTTP请求(也就是说,WebSocket的建立是依赖HTTP的)。请求报文格式如下:

该请求和普通的HTTP请求有几点不同:

其中 HTTP 头部字段 Upgrade: websocket 和 Connection: Upgrade 很重要,告诉服务器通信协议将发生改变,转为 WebSocket 协议。

Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;

Sec-WebSocket-Version指定了WebSocket的协议版本。

支持 WebSocket 的服务器端在确认以上请求后,应返回状态码为101 Switching Protocols的响应:

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用 WebSocket 的 API ,就不需要关心这些。

现在,一个 WebSocket 连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

为什么 WebSocket 连接可以实现全双工通信而 HTTP 连接不行呢?实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP 协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,接下来的通信就不使用 HTTP 协议了,直接互相发数据。

安全的 WebSocket 连接机制和 HTTPS 类似。首先,浏览器用 wss://xxx 创建 WebSocket 连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS 连接升级为 WebSocket 连接,底层通信走的仍然是安全的 SSL/TLS 协议。

WebSocket 、Ajax 轮询 和 Long Poll(长轮询) 原理解析

说到 WebSocket ,那就不得不说说 Ajax 轮询 和 Long Poll(长轮询)。

Ajax 轮询 和 Long Poll(长轮询) 都是 HTTP 请求的应用,都属于非持久连接。

首先来说说 Ajax 轮询。Ajax 轮询的原理非常简单,让浏览器每隔一定的时间就发送一次请求,询问服务器是否有新信息。

Long Poll(长轮询) 其实原理跟 Ajax 轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型,也就是说,客户端发起连接后,如果没消息,服务器不会马上告诉你没消息,而是将这个请求挂起(pending),直到有消息才返回。返回完成或者客户端主动断开后,客户端再次建立连接,周而复始。我们可以看出Long Poll(长轮询) 已经具备了一定的实时性。

上面这两种应用都是非常消耗资源。Ajax 轮询需要服务器有很快的处理速度和资源。Long Poll(长轮询) 需要有很高的并发,也就是说同时连接数的能力。同时也受到客户端的连接数限制,比如老早的IE6,客户端同事连接数为2。尽管如此,在过去 Ajax 轮询 和 Long Poll(长轮询) 还是有广泛的应用,特别是实时聊天,短消息推送等方面, Long Poll(长轮询) 是除了 Flash 之外唯一的选择。

相对于 HTTP 连接的非持久连接来说,WebSocket 则是非持久连接。

上面已经说了 WebSocket 是类似 Socket 的TCP长连接通讯模式。一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。而且浏览器和服务器就可以随时主动发送消息给对方,是全双工通信。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。

WebSocket API

WebSocket 客户端的 API 和流程非常简单:创建 WebSocket 对象,然后指定 open、message等事件的回调即可。其中 message 是客户端与服务器端通过WebSocket通信的关键事件,想要在收到服务器通知后做点什么,写在message事件的回调函数里就好了:

WebSocket 构造函数

WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。

执行上面语句之后,客户端就会与服务器进行连接。实例对象的所有属性和方法参见 https://developer.mozilla.org/en-US/docs/Web/API/WebSocket 。

webSocket.readyState

属性返回实例对象的当前状态,共有四种。

:值为 ,表示正在连接。

:值为 ,表示连接成功,可以通信了。

:值为 ,表示连接正在关闭。

:值为 ,表示连接已经关闭,或者打开连接失败。

webSocket.onopen

实例对象的 属性,用于指定连接成功后的回调函数。

如果要指定多个回调函数,可以使用addEventListener方法。

webSocket.onclose

实例对象的 属性,用于指定连接关闭后的回调函数。

webSocket.onmessage

实例对象的 属性,用于指定收到服务器数据后的回调函数。

注意,服务器数据可能是文本,也可能是二进制数据( 对象或 对象)。

除了动态判断收到的数据类型,也可以使用 属性,显式指定收到的二进制数据类型。

webSocket.send()

实例对象的send()方法用于向服务器发送数据。

发送文本的例子。

发送 Blob 对象的例子。

发送 ArrayBuffer 对象的例子。

webSocket.bufferedAmount

实例对象的 属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

webSocket.onerror

实例对象的onerror属性,用于指定报错时的回调函数。

简单的示例

这里我们介绍一下实际应用的简单示例

服务器端

以Node的服务器为例,我们使用ws这个组件,这样搭建一个支持WebSocket的服务器端:

这个例子使用了姓名生成站点 uinames 的API服务,来生成 这样的数据。函数 会不定时执行,并把包含姓名和时间的信息通过 方法发送给客户端。另外,注意 方法需要以字符串形式来发送json数据。

这就像是服务器自己在做一些事,然后在需要的时候会通知客户端一些信息。

客户端

客户端我们使用原生javascript来完成(仅支持WebSocket的浏览器):

WebSocket的URL格式是 与 。因此,需要注意下URL地址的写法,这也包括注意WebSocket服务器端的路径(如这里的 )等信息。因为是本地的示例所以这里是localhost。

效果及分析

通过 (假定服务器端的文件名为 )启动WebSocket服务器后,用浏览器打开一个引入了前面客户端代码的html(直接文件路径 就可以),就可以得到像这样的结果:

联系前面客户端的代码可以想到,实际从创建WebSocket对象的语句开始,连接请求就会发送,并很快建立起WebSocket连接(不出错误的话),此后就可以收到来自服务器端的通知。如果此时客户端还想再告诉服务器点什么,这样做:

socket.send("Hello, server!");

服务器就可以收到:

当然,这也是因为前面服务器端的代码内同样设置了 事件的回调。在这个客户端和服务器都是javascript的例子中,无论是服务器端还是客户端,都用 发送信息,都通过 事件设置回调,形式上可以说非常一致。

其他可用的数据类型

WebSocket的 可以发送的消息,除了前面用的字符串类型之外,还有两种可用,它们是 Blob https://developer.mozilla.org/zh-CN/docs/Web/API/Blob 和 ArrayBuffer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer。

它们都代表二进制数据,可用于原始文件数据的发送。比如,这是一个发送Blob类型数据以完成向服务器上传图片的例子:

在客户端接收二进制数据时,需注意WebSocket对象有一个属性 ,初始值为 。因此,如果接收的二进制数据是 ,应在接收之前这样做:

其他WebSocket服务器端

其他语言来做WebSocket服务器是怎样的呢?下面是一个php的WebSocket服务器的例子(使用Ratchet http://socketo.me/):

这个例子也同样是由服务器返回 的json数据,不过由于php不像Node那样可以用 很容易地实现异步定时任务,这里改为在客户端发送一次任意信息后,再去uinames取得信息并返回。

也可以看到,php搭建的WebSocket服务器仍然是近似的,主要通过WebSocket的 、 等事件来实现功能。

在Chrome开发工具中查看WebSocket数据帧

Chrome开发工具中选择 ,然后找到WebSocket的那个请求,里面可以选择 。在 里看到的,就是WebSocket的数据帧了:

可以看到很像聊天记录,其中用浅绿色标注的是由客户端发送给服务器的部分。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181120G0AHET00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券