工作流程
更新时间:2024年1月19日 19:21
浏览:291
websocket 工作流程分为两部分
- 握手
- 数据传输
1. 握手
握手阶段使用了 http get 请求,支持 http 协议的所有功能,如传输 get 参数,读写 cookie
1.1 客户端发起 http 连接,发送的数据示例:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
- GET /chat HTTP/1.1 - GET 请求指定网址
- Host:server.example.com - 请求的主机名
- Upgrade: websocket - 升级到 websocket 协议。
- Connection: Upgrade - 请求升级协议
- Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ== - 客户端生成的长度16位随机字符串, base64 编码,配合服务端响应的 Sec-WebSocket-Accept 实现简单身份验证,用于防止恶意的连接,或者无意的连接。
- Origin:http://example.com - 请求源(客户端是 Web 浏览器时提供),用于跨域验证
- Sec-WebSocket-Protocol: chat, superchat - 子协议, (非必要)
- Sec-WebSocket-Version: 13 - websocket 版本,当前标准锁定为 13
注:以 Sec- 开头的请求头受浏览器保护,用户无法通过 Javascipt API (如:XMLHttpRequest) 设置此类请求头
1.2 服务器响应
服务器验证客户端发送的 http 请求是否符合 RFC6455 规范,如有问题,则向客户端响应 http 错误(如:400),并主动断开连接
正常响应数据示例::
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
- HTTP/1.1 101 Switching Protocols - 状态码 101, 表示协议转换
- Upgrade: websocket - 升级为 websocket
- Connection: Upgrade - 连接升级
- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= - 使用请求头 Sec-WebSocket-Key 计算得出的值
- Sec-WebSocket-Protocol: chat - 子协议,非必要
Sec-WebSocket-Accept 生成算法
- Sec-WebSocket-Key(不需要 base64 解码) 直接拼接上 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
- 对拼接结果计算 sha1 哈希值
- 对 sha1 哈希值 进行 base64 编码
1.3 客户端验证
客户端验证服务端返回的数据
- 状态码是否为 101
- 核对 Sec-WebSocket-Accept 值
- 校验其它头字段(Connection,Upgrade 等)
- 如有设置,校验 Sec-WebSocket-Extensions Sec-WebSocket-Protocol 等
当客户端认为返回数据有问题时,则断开连接,不再发送数据帧
1.4 协议升级
以上交互正常完成后,该 http 连接不会断开,将被复用为 websocket 的连接通道,后续的数据传输将按照 websocket 协议进行
浏览器客户端代码
<script>
var client = new WebSocket('ws://io.liu12.com');
client.onopen = function () {
client.send('hello');
};
client.onmessage = function (e) {
console.log(e);
console.log('Receive: ' + e.data);
};
</script>
GO 语言实现握手功能的 websocket 服务器简单示例:
package main
import (
"crypto/sha1"
"encoding/base64"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
// TODO 非 GET 请求
}
if r.Header.Get("Connection") != "Upgrade" {
// TODO 检查 Connection 请求头
}
if r.Header.Get("Upgrade") != "websocket" {
// TODO 检查 Upgrade 请求头
}
if r.Header.Get("Sec-Websocket-Version") != "13" {
// TODO 检查 Sec-Websocket-Version 请求头
}
// TODO 趺域检查
// r.Header.Get("Origin")
hj, ok := w.(http.Hijacker)
if !ok {
return
}
conn, _, err := hj.Hijack()
if err != nil {
return
}
secKey := r.Header.Get("Sec-Websocket-Key")
if secKey == "" {
// TODO 请求头 Sec-Websocket-Key 为空处理
return
}
buf := []byte("HTTP/1.1 101 Switching Protocols\n")
buf = append(buf, "Upgrade: websocket\r\n"...)
buf = append(buf, "Connection: Upgrade\r\n"...)
// 计算 Sec-WebSocket-Accept 算法
hash := sha1.New()
// 请求中 Sec-WebSocket-Key 的值
hash.Write([]byte(secKey))
// 拼接上 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
hash.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
// 计算 SHA1 哈希值,再进行 base64 编码
secAccept := base64.StdEncoding.EncodeToString(hash.Sum(nil))
buf = append(buf, "nSec-WebSocket-Accept: "...)
buf = append(buf, secAccept...)
buf = append(buf, "\r\n"...)
if _, err = conn.Write(buf); err != nil {
err := conn.Close()
if err != nil {
return
}
}
})
err := http.ListenAndServe(":80", nil)
if err != nil {
return
}
}
2. 数据传输