websocket


构建网络应用的过程中,我们经常需要与服务器进行持续的通讯以保持双方信息的同步。通常这种持久通讯在不刷新页面的情况下进行,消耗一定的内存资源常驻后台,并且对于用户不可见。在WebSocket出现之前,我们有一下解决方案:

当前Web应用中较常见的一种持续通信方式,通常采取setInterval或者setTimeout实现。例如如果我们想要定时获取并刷新页面上的数据,可以结合Ajax写出如下实现:

上面的程序会每隔10秒向服务器请求一次数据,并在数据到达后存储。这个实现方法通常可以满足简单的需求,然而同时也存在着很大的缺陷:在网络情况不稳定的情况下,服务器从接收请求、发送请求到客户端接收请求的总时间有可能超过10秒,而请求是以10秒间隔发送的,这样会导致接收的数据到达先后顺序与发送顺序不一致。于是出现了采用setTimeout的轮询方式:

程序首先设置10秒后发起请求,当数据返回后再隔10秒发起第二次请求,以此类推。这样的话虽然无法保证两次请求之间的时间间隔为固定值,但是可以保证到达数据的顺序。

上面两种传统的轮询方式都存在一个严重缺陷:程序在每次请求时都会新建一个HTTP请求,然而并不是每次都能返回所需的新数据。当同时发起的请求达到一定数目时,会对服务器造成较大负担。这时我们可以采用长轮询方式解决这个问题。

长轮询的基本思想是在每次客户端发出请求后,服务器检查上次返回的数据与此次请求时的数据之间是否有更新,如果有更新则返回新数据并结束此次连接,否则服务器hold住此次连接,直到有新数据时再返回相应。而这种长时间的保持连接可以通过设置一个较大的HTTPtimeout`实现。下面是一个简单的长连接示例:

长轮询可以有效地解决传统轮询带来的带宽浪费,但是每次连接的保持是以消耗服务器资源为代价的。尤其对于Apache PHP服务器,由于有默认的workerthreads数目的限制,当长连接较多时,服务器便无法对新请求进行相应。

服务器发送事件(以下简称SSE)是HTML5规范的一个组成部分,可以实现服务器到客户端的单向数据通信。通过SSE,客户端可以自动获取数据更新,而不用重复发送HTTP请求。一旦连接建立,“事件”便会自动被推送到客户端。服务器端SSE通过事件流(EventStream)的格式产生并推送事件。事件流对应的MIME类型为text/event-stream,包含四个字段:event、data、id和retry。event表示事件类型,data表示消息内容,id用于设置客户端EventSource对象的lasteventIDstring内部属性,retry指定了重新连接的时间。

客户端中,SSE借由EventSource对象实现。EventSource包含五个外部属性:onerror,onmessage,onopen,readyState、url,以及两个内部属性:reconnectiontime与lasteventIDstring。在onerror属性中我们可以对错误捕获和处理,而onmessage则对应着服务器事件的接收和处理。另外也可以使用addEventListener方法来监听服务器发送事件,根据event字段区分处理。

SSE相较于轮询具有较好的实时性,使用方法也非常简便。然而SSE只支持服务器到客户端单向的事件推送,而且所有版本的IE(包括到目前为止的MicrosoftEdge)都不支持SSE。如果需要强行支持IE和部分移动端浏览器,可以尝试EventSourcePolyfill(本质上仍然是轮询)。SSE的浏览器支持情况如下图所示:

WebSocket协议在年诞生,年成为国际标准。所有浏览器都已经支持了。

WebSocket同样是HTML5规范的组成部分之
一,现标准版本为RFC。WebSocket相较于上述几种连接方式,实现原理较为复杂,用一句话概括就是:客户端向WebSocket服务器通知(notify)一个带有所有接收者ID(recipientsIDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。由于WebSocket本身是基于TCP协议的,所以在服务器端我们可以采用构建TCPSocket服务器的方式来构建WebSocket服务器。

这个WebSocket是一种全新的协议。它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动状态连接通道,并且属于全双工(双方同时进行双向通信)。

其实是这样的,WebSocket协议是借用HTTP协议的101switchprotocol来达到协议转换的,从HTTP协议切换成WebSocket通信协议。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。其他特点包括:

WebSocket协议被设计来取代现有的使用HTTP作为传输层的双向通信技术,并受益于现有的基础设施(代理、过滤、身份验证)。

来自客户端的首行遵照Request-Line格式。来自服务器的首行遵照Status-Line格式。

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

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

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

首先,客户端发起协议升级请求。可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。

服务端返回内容如下,状态代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。

将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接。

WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。

下面给出了WebSocket数据帧的统一格式。熟悉TCP/IP协议的同学对这样的图应该不陌生。

针对前面的格式概览图,这里逐个字段进行讲解,如有不清楚之处,可参考协议规范,或留言交流。

如果是

1,表示这是消息(message)的最后一个分片(fragment),如果是

0,表示不是是消息(message)的最后一个分片(fragment)。

一般情况下全为
0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非

0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。

操作代码,Opcode的值决定了应该如何解析后续的数据载荷(datapayload)。如果操作代码是不认识的,那么接收端应该断开连接(failtheconnection)。可选的操作代码如下:

表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。

如果Mask是

1,那么在Masking-key中会定义一个掩码键(maskingkey),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask都是
1。

此外,如果payloadlength占用了多个字节的话,payloadlength的二进制表达采用网络序(bigendian,重要的位在前)。

所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask为

1,且携带了4字节的Masking-key。如果Mask为

0,则没有Masking-key。

应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度减去扩展数据长度,就得到应用数据的长度。

掩码键(Masking-key)是由客户端挑选出来的32位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

算法描述为:original-octet-i与masking-key-octet-j异或后,得到transformed-octet-i。

WebSocket根据opcode来区分操作的类型。比如0x8表示断开连接,0x0-0x2表示数据交互。

WebSocket的每条消息可能被切分成多个数据帧。当WebSocket的接收方收到一个数据帧时,会根据FIN的值来判断,是否已经收到消息的最后一个数据帧。

FIN=1表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。FIN=

0,则接收方还需要继续监听接收其余的数据帧。

此外,opcode在数据交换的场景下,表示的是数据的类型。0x01表示文本,0x02表示二进制。而0x00比较特殊,表示延续帧(continuationframe),顾名思义,就是完整消息对应的数据帧还没接收完。

WebSocket为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的TCP通道保持连接没有断开。然而,对于长时间没有数据往来的连接,如果依旧长时间保持着,可能会浪费包括的连接资源。

但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。

ping、pong的操作,对应的是WebSocket的两个控制帧,opcode分别是0x9、0xA。

一旦发送或接收到一个Close控制帧,这就是说,_WebSocket关闭阶段握手已启动,且WebSocket连接处于CLOSING状态。

当底层TCP连接已关闭,这就是说WebSocket连接已关闭且WebSocket连接处于CLOSED状态。如果TCP连接在WebSocket关闭阶段已经完成后被关闭,WebSocket连接被说成已经完全地关闭了。

如果WebSocket连接不能被建立,这就是说,WebSocket连接关闭,但不是完全的。

当关闭一个已经建立的连接(例如,当在打开阶段握手已经完成后发送一个关闭帧),端点可以表明关闭的原因。由端点解释这个原因,并且端点应该给这个原因采取动作,本规范是没有定义的。本规范定义了一组预定义的状态码,并指定哪些范围可以被扩展、框架和最终应用使用。状态码和任何相关的文本消息是关闭帧的可选的组件。

WebSocket对象提供了用于创建和管理WebSocket连接,以及可以通过该连接发送和接收数据的API。

可以是一个单个的协议名字字符串或者包含多个协议名字字符串的数组。这些字符串用来表示子协议,这样做可以让一个服务器实现多种WebSocket子协议(例如你可能希望通过制定不同的协议来处理不同类型的交互)。如果没有制定这个参数,它会默认设为一个空字符串。

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

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

关闭WebSocket连接或停止正在进行的连接请求。如果连接的状态已经是closed,这个方法不会有任何效果

一个数字值表示关闭连接的状态号,表示连接被关闭的原因。如果这个参数没有被指定,默认的取值是(表示正常连接关闭)。请看CloseEvent页面的listofstatuscodes来看默认的取值。

一个可读的字符串,表示连接被关闭的原因。这个字符串必须是不长于123字节的UTF-8文本(不是字符)。

WebSocket是基于TCP的独立的协议。它与HTTP唯一的关系是它的握手是由HTTP服务器解释为一个Upgrade请求。

WebSocket协议试图在现有的HTTP基础设施上下文中解决现有的双向HTTP技术目标;同样,它被设计工作在HTTP端口80和443,也支持HTTP代理和中间件,

HTTP服务器需要发送一个“Upgrade”请求,即101SwitchingProtocol到HTTP服务器,然后由服务器进行协议转换。

前面提到了,Sec-WebSocket-Key/ept在主要作用在于提供基础的防护,减少恶意连接、意外连接。

避免服务端收到非法的websocket连接(比如ept的转换计算公式是公开的,而且非常简单,最主要的作用是预防一些常见的意外情况(非故意的)。

WebSocket协议中,数据掩码的作用是增强协议的安全性。但数据掩码并不是为了保护数据本身,因为算法本身是公开的,运算也不复杂。除了加密通道本身,似乎没有太多有效的保护通信安全的办法。

那么为什么还要引入掩码计算呢,除了增加计算机器的运算量外似乎并没有太多的收益(这也是不少同学疑惑的点)。

答案还是两个字:安全。但并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxycachepoisoningattacks)等问题。

taotaost:如果鲍勃的数字证书被客户端2给窃取了,鲍勃的私钥没有被窃取,那是不是此时客户端2就完全可以冒充鲍勃了呢?

weixin_:牛设置showSymbol和showAllSymbol真的有效

晓小邰:equals是字符串对象之间的比较,str=null的实质是没有等于任何东西,不是字符串对象。



1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。

2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。