websocket简述


发布于 2021-01-30 / 71 阅读 / 0 评论 /
websocket是HTML5规范中的一部分,为web应用程序客户端和服务端之间提供了一种全双工通信机制。

1.WebSocket概述

从定义上来说:WebSocket是一种在单个TCP连接上进行全双工通信的协议。

1.1.WebSocket与HTTP

WebSocket 通过HTTP/1.1 协议的101状态码进行握手。

即然已经有了HTTP协议,为什么还需要WebSocket协议呢?主要是因为HTTP协议有一个缺陷——通信只能由客户端发起。HTTP这种单向请求的特点,如果服务器有连续的状态变化(比如执行任务的日志),客户端要获知就非常麻烦。只能用轮询,每隔一段时间,就发一个询问,去了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询方案的缺点:浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

因此,HTML5定义了WebSocket协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1.2.WebSocket优点

此外,WebSocket还有以下优点:

较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。

更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。

保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。

更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。

可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。

更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

1.3.WebSocket缺点

每一项技术都有自身的缺点,只有在适合它的场景才能发挥最大长处。相对应的,WebSocket还有以下缺点。

缺点一:传输大文件、图片、媒体流的时候,最好还是老老实实用HTTP来传。如果一定要用WebSocket的话,至少也专门为这些数据专门开辟个新通道,而别去占用那条用于推送消息、对实时性要求很强的连接。否则会把串行的WebSocket彻底堵死的。

1.4.WebSocket与Socket

我们知道,在网络中的两个应用程序(进程)需要全双工相互通信(全双工即双方可同时向对方发送消息),需要用到的就是socket,它能够提供端对端通信,对于程序员来讲,他只需要在某个应用程序的一端(暂且称之为客户端)创建一个socket实例并且提供它所要连接一端(暂且称之为服务端)的IP地址和端口,而另外一端(服务端)创建另一个socket并绑定本地端口进行监听,然后客户端进行连接服务端,服务端接受连接之后双方建立了一个端对端的TCP连接,在该连接上就可以双向通讯了,而且一旦建立这个连接之后,通信双方就没有客户端服务端之分了,提供的就是端对端通信了。我们可以采取这种方式构建一个桌面版的im程序,让不同主机上的用户发送消息。从本质上来说,socket并不是一个新的协议,它只是为了便于程序员进行网络编程而对tcp/ip协议族通信机制的一种封装。

websocket是html5规范中的一个部分,它借鉴了socket这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供了一种全双工通信机制。同时,它又是一种新的应用层协议,websocket协议是为了提供web应用程序和服务端全双工通信而专门制定的一种应用层协议。

2.WebSocket客户端API

WebSocket的客户端API一般是浏览器端使用,有以下形式。

2.1.WebSocket构造函数

用于新建 WebSocket 实例,函数格式为WebSocket(url[, protocols]),例如:

var ws = new WebSocket('ws://localhost:8080');

2.2.WebSocket对象属性

WebSocket主要有以下属性:

属性名

属性说明

备注

binaryType

连接使用的二进制数据类型

可选值有blob或arraybuffer,默认为blob

bufferedAmount

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

只读属性

extensions

服务端可接收的扩展项

只读属性,利用扩展,可以发送压缩帧、多路复用帧等

onclose

指定连接关闭后的回调函数

onerror

指定报错时的回调函数

onmessage

指定收到服务器数据后的回调函数

onopen

指定连接成功后的回调函数

protocol

服务端选择的子协议

只读属性

readyState

实例对象的当前状态,共有以下四种枚举值:

CONNECTING:值为0,表示正在连接。

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

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

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

只读属性

url

WebSocket连接的url绝对地址

只读属性

2.3.WebSocket对象方法

WebSocket主要有以下两个方法:

方法名称

方法声明

方法描述

备注

close

WebSocket.close([code[, reason]])

关闭连接

send

WebSocket.send(data)

把data放到发送队列中,以便发送

data可以是String、Blob或ArrayBuffer

2.4.WebSocket监听事件

通过addEventListener方法对监听事件进行注册。WebSocket支持对以下事件进行监听:

事件类型

事件说明

close

当一个WebSocket连接断开时被触发,实现的功能与onclose属性类似。

error

当一个WebSocket连接因错误而端开时触发,例如数据未被送达,实现的功能与onerror属性类似。

message

当通过WebSocket连接接收到数据时触发,实现的功能与onmessage类似。

open

当一个WebSocket连接被open时触发,实现的功能与onopen类似。

监听事件的使用例如:

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
    socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
    console.log('Message from server ', event.data);
});

socket.onerror = function(event) {
  // handle error event
};

3.服务端实现

使用 WebSocket 为服务器端应用带来了全新的用法。虽然 LAMP 等传统服务器堆栈是围绕 HTTP 请求/响应循环而设计的,但是通常无法很好地处理大量打开的 WebSocket 连接。要同时维持大量连接处于打开状态,就需要能以低性能开销接收高并发数据的架构。此类架构通常是围绕线程或所谓的非阻塞 IO 而设计的。

根据不同的语言栈,WebSocket服务端实现可分为以下几类:

(1)Node.js:Socket.IO、WebSocket-Node、ws

(2)Java:Jetty

(3)Ruby:EventMachine

(4)Python:pywebsocket、Tornado

(5)Erlang:Shirasu

(6)C++:libwebsockets

(7).NET:SuperWebSocket

现在,WebSocket 的单线协议(客户端与服务器之间的握手和数据传输)是 RFC6455。

3.1.反向代理配置

一般通过nginx对WebSocket服务器进行反向代理。例如下nginx配置所示:

http {
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    server {
        listen       9090 ssl;
        server_name  192.168.1.100;
        location /test/websocket/ {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }
    }
}

对应不同的环境,只需修改端口、ip、location路径即可。

4.WebSocket通信原理和机制

下面主要描述WebSocket的通信机制。

4.1.Exchange Data Frame

表示客户端与服务器之间的交换数据帧。

4.1.1.数据帧格式

交换数据帧都遵从相同的格式规范,规范如下图所示:

一般来说,从客户端发送到服务端的数据都被异或加密(用一个32位的key)格式化。

MASK位(掩码)告诉我们消息是否经过格式化。来自客户端的消息必须被格式化,所以服务器必须要求MASK位为1。如果客户端发送了没有格式化的消息,服务器应该断开此连接。而当服务端向客户端发送数据帧时,不需要对数据进行格式话,也不需要设置MASK位。

opcode位(操作码)定义了如何解释Payload Data:0x0表示延续;0x1表示文本(总是用UTF-8编码);0x2表示二进制。在这个版本的WebSockets中,0x3到0x7和0xB到0xF没有任何意义。

FIN位告诉我们此帧是否为当前消息(帧序列)的最后一帧。如果是0,那么服务器将继续帧听更多的数据帧;如果是1,服务器将考虑传送消息(帧序列已完整)。

4.1.2.获取Payload Data长度

消息数据都存储在Payload Data中,要读取Payload Data,我们必须知道何时停止读取数据帧。这就是为什么Payload Data长度如此重要的原因。从数据帧格式定义中,可看出与Payload Data长度有关的数据共有64位。

获取Payload Data长度需遵循以下三个步骤:

第一步:读取第9到第15位(共7位),把它转化为无符号整数。如果该整数小于等于125,那么该整数就是Payload Data的长度。如果该整数为126,则第二步的整数就是Payload Data的长度。如果该整数为127,则第三步的整数就是Payload Data的长度。

第二步:读取接下来的16位,把它转化为无符号整数。

第三步:读取接下来的64位,把它转化为无符号整数,最高位必须是0。

4.1.3.读取并解码Payload Data

如果MASK位为1,Payload Data Length之后的32位,共4个字节,即Masking-key。

一旦获取了Payload Data Length合Masking-key,我们就可以继续从socket中读取Payload Data Length个字节,记为Payload-Data,这也是加密的数据,为了获取解密数据,我们需要对Payload-Data中的每个字节与Masking-key中的相对应字节进行亦或操作。伪代码如下所示:

var DECODED = "";
for (var i = 0; i < Payload_Data.length; i++) {
    DECODED[i] = Payload_Data[i] ^ Masking_key[i % 4];
}

4.1.4.消息帧

FIN位和opcode位是一起工作的,把需要发送的消息数据切分为数据分片,这也叫做消息帧。消息帧只有在opcode为0x00~0x02上有效。

opcode告诉我们一个数据帧应该做什么。如果opcode为0x1,Payload Data就是文本;如果是0x2,Payload Data就是二进制数据。但如果opcode为0x0,数据帧就是连续的数据帧,也就意味着当服务端接收最后一个数据帧后,服务端需要连接所有接收到的数据帧,作为一个完整的Payload Data。

下面是一个简单的数据交换过程,服务端响应客户端发送的文本消息。第一条消息由单帧发送,第二条消息由三个消息帧组成。

Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, new message containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!

需要注意的是,FIN位和opcode位只为客户端展示。

第一个消息帧包含完整的消息,所以服务端可以直接处理并响应自己接收到数据。客户端发送的第二个消息帧表示这是一个文本有效负载,但整个消息还没有完全到达(FIN=0)。等到所有消息帧(opcode=0x0)都接收完成,最后一个消息帧的FIN就会被标记为1。

4.2.WebSocket心跳

在握手之后的任意时刻,客户端和服务端都可以选择发送一个ping给对方。任何一方接收到ping消息后,必须回复一个pong消息。可以使用这种方式来确保连接还存在。

ping或pong消息都是常规的消息帧,不过是一个控制帧。ping消息的opcode字段为0x9,pong消息的opcode值为0xA。当获取到一个ping消息的时候,回复一个跟ping消息相同的Payload Data的pong消息。对于ping和pong消息,最大的Payload Data length为125。如果在没有发送ping的时候接收到pong消息,则忽略此pong消息。

如果在你有机会发送一个pong消息之前,你已经获取了超过一个的ping消息,那么你只发送一个pong消息。

如果没有心跳,则长连接会有一个超时时间,默认为6秒。可在nginx中添加proxy_read_timeout参数。

4.3.关闭连接

客户端或服务器端都可以通过发送一个带有指定控制序列的控制帧以开始关闭连接握手。对端收到这个控制帧会回复一个关闭帧,关闭发起端关闭连接。任何在关闭连接后接收到的数据都会被丢弃。

5.WebSocket使用场景

列举了以下9类使用场景

5.1.场景1——社交订阅

对社交类的应⽤的⼀个裨益之处就是能够即时的知道你的朋友正在做什么。虽然听起来有点可怕,但是我们都喜欢这样做。你不会想要在数分钟之后才能知道⼀个家庭成员在馅饼制作⼤赛获胜或者⼀个朋友订婚的消息。你是在线的,所以你的订阅的更新应该是实时的。

5.2.场景2——多玩家游戏

⽹络正在迅速转变为游戏平台。在不使⽤插件(我指的是Flash)的情况下,⽹络开发者现在可以在浏览器中实现和体验⾼性能的游戏。⽆论你是在处理DOM元素、CSS动画,HTML5的canvas或者尝试使⽤WebGL,玩家之间的互动效率是⾄关重要的。我不想在我扣动扳机之后,我的对⼿却已经移动位置。

5.3.场景3——协同编辑/编程

我们⽣活在分布式开发团队的时代。平时使⽤⼀个⽂档的副本就满⾜⼯作需求了,但是你最终需要有⼀个⽅式来合并所有的编辑副本。版本控制系统,⽐如Git能够帮助处理某些⽂件,但是当Git发现⼀个它不能解决的冲突时,你仍然需要去跟踪⼈们的修改历史。通过⼀个协同解决⽅案,⽐如WebSocket,我们能够⼯作在同⼀个⽂档,从⽽省去所有的合并版本。这样会很容易看出谁在编辑什么或者你在和谁同时在修改⽂档的同⼀部分。

5.4.场景4——点击流数据

分析⽤户与你⽹站的互动是提升你的⽹站的关键。HTTP的开销让我们只能优先考虑和收集最重要的数据部分。然后,经过六个⽉的线下分析,我们意识到我们应该收集⼀个不同的判断标准——⼀个看起来不是那么重要但是现在却影响了⼀个关键的决定。与HTTP请求的开销⽅式相⽐,使⽤Websocket,你可以由客户端发送不受限制的数据。想要在除页⾯加载之外跟踪⿏标的移动?只需要通过WebSocket连接发送这些数据到,并存储在你喜欢的NoSQL数据库中就可以了(MongoDB是适合记录这样的事件的)。现在你可以通过回放⽤户在页⾯的动作来清楚的知道发⽣了什么。

5.5.场景5——股票基⾦报价

⾦融界——⼏乎是每毫秒都在变化。我们⼈类的⼤脑不能持续以那样的速度处理那么多的数据,所以我们写了⼀些算法来帮我们处理这些事情。虽然你不⼀定是在处理⾼频的交易,但是,过时的信息也只能导致损失。当你有⼀个显⽰盘来跟踪你感兴趣的公司时,你肯定想要随时知道他们的价值,⽽不是10秒前的数据。使⽤WebSocket可以流式更新这些数据变化⽽不需要等待。

5.6.场景6——体育实况更新

现在我们开始讨论⼀个让⼈们激情澎湃的愚蠢的东西——体育。我不是运动爱好者,但是我知道运动迷们想要什么。当在打⽐赛的时候,我的妹夫将会沉浸于这场⽐赛中⽽。那是⼀种疯狂痴迷的状态,完全发⾃内⼼的。我虽然不理解这个,但是我敬佩他们与运动之间的这种强烈的联系,所以,最后我能做的就是给他的体验中降低延迟。如果你在你的⽹站应⽤中包含了体育新闻,WebSocket能够助⼒你的⽤户获得实时的更新。

5.7.场景7——多媒体聊天

视频会议并不能代替和真⼈相见,但当你不能在同⼀个屋⼦⾥见到你谈话的对象时,视频会议是个不错的选择。尽管视频会议私有化做的“不错”,但其使⽤还是很繁琐。我可是开放式⽹络的,所以⽤WebSockets getUserMedia API和HTML5⾳视频元素明显是个不错的选择。WebRTC的出现的成为我刚才概括的组合体,它看起来很有希望,但其缺乏⽬前浏览器的⽀持,所以就取消了它成为候选⼈的资格。

5.8.场景8——基于位置的应⽤

越来越多的开发者借⽤移动设备的GPS功能来实现他们。如果你⼀直记录⽤户的位置(⽐如运⾏应⽤来记录运动轨迹),你可以收集到更加细致化的数据。如果你想实时的更新⽹络数据仪表盘(可以说是⼀个监视运动员的教练),HTTP协议显得有些笨拙。借⽤WebSocket TCP链接可以让数据飞起来。

5.9.场景9——在线教育

上学花费越来越贵了,但互联⽹变得更快和更便宜。是学习的不错⽅式,尤其是你可以和⽼师以及其他同学⼀起交流。很⾃然,WebSockets是个不错的选择,可以多媒体聊天、⽂字聊天以及其它优势如与别⼈合作⼀起在公共数字⿊板上画画...