工作流程

更新时间:2024年1月19日 19:21 浏览:291

websocket 工作流程分为两部分

  1. 握手
  2. 数据传输

 

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 生成算法

  1. Sec-WebSocket-Key(不需要 base64 解码) 直接拼接上 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  2. 对拼接结果计算 sha1 哈希值
  3. 对 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. 数据传输

 

 

 

 

 

 

导航