WebSocket连接过程及原理分析
# 前言
最近在面试的过程中有被问及到websocket
的连接过程(简历中项目有使用到websocket
),一时有点懵,以为是在问使用方式,后来确定了下是在问网络层面的连接过程,是如何进行的,以及http
和socket
的过程。
我只做过心跳和断网重连的一些基本使用,对原理层面知之甚少。对于简历中的项目涉及到的websocket
技术还是属于了解的比较浅的,问题很有点大啊。总的来说对于简历中的技术应该还是要做到比较熟悉才行,项目和技术栈还是有必要好好准备一番的。
# 为什么需要WebSocket
主要原因就是:HTTP 协议通信只能由客户端发起(HTTP是非持久的协议)。
以PHP生命周期为例:
- HTTP的生命周期通过
Request
来界定,也就是一个Request
一个Response
,那么在HTTP1.0中,这次HTTP请求就结束了。 - 在HTTP1.1中进行了改进,使得有一个
keep-alive
,也就是说,在一个HTTP连接中,可以发送多个Request
,接收多个Response
。
但是请记住 Request = Response
, 在HTTP中永远是这样,也就是说一个request
只能有一个response
。而且这个response
也是被动的,不能主动发起。
对于状态监听并在变化后推送消息通知到客户端这种场景,在此之前常用的手段一般是轮询:每隔一段时候,就发出一个询问,了解服务器有没有新的信息(轮询的效率低,非常浪费资源——因为必须不停连接,或者 HTTP
连接始终打开)。
轮询这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据需要付出巨大的代价,是很不合算的,占用了很多的宽带资源。
# 简介
首先,WebSocket
是HTML5新出的一种协议。
Websocket其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,有交集,但是并不是全部。
WebSocket
协议在2008年诞生,2011年成为国际标准(目前主流浏览器都支持,不存在兼容问题)。
# 一些特点
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
- 更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。
- 更小的控制开销:数据格式比较轻量,性能开销小,通信高效(用于协议控制的数据包头部相对较小)。
- 良好的
HTTP
协议兼容性:与HTTP
和HTTPS
使用相同的TCP
端口,握手阶段采用HTTP
协议,协议标识符是ws
(如果加密,则为wss
) - 更好的二进制支持:可以发送文本,也可以发送二进制数据(定义了二进制帧)。
- 可在单个
TCP
连接上进行全双工通信,位于 OSI 模型的应用层。 - 没有同源限制,客户端可以与任意服务器通信。
# 主要API
- WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例(相关API见官网)。
- 核心属性:
onopen
、onclose
、onerror
、onmessage
、binaryType
、readyState
等9个。onopen
:用于指定连接成功后的回调函数onclose
:用于指定连接关闭后的回调函数onerror
:用于指定连接失败后的回调函数onmessage
:用于指定当从服务器接受到信息时的回调函数binaryType
:使用二进制的数据类型连接(binaryType: "blob"
)readyState
(只读):返回当前 WebSocket 的连接状态,共有 4 种状态
- 核心方法:
send
、close
。send
:该方法将需要通过 WebSocket 链接传输至服务器的数据排入队列close
:该方法用于关闭 WebSocket 连接
- 事件:
open
、close
、error
、message
(配合addEventListener
方法使用,与通过上面onopen
、onclose
等4个属性方法设置使用一样)。
// readyState
- CONNECTING — 正在连接中,对应的值为 0;
- OPEN — 已经连接并且可以通讯,对应的值为 1;
- CLOSING — 连接正在关闭,对应的值为 2;
- CLOSED — 连接已关闭或者没有连接成功,对应的值为 3
2
3
4
5
# 基本用法
const 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.");
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# webSocket与http
相同点:
- 都是基于TCP的可靠性传输协议。
- 都是应用层协议
不同点:
WebSocket
是一个持久化的协议,相对于HTTP
这种非持久的协议来说。WebSocket
是双向通信协议,模拟Socket
协议,可以双向发送或接受信息。HTTP
是单向的。- 连接行为和状态
- 在传统的方式上,要不断的建立,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输identity info(鉴别信息),来告诉服务端你是谁。
- Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。
特殊说明:WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的
# webSocket的连接过程
- Websocket一开始的握手需要借助HTTP请求完成,也是建立在TCP之上的,即浏览器、服务器建立TCP连接,三次握手。
- TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。
- 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
- 当收到了连接成功的消息后,通过TCP通道进行传输通信。
在客户端,new WebSocket('ws://server.example.com')
实例化一个新的WebSocket
客户端对象,连接服务端WebSocket URL
,WebSocket
客户端对象会自动解析并识别为WebSocket
请求,从而连接服务端端口,执行双方握手过程。
# 客户端请求报文 Header
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
2
3
4
5
6
7
8
可以看到,客户端发起的WebSocket
连接报文类似传统HTTP
报文,但多出了一些东西
- Upgrade:websocket/Connection: Upgrade:参数值表明这是
WebSocket
类型请求(这个是Websocket的核心,告诉Apache、Nginx等服务器,发起的是Websocket协议)。 - Sec-WebSocket-Key:是一个
Base64
编码的值,是由浏览器随机生成的,提供基本的防护,防止恶意或者无意的连接。 - Sec_WebSocket-Protocol:是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。
- Sec-WebSocket-Version:表示 WebSocket 的版本,最初 WebSocket 协议太多,不同厂商都有自己的协议版本,不过现在已经定下来了。如果服务端不支持该版本,需要返回一个
Sec-WebSocket-Versionheader
,里面包含服务端支持的版本号。
# 服务端响应报文 Header
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
2
3
4
5
- 首先,
101
状态码表示服务器已经理解了客户端的请求,并将通过Upgrade
消息头通知客户端采用不同的协议来完成这个请求; - 然后,
Sec-WebSocket-Accept
这个则是经过服务器确认,并且加密过后的Sec-WebSocket-Key
; - 最后,
Sec-WebSocket-Protocol
则是表示最终使用的协议。
注意:Sec-WebSocket-Key/Sec-WebSocket-Accept 的换算,只能带来基本的保障,但连接是否安全、数据是否安全、客户端 / 服务端是否合法的 ws 客户端、ws 服务端,其实并没有实际性的保证。
至此,HTTP已经完成它所有工作了,接下来就是完全按照Websocket协议进行了。