- 本笔记一共分为4步
- 建立TCP连接
- 建立WebSocket连接
- 发送和接收数据
- 关闭WebSocket连接
1 建立TCP连接
- WebSocket协议在浏览器和服务器之间建立了一个持久化的TCP连接。
- 这个连接会一直保持到浏览器或服务器关闭连接为止。
- 持久化的TCP连接使得浏览器和服务器之间可以随时发送数据,而不必像HTTP协议那样必须等待请求和响应。
1.1 从抓包结果分析
- 客户端向服务器发送SYN包(第一次握手):客户端发送一个SYN标志的TCP包,请求与服务器建立连接。
- 服务器向客户端发送SYN+ACK包(第二次握手):服务器收到客户端发送的SYN包后,会向客户端发送一个SYN+ACK标志的TCP包,表示已经收到客户端的请求,并同意建立连接。
- 客户端向服务器发送ACK包(第三次握手):客户端收到服务器发送的SYN+ACK包后,会向服务器发送一个ACK标志的TCP包,表示确认建立连接。此时,TCP连接已经建立完成,客户端和服务器可以进行数据传输。
- 客户端向服务器发送WebSocket握手请求:客户端会向服务器发送一个HTTP请求,包含WebSocket握手所需的信息,如Upgrade、Connection、Sec-WebSocket-Key等字段。
- 服务器向客户端发送WebSocket握手响应:服务器收到客户端的WebSocket握手请求后,会返回一个HTTP响应,包含WebSocket握手所需的信息,如Upgrade、Connection、Sec-WebSocket-Accept等字段。
- 客户端向服务器发送WebSocket Frame:WebSocket连接建立完成后,客户端和服务器可以在建立好的TCP连接上进行双向通信。此时,客户端会向服务器发送WebSocket Frame,包含需要传输的数据。
2 建立WebSocket连接
- 在建立WebSocket连接之前,浏览器首先向服务器发送一个HTTP请求,请求升级到WebSocket协议。
- 这个请求中包含了一些特殊的头部信息,告诉服务器浏览器要升级到WebSocket协议。
- 如果服务器支持WebSocket协议,就会返回一个特殊的响应,告诉浏览器已经升级到WebSocket协议了。
- 这个响应中包含了一些特殊的头部信息,告诉浏览器如何和服务器进行通信。
- 这个过程称为WebSocket握手。
2.1 WebSocket握手
- 浏览器发送握手请求
- 浏览器会向服务器发送一个HTTP请求,请求升级到WebSocket协议。请求头部中包含了一些特殊的头部信息,例如:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
************************************************
Upgrade:指明升级的目标为 websocket
Connection:说明需要升级
Sec-WebSocket-Key:是一个随机生成的字符串,用于计算
Sec-WebSocket-Version:用于协商 websocket 的版本
- 服务器返回握手响应
- 如果服务器支持WebSocket协议,就会返回一个特殊的响应,告诉浏览器已经升级到WebSocket协议了
- 浏览器会将 Sec-WebSocket-Accept 的值与本地计算的结果进行比较,如果一致,说明握手成功,可以开始建立WebSocket连接了
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
*********************************************************
Upgrade和Connection字段指 服务器已经升级到WebSocket协议
Sec-WebSocket-Accept:是将Sec-WebSocket-Key和一个特定的GUID字符串拼接在一起,进行SHA-1哈希计算后生成的一个Base64编码的字符串
- 建立持久化的TCP连接
- 在握手成功之后,浏览器和服务器之间就建立了一个持久化的TCP连接,可以随时发送数据
2.2 Sec-WebSocket-Accept 的生成过程
- 获取客户端请求中的 Sec-WebSocket-Key
- 在WebSocket连接请求中,客户端会随机生成一个字符串 Sec-WebSocket-Key
- 并将其放在请求头中的Sec-WebSocket-Key字段中
- 拼接GUID
- 服务器 需要将一个特定的GUID字符串 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"和客户端请求中的Sec-WebSocket-Key字段的值 拼接在一起,生成一个新的字符串。
- 进行SHA-1哈希计算
- 服务器对上一步中生成的字符串进行 SHA-1 哈希计算,得到一个20字节的哈希值,这个哈希值是一个二进制数据。
- 进行Base64编码得到 Sec-WebSocket-Accept
- 服务器将上一步中得到的二进制哈希值进行Base64编码,生成一个字符串Sec-WebSocket-Accept
- 这个字符串就是用于验证WebSocket连接请求的服务器响应头 Sec-WebSocket-Accept
一个生成 Sec-WebSocket-Accept 的示例代码
import hashlib
import base64
client_key = 'dGhlIHNhbXBsZSBub25jZQ=='
guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
concatenated = client_key + guid
sha1 = hashlib.sha1()
sha1.update(concatenated.encode())
hash = sha1.digest()
accept = base64.b64encode(hash).decode()
print(accept)
- 为什么使用一个 固定GUID 的值与 client_key 拼接呢?
- 使用固定的值与client_key拼接,是为了确保WebSocket连接请求的安全性和唯一性。
- 这个固定的值是一个GUID(全局唯一标识符)字符串,它是一个128位的数字,可以保证全球范围内的唯一性。
- 在WebSocket协议中,规定了一个固定的GUID字符串,即"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",服务器需要将这个GUID字符串与客户端请求中的Sec-WebSocket-Key字段的值拼接在一起进行哈希计算。
- 通过使用固定的GUID字符串,可以保证每个WebSocket连接请求都是唯一的,避免了请求被伪造的可能性。
- 同时,使用SHA-1哈希算法进行计算,可以保证连接请求的安全性,避免了数据被篡改的风险。
- 因此,WebSocket协议在设计时使用了GUID字符串与客户端请求中的Sec-WebSocket-Key字段的值拼接的方式,以确保WebSocket连接请求的安全性和唯一性。
3 发送和接收数据
- 在建立WebSocket连接之后,浏览器和服务器就可以互相发送数据了。
- 当浏览器要发送数据时,它会将数据封装在WebSocket帧中,并发送到服务器。
- 服务器接收到帧后,解析出数据,并发送响应帧回到浏览器。
- 浏览器接收到响应帧后,解析出数据,然后可以继续发送数据到服务器。
- 这个过程可以不断地进行下去,直到浏览器或服务器关闭连接为止。
4 关闭WebSocket连接
- 当浏览器或服务器想要关闭WebSocket连接时,它会发送一个特殊的帧,告诉对方它要关闭连接。
- 对方接收到这个帧后,也会发送一个特殊的帧回应,告诉对方已经收到关闭请求。
- 然后双方会分别关闭TCP连接,这样WebSocket连接就成功地关闭了。