浅谈WebSocket协议

计算机 2019-01-04

有关WebSocket协议详见 IETF 互联网工程任务组于2011年12月发表 RFC-6455标准规范

什么是WebSocket协议

官方抽象概念: WebSocket协议实现在受控环境中运行不受信任代码的一个客户端到一个从该代码已经选择加入通信的远程主机之间的全双工通信。用于这个的安全模型是通常由web浏览器使用的基于来源的安全模型。该协议包括一个打开阶段握手、接着是基本消息帧、TCP之上的分层(layered over TCP)。该技术的目标是为需要与服务器全双工通信且不需要依赖打开多个HTTP连接(例如,使用XMLHttpRequest或和长轮询)的基于浏览器应用的提供一种机制。

个人理解: HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端就是HTTP 协议无法实现服务器主动向客户端发起消息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。大多数 Web 应用程序将通过频繁的异步JavaScript和XML(AJAX)请求实现长轮询。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。WebSocket协议可以很好的解决这个场景问题。

背景

官方非规范文档化解释: 过去,创建需要在客户端和服务之间双向通信(例如,即时消息和游戏应用)的web应用, 需要一个滥用的HTTP来轮询服务器进行更新。

这将导致各种各样的问题:

o 服务器被迫为每个客户端使用一个不同的底层TCP连接: 一个用于发送信息到客户端,一个用于接收每个客户端传入的消息。

o 线路层协议有较高的开销,因为每个客户端-服务器消息都有一个HTTP头信息。

o 客户端脚本被迫维护一个传出的连接到传入的连接的映射来跟踪回复。

一个简单的办法是使用单个TCP连接双向传输。这是为什么提供WebSocket 协议。与WebSocket API结合[WSAPI],它提供了一个HTTP轮询的替代来进行从web 页面到远程服务器的双向通信。

同样的技术可以用于各种各样的web应用:

游戏、股票行情、同时编辑的多用户应用、服务器端服务需要实时暴露的用户接口、等等。

WebSocket协议被设计来取代现有的使用HTTP作为传输层的双向通信技术,并受益于现有的基础设施(代理、过滤、身份验证)。这样的技术被实现来在效率和可靠性之间权衡,因为HTTP最初目的不是用于双向通信(参见[RFC6202]的进一步讨论)。WebSocket协议试图在现有的HTTP基础设施上下文中解决现有的双向HTTP技术目标;同样,它被设计工作在HTTP端口80和443,也支持HTTP代理和中间件,即使这具体到当前环境意味着一些复杂性。但是,这种设计不限制WebSocket到HTTP,且未来的实现可以在一个专用的端口上使用一个更简单的握手,且没有再创造整个协议。最后一点是很重要的,因为交互消息的传输模式不精确地匹配标准HTTP传输并可能在相同组件上包含不常见的负载。

个人理解: WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

WebSocket的URI方案

规范文档提供了两种URI的方案,他们分别是:

  1. WS-URI 默认端口80
  2. WSS-URI 默认端口443

其中如果方案组件不区分大写匹配“wss”,URI被称为“安全的”(设置了安全标记)。

WebSocket 如何来工作?

客户端发起握手(基于HTTP协议,报文包含用于实现协议升级的信息,此时连接处于connecting状态)->服务端响应握手(基于HTTP协议,返回HTTP状态码)->客户端握手(HTTP连接建立,得到key,此时连接处于open状态)->进行websocket通信(服务端需要验证客户端key的合法性)

本协议有两部分:握手和数据传输(先握手后数据传输)。

来自客户端的握手看起来像如下形式:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13

可以看见HTTP/1.1字样,表明这是基于HTTP的信息传送 如下的信息用于帮助协议升级

    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13

来自服务器的握手看起来像如下形式:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Sec-WebSocket-Protocol: chat

一旦客户端和服务器都发送了它们的握手,且如果握手成功,接着开始数据传输部分。这是一个每一端都可以的双向通信信道,彼此独立,随意发生数据传输。

一个成功握手之后,客户端和服务器来回地传输数据,在规范中提到的概念单位为“消息”。在线路上,一个消息是由一个或多个帧的组成。WebSocket的消息并不一定对应于一个特定的网络层帧,可以作为一个可以被一个中间件合并或分解的片段消息。

一个帧有一个相应的类型。属于相同消息的每一帧包含相同类型的数据。从广义上讲,有文本数据类型(它被解释为UTF-8[RFC3629]文本)、二进制数据类型(它的解释是留给应用)、和控制帧类型(它是不准备包含用于应用的数据,而是协议级的信号,例如应关闭连接的信号)。这个版本的协议定义了六个帧类型并保留10以备将来使用。

两种轮询方式

AJAX轮询 转自扶强cnblogs

ajax轮询的原理非常简单,让浏览器隔一段时间(据说是0.5s)就发送一次请求,询问服务器是否有新信息。

场景再现:

客户端:有没有新信息(Request)

服务端:没有(Response)

客户端:有没有新信息(Request)

服务端:没有(Response)

客户端:有没有新信息(Request)

服务端:没有(Response)

客户端:有没有新消息(Request)

服务端:有,给你。(Response)

客户端:有没有新消息(Request)

服务端:没有(Response) —- loop

long poll (长轮询)

long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

场景再现:

客户端:有没有新信息,没有的话就等有了才返回给我吧(Request)

服务端:等待。。。。(一段时间后)有消息后,给客户端(Response)

客户端:有没有新信息,没有的话就等有了才返回给我吧(Request) -loop

从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性,服务端不能主动联系客户端,只能有客户端发起。

从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。

ajax轮询 需要服务器有很快的处理 速度 和资源。long poll 需要有很高的 并发 ,也就是说同时处理数据的能力。

所以 ajax轮询 和 long poll 都有可能发生这种情况。

客户端:有新信息么?

服务端:请稍后再试(503 Server Unavailable)

客户端:有新信息么?

服务端:请稍后再试(503 Server Unavailable)

---loop

WebSocket操作

下面列举常见事件方法。查看所有WebSocket属性方法 Developer-Mozilla

WebSocket四事件

事件处理api描述
openSocket.onopen连接建立时触发
messageSocket.onmessage客户端接收服务端数据时触发
errorSocket.onerror通信发生错误时触发
closeSocket.onclose连接关闭时触发

WebSocket两方法

方法描述
Socket.send()使用连接发送数据
Socket.close()关闭连接

建立WebSocket连接

//实例化对象
var ws = new WebSocket("wss://echo.websocket.org");
//指定连接成功后的回调函数
ws.onopen = function(evt) {
  console.log("Connection open ...");
  ws.send("Hello WebSockets!");  //发送数据
};
//指定收到服务器数据后的回调函数
ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};
//指定连接关闭后的回调函数
ws.onclose = function(evt) {
  console.log("Connection closed.");
};

客户端的 API

推荐参考 阮一峰的网络日志

参考

  1. RFC-6455
  2. websocket协议翻译
  3. 阮一峰的网络日志
  4. 静默虚空的cnblogs
  5. 扶强cnblogs

本文由 xuthus 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论