WebSocket简介

服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

(1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

image-20231011133815387

请求示例

  1. 客户端:

    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. 服务端:

    HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
        Sec-WebSocket-Protocol: chat
    

观察报文中的参数websocket像是http的升级,但是准确来说他不是建立在http之上的,只是通过http握手之后进行服务的升级

建立连接的步骤建立

  1. 客户端发起WebSocket连接:客户端通过HTTP协议向服务器发起WebSocket连接请求。这是一个普通的HTTP GET请求,但包含特定的WebSocket头信息,例如升级协议(Upgrade)为"websocket"、连接方式(Connection)为"Upgrade",以及一个随机生成的Sec-WebSocket-Key头部。客户端还可以附加其他WebSocket特定的头部信息。
  2. 服务器接受WebSocket连接请求:服务器接收客户端的HTTP连接请求,并检查头部信息以确保其包含了WebSocket相关的信息。如果一切正常,服务器将向客户端发送HTTP 101切换协议(HTTP 101 Switching Protocols)响应,表示接受了WebSocket连接。
  3. WebSocket握手:在接受WebSocket连接后,服务器和客户端进行握手。服务器会计算客户端发送的Sec-WebSocket-Key的哈希值,并与一个固定的GUID(“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”)一起计算SHA-1哈希值,然后将结果转换为Base64编码。这个Base64编码的值将作为响应头部Sec-WebSocket-Accept返回给客户端,以证明WebSocket连接已成功建立。
  4. WebSocket连接建立:一旦客户端收到HTTP 101切换协议响应,并验证Sec-WebSocket-Accept头部,连接被视为成功建立。此时,WebSocket连接可以用于双向通信。

传输数据帧

握手成功完成后,您的应用程序可以从客户端读取数据或向客户端写入数据。WebSocket 规范定义了的一个客户机和服务器之间使用的特定帧格式。这是框架的位模式:

image-20231013220340208

http和websocket

共同点

  1. 都是基于tcp协议的,所以都是可靠传
  2. 都在应用层

不同

  1. websocket是双向通讯的协议,模拟socket协议,可以双向发送和接受信息,而http是单向的
  2. 都需要握手建立连接,但是http每次都需要进行握手,而websocket在握手之后再发送数据无需进行握手,
  3. websocket没有header

websocket 握手过程

  1. 浏览器和服务端建立tcp三次握手,这是通讯的基础,传输控制层,若失败了就不继续了
  2. tcp握手完成后,浏览器通过http协议向服务端发送websocket支持的版本号等信息(开始前的http握手)
  3. 服务器收到客户端的握手请求后,同样采用http协议回馈数据。
  4. 当收到了连接成功的信息后,通过tcp通道进行传输通讯

在GO语言使用websocket

要基于 Go 语言内置的 net/http 库编写 WebSocket 服务器,你需要:

  • 发起握手
  • 从客户端接收数据帧
  • 发送数据帧给客户端
  • 关闭握手

建立发起握手

首先,让我们创建一个带有 WebSocket 端点的 HTTP 处理程序:

// HTTP server with WebSocket endpoint
func Server() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            ws, err := NewHandler(w, r)
            if err != nil {
                 // handle error
            }
            if err = ws.Handshake(); err != nil {
                // handle error
            }
        

然后初始化 WebSocket 结构。

初始握手请求始终来自客户端。服务器确定了 WebSocket 请求后,需要使用握手响应进行回复。

请记住,你无法使用 http.ResponseWriter 编写响应,因为一旦开始发送响应,它将关闭其基础的 TCP 连接(这是 HTTP 协议的运行机制决定的,发送响应后即关闭连接)。

因此,您需要使用 HTTP 劫持 (hijack)。通过劫持,可以接管基础的 TCP 连接处理程序和 bufio.Writer。这使可以在不关闭 TCP 连接的情况下读取和写入数据。

// NewHandler initializes a new handler
func NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) {
        hj, ok := w.(http.Hijacker)
        if !ok {
            // handle error
        }                  .....
}

要完成握手,服务器必须使用适当的头进行响应。

// Handshake creates a handshake header
    func (ws *WS) Handshake() error {

        hash := func(key string) string {
            h := sha1.New()
            h.Write([]byte(key))
            h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

        return base64.StdEncoding.EncodeToString(h.Sum(nil))
        }(ws.header.Get("Sec-WebSocket-Key"))
      .....
}

客户端发起 WebSocket 连接请求时用的 Sec-WebSocket-key 是随机生成的,并且是 Base64 编码的。接受请求后,服务器需要将此密钥附加到固定字符串。假设秘钥是 x3JJHMbDL1EzLkh9GBhXDw==。在这个例子中,可以使用 SHA-1 计算二进制值,并使用 Base64 对其进行编码。得到 HSmrc0sMlYUkAGmm5OPpG2HaGWk=。然后使用它作为 Sec-WebSocket-Accept 响应头的值。

使用第三方库快速构建 WebSocket 服务