目录
3、WebSocket 的实例方法 和 WebSocket 的事件
2、服务端-传输层 TCP 的 KeepAlive 保活机制
前言
在项目开发时,我们经常需要与服务器进行持续的通讯以保持双方信息的同步。通常这种持久通讯在不刷新页面的情况下进行,消耗一定的内存资源常驻后台,并且对于用户不可见。就聊天的功能来说,我们有以下解决方案:
- 轮询:
- 定时轮询:通过 Ajax 轮询请求,每隔一秒或者一段时间请求一次服务器查看是否有未读消息。
- 长轮询:每一个请求发送到服务器时,服务器将请求卡主,直到有消息时才返回。
- 使用 WebSocket 协议(推荐)
由于轮询存在明显的弊端:占用服务端地资源, 增大服务端压力,会产生很多无效请求,而且消息存在延时性。所以,推荐使用 WebSocket 协议代替轮询来做即时的、持续的通讯。
一、websocket 协议
推荐一款 在线WebSocket调试 工具。
推荐一个基于 websocket 协议的用来提供持续通信的库——Socket.IO。
WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。
websocket 是一个基于应用层的网络协议,建立在 TCP 协议之上,和 HTTP 协议可以说是兄弟的关系。我们会先用 HTTP 先进行三次握手,再向服务器请求升级为websocket 协议。
WebSocket 协议将 TCP 的 Socket(套接字)应用在了 web page 上,从而使通信双方建立起一个保持在活动状态连接通道,并且属于全双工(双方同时进行双向通信)。
WebSocket 协议是借用 HTTP 协议 的 101 switch protocol 来达到协议转换的,从 HTTP 协议切换成 WebSocket 通信协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话。
1、使用 websocket 协议请求过程解析
连接过程 —— 握手过程:
- 浏览器、服务器建立 TCP 连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
- TCP 连接成功后,浏览器通过 HTTP 协议向服务器传送 WebSocket 支持的版本号等信息。(开始前的 HTTP 握手)
- 服务器收到客户端的握手请求后,同样采用 HTTP 协议回馈数据。
- 当收到了连接成功的消息后,通过 TCP 通道进行传输通信。
只需要一次握手,就可以建立持久连接。为了建立一个 WebSocket 连接,浏览器需要向服务器发送一个 HTTP 请求,这个请求和普通的 HTTP 请求不同。请求头中需要附加 Upgrade: WebSocket。这样表示这是一个申请协议升级的 HTTP 请求。其中:
- Upgrade 和 Connection 字段:告诉服务端,发起的是 webSocket 协议。
- Sec-WebSocket-Key 字段:是浏览器经过 Base64 加密后的密钥,用来和 response 里面的Sec-WebSocket-Accept进行比对验证。
- Sec-WebSocket-Version 字段:是当前的协议版本。
- Sec-WebSocket-Extensions 字段:是对 WebSocket 的协议扩展。
GET /spring-WebSocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
服务器解析对应的请求头进行响应。其中:
- Upgrade 和 Connection 字段:告诉浏览器,服务已经是基于 webSocket 协议的了,让浏览器也遵循这个协议
- Sec-WebSocket-Accept 字段:服务端确认后并加密后的 Sec-WebSocket-Accept。
HTTP/1.1 101 Switching Protocols
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
成功响应的状态码为 101,同样也有 Upgrade 表示服务器同意协议升级。成功握手后,HTTP 升级请求底层的 TCP 套接字保持打开状态,客户端和服务器都可以继续发送和接收消息。
请求消息中 Sec-WebSocket-Key 是随机的,服务器会用这些数据构造出一个 SHA-1 的信息摘要,把 Sec-WebSocket-Key 加上一个魔幻字符串。使用 SHA-1 加密,然后进行 BASE-64 编码,结果做为 Sec-WebSocket-Accept 头的值,返回给客户端。
2、创建一个 WebSocket 对象
var ws = new WebSocket('ws://echo.websocket.org');
3、WebSocket 的实例方法 和 WebSocket 的事件
(1)、WebSocket 的实例方法
WebSocket 方法 | 描述 |
---|---|
ws.send() | 使用连接发送数据 |
ws.close() | 关闭链接 |
(2)、WebSocket 事件
WebSocket 事件 | 描述 |
---|---|
open | 连接建立时触发。 |
message | 客户端接收服务端数据时触发。 |
error | 通信发生错误时触发。 |
close | 连接关闭时触发。 |
例如:
ws.onopen = function(e) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(e) {
console.log( "Received Message: " + e.data);
ws.close();
};
ws.onerror = function(e) {
console.log("error!!!");
};
ws.onclose = function(e) {
console.log("Connection closed.");
};
当然也可以使用 addEventListener 方法添加事件监听,使用 addEventListener 方法添加事件监听时需要把方法名去掉 on。例如:
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
3、WebSocket 协议与 HTTP 协议的区别
相同点:
- 都是基于 TCP 的可靠性传输协议。
- 都工作在应用层。
不同点:
- WebSocket 协议:
- 客户端和服务器只需要一次握手,就可以建立持久连接。
- 全双工(双向的)通信:客户端和服务器都能主动的向对方发送或接收数据。
- WebSocket 在建立握手时,数据是通过 HTTP 传输的。但是建立之后,是不需要 HTTP 协议的。建立了 WebSocket 之后服务器不必在浏览器发送 request 请求之后才能发送信息到浏览器,服务器可以主动向浏览器发送数据,而且信息当中不必再带有 head 的部分信息了。与 HTTP 的长链接通信相比,这种方式,不仅能降低服务器的压力,而且信息当中也减少了部分多余的信息,节省了带宽。
- HTTP 协议:
- 无状态:对于历史连接是完全没有记忆的, 每一次连接都是新的连接。
- 无连接、非持久化:一次请求, 一次响应, 不会持续。
- 半双工(单向的)通信:通信请求只能由客户端发起, 服务端只能对于请求做出应答, 服务端不能主动地向客户端发送数据。
- HTTP 链接分为短链接,长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个 Request 对应一个 Response。长链接是在一定的期限内保持 TCP 连接不断开。
4、WebSocket 与 Socket 的关系
WebSocket 是一个协议。Socket(套接字)是一组接口而不是一个协议,Socket 是为了方便使用 TCP 或 UDP 而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。
二、Socket
1、客户端-应用层实现心跳包机制
Socket 保活指的是:Socket 保持长链接。
常见的 Socket 保活有两种方案:服务端传输层 TCP 的 KeepAlive 保活机制 和 客户端应用层实现心跳包机制。
(1)、应用层的心跳包机制
跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。
(2)、心跳检测步骤
- 客户端每隔一个时间间隔发生一个探测包给服务器
- 客户端发包时启动一个超时定时器
- 服务器端接收到检测包,应该回应一个包
- 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
- 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
(3)、心跳检测的优缺点
优点:
- 最大的有点就是自己实现检测机制带来的灵活性。
- 我们可以做很多事情:控制检测时机,间隔和处理流程。还可以在发出的心跳包中加入额外信息。可以避免上面所说tcp keepalive的缺点。可以检测连接存在,还可以检测连接可用。
- 还有就是通用性,当传输层使用的是udp的时候,我们socket编程也不需要改变很多代码。socket为我们提供了tcp和udp编程。
缺点:
- 需要应用层自己实现。自己利用 socket 编程实现。
2、服务端-传输层 TCP 的 KeepAlive 保活机制
(1)、传输层 TCP 的 KeepAlive 保活机制
因为要考虑到一个服务器通常会连接多个客户端,因此由用户在应用层自己实现心跳包,代码较多 且稍显复杂,而利用TCP/IP协议层为内置的KeepAlive功能来实现心跳功能则简单得多。
不论是服务端还是客户端,一方开启KeepAlive功能后,就会自动在规定时间内向对方发送心跳包, 而另一方在收到心跳包后就会自动回复,以告诉对方我仍然在线。 因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启KeepAlive功 能,尽管这微不足道,但在按流量计费的环境下增加了费用。另一方面,KeepAlive设置不合理时可能会 因为短暂的网络波动而断开健康的TCP连接。并且,默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于很多服务端应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启KeepAlive功能并设置合理的KeepAlive参数。
(2)、KeepAlive 优缺点
优点:
- 使用简单,tcp协议提供的检活(发送探测包 ack包)。
缺点:
- KEEPALIVE的目的是探测连接是否存在,无法检测能不能发送数据,比如服务器由于负载过大到处无法响应请求,应用层的的原因导致数据无法传输,但是连接还是正常。
- 如果TCP连接的一端断网或者断电,应用层并不知晓,继续发送数据,这个数据包的优先级是高于KEEPALIVE的数据包,因此这个KEEPALIVE包是无法发送出去的,只有在长时间的重传失败后,我们才能判断连接断开,这段长时间,应用及其容易产生业务逻辑BUG。
3、案例
三、TCP 协议
1、传输控制协议(TCP)
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。
2、TCP保活的必要性
TCP 的长连接理论上只要连接建立后,就会一直保持着。但有时有一些防火墙之类的软件会自动检查主机的网络连接状况,比如说如果发现某个连接在几分钟之内都没有数据通讯,则会关闭这个连接。有时客户端与服务器需要实时的检测连接状态,就是需要知道对方是否还在线,如果对方不在线了,需要做相应的处理,这是就需要通过发送心跳包的方法监测链路的状态。
3、TCP 长连接与短连接
(1)、短连接
我们模拟一下 TCP 短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作
短连接的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。
(2)、长连接
接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。
首先说一下TCP/IP详解上讲到的TCP保活功能,保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。
【本文参考】
【分布式WebSocket - 1】超详细!WebSocket协议详解
websocket和http的瓜葛以及websocket协议实现
TCP连接 保持 保活
socket保活方案 Tcp KeepAlive和应用层HeartBeat
Socket如何保证长连接