WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。
http协议是无状态的,每一个新的http请求request,只能通过client发起,server端收到后,返回一个response,然后连接断开。http1.1版本增加了keep-alive请求头,可以通过一条通道请求多次.且server端不能主动被client端发送数据,只能被动地相应请求.
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输
websocket是基于tcp的独立的协议,与http有良好的兼容性,使用80,443端口,在握手阶段采用http协议.
优点:
握手协议:
WebSocket 是独立的、创建在TCP上的协议。
Websocket 通过 HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(Handshaking)。
1.首先由客户端client发起协议升级请求.
采用http报文,用get方法,websocket连接地址为ws://host:port/path
connection:update
:表示要升级协议upgrade:websocket
:表示要升级的协议是websocketSec-WebSocket-Version: 13
:表示wbsocket的版本Sec-WebSocket-Key: x7qdyJEvQAUvGSweHB4N6g==
:一段随机的base64编码的字符串,与响应头的Sec-WebSocket-Accept
配套,提供基本的防护.Sec-WebSocket-Extensions
指定一个或多个协议级WebSocket扩展以要求服务器使用。允许在一个请求中使用多个Sec-WebSocket-Extension标头;Sec-WebSocket-Accept: yNdP+JNEFr+tEVUzCYTdSGpZy0Y=
Sec-WebSocket-Accept计算流程去如下:
Sec-WebSocket-Key
字段与魔串(258EAFA5-E914-47DA-95CA-C5AB0DC85B11
)拼接代码如下:
// golang
var magicString = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
func main() {
SecWebSocketKey := "x7qdyJEvQAUvGSweHB4N6g=="
// 将 Sec-WebSocket-Key与magicString拼接
str := append([]byte(SecWebSocketKey), magicString...)
// 创建sha1对象
h := sha1.New()
h.Write(str)
SecWebSocketAccept := base64.StdEncoding.EncodeToString(h.Sum(nil))
fmt.Println(SecWebSocketAccept) // 输出yNdP+JNEFr+tEVUzCYTdSGpZy0Y=
}
# python
import hashlib
import base64
Sec_WebSocket_Key="x7qdyJEvQAUvGSweHB4N6g=="
magic_string="258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
value =Sec_WebSocket_Key + magic_string
Sec_WebSocket_Accept= base64.b64encode(hashlib.sha1(value.encode('utf-8')).digest())
print(Sec_WebSocket_Accept)# 输出 b'yNdP+JNEFr+tEVUzCYTdSGpZy0Y='
在客户端接收到响应后,将计算的结果与Sec_WebSocket_Accept
比较,用于判断是否有效.
websocketke客户端和服务端传输的数据为帧(frame),下面为一帧的格式,以bit为单位
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
FIN: 1 bit
表示这是一个消息的最后的一帧。第一个帧也可能是最后一个。
%x0 : 还有后续帧
%x1 : 最后一帧
RSV1、2、3: 1 bit each
除非一个扩展经过协商赋予了非零值以某种含义,否则必须为0
如果没有定义非零值,并且收到了非零的RSV,则websocket链接会失败
Opcode: 4 bit
解释说明 “Payload data” 的用途/功能
如果收到了未知的opcode,最后会断开链接
定义了以下几个opcode值:
%x0 : 代表连续的帧
%x1 : text帧
%x2 : binary帧
%x3-7 : 为非控制帧而预留的
%x8 : 关闭握手帧
%x9 : ping帧
%xA : pong帧
%xB-F : 为非控制帧而预留的
Mask: 1 bit
定义“payload data”是否被添加掩码
如果置1, “Masking-key”就会被赋值
所有从客户端发往服务器的帧都会被置1从服务端向客户端发送数据时,不需要对数据进行掩码操作
Payload length: 7 bit | 7+16 bit | 7+64 bit
“payload data” 的长度如果在0~125 bytes范围内,它就是“payload length”,
如果是126 bytes, 紧随其后的被表示为16 bits的2 bytes无符号整型就是“payload length”,
如果是127 bytes, 紧随其后的被表示为64 bits的8 bytes无符号整型就是“payload length”
Masking-key: 0 or 4 bytes
所有从客户端发送到服务器的帧都包含一个32 bits的掩码(如果“mask bit”被设置成1),否则为0 bit。一旦掩码被设置,所有接收到的payload data都必须与该值以一种算法做异或运算来获取真实值.如果Mask为0,则没有Masking-key
Payload data: (x+y) bytes
它是"Extension data"和"Application data"的总和,一般扩展数据为空。
Extension data: x bytes
扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
除非扩展被定义,否则就是0
任何扩展必须指定其Extension data的长度
Application data: y bytes
任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。"Extension data"之后的剩余帧的空间
WebSocket的每条消息可能被切分成多个数据帧。当WebSocket的接收方收到一个数据帧时,会根据FIN
的值来判断,是否已经收到消息的最后一个数据帧。
FIN=1表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。FIN=0,则接收方还需要继续监听接收其余的数据帧。
此外,opcode
在数据交换的场景下,表示的是数据的类型。0x01
表示文本,0x02
表示二进制。而0x00
比较特殊,表示延续帧(continuation frame),顾名思义,就是完整消息对应的数据帧还没接收完
如果长时间没有数据往来,防止造成资源的浪费,需要主动断开连接.但是某些情况下,即使长时间没有数据也需要保持连接,即心跳机制.
一般是服务端给客户端发送Ping,然后客户端发送Pong来回应.
ping/pong操作对应的是websocket的ping/pong 控制帧
参考:
https://tonybai.com/2019/09/28/how-to-build-websockets-in-go/