WebSocket协议详解及使用Go实现WebSocket服务端

说明

本文主要描述WebSocket协议及请求响应交互过程,最后通过go实现一个WebSocket服务端的例子

WebSocket消息格式

  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
    0-表示后续还有帧;1-表示这是最后一帧。第一帧也可能是最后一帧。

  • RSV1、RSV2、RSV3:1 bit each
    预留位,通常设为 0,除非扩展定义了使用

  • Opcode: 4 bits
    表示帧的类型,目前定义类型有:

    • %x0 denotes a continuation frame 继续帧用于延续之前的帧。当一条消息被分割成多个帧时,除了第一个帧之外的所有帧都应该使用 Opcode = 0。

    • %x1 denotes a text frame 文本帧,用于传输文本数据(UTF-8编码)

    • %x2 denotes a binary frame 二进制帧,用于传输二进制数据

    • %x3-7 are reserved for further non-control frames 保留帧,用于非控制帧

    • %x8 denotes a connection close 关闭帧,用于表示关闭连接

    • %x9 denotes a ping Ping帧 用于心跳检测 收到一个ping帧,必须发送一个Pong帧作为响应,除非已经接收了一个Close帧。 Ping帧可以用来做服务探测保活

    • %xA denotes a pong Pong帧 用于对ping帧的响应

    • %xB-F are reserved for further control frames 保留帧,用于控制帧

  • Mask: 1 bit
    掩码标志,如果设置为1,表示需要对数据做掩码处理,出于安全考虑,WebSocket协议规定所有从client到server的数据都要进行掩码处理。server端往client端则不做要求。

  • Payload length:7 bits 7位表示, 7+16 bits 7+16位表示, or 7+64 bits 7+64位表示 负载长度

   frame-payload-length    = ( %x00-7D )
                           / ( %x7E frame-payload-length-16 )
                           / ( %x7F frame-payload-length-63 )
                           ; 7, 7+16, or 7+64 bits in length,
                           ; respectively

   frame-payload-length-16 = %x0000-FFFF ; 16 bits in length

   frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
                           ; 64 bits in length
  • Masking-key: 0 or 4 bytes 所有client端发往server的数据都会进行掩码处理,掩码是通过与32为掩码键(Masking-key)异或(XOR)运算完成的 。Masking-key由客户端按照一定规则随机生成并发送服务端,服务端通过掩码键进行解码处理

  • Payload data: (x+y) bytes
    包含 Extension data和Application data

  • Extension data: x bytes 扩展数据通常为0字节,除非协商指定长度

  • Application data: y bytes 应用程序数据

分段传输

WebSocket支持消息的分段传输,分片的主要目的是允许在消息开始时发送大小未知的消息,而无需缓冲该消息。如果消息不能分片,那么端点将不得不缓冲整个消息,以便在发送第一个字节之前计算其长度。通过分片,服务器或中间件可以选择一个合理大小的缓冲区,并在缓冲区满时,将片段写入网络。分片的第二个场景是多路复用扩展需要。

一个分片消息由单一帧组成。通过Opcode和Fin标志位来表示连续的分片。示例:对于作为一个文本消息分成三个片段发送的情况,第一个片段的Opcode将是0x1,FIN位被清除;第二个片段的Opcode将是0x0,FIN位被清除;第三个片段的Opcode将是0x0,并且FIN位被设置。

  • 控制帧 可以在插入在任何两个分片消息的中间。控制帧通常用于传输重要的控制信息,比如心跳检测(Ping/Pong帧)、关闭连接(Close帧)等。这些信息对维持连接的稳定性和正确性至关重要,因此它们可以打断正在进行的数据传输。需要注意,控制帧本身不能被分片。所有Control Frames的payload长度必须是小于等于125字节
  • 分片消息 必须按顺序发送。一段连续的分片消息中间不能插入另外的分片消息,除非事先协商约定好了这么做。

连接说明

WebSocket建立连接是基于Http的握手过程,客户端通过发送一个HTTP GET请求到服务器来发起WebSocket连接。这个请求包含特定的HTTP头字段,表明这是一个WebSocket升级请求。

Http请求头字段

  • Connection:upgrade
  • Upgrade:websocket
  • Sec-Websocket-Version:13
  • Sec-Websocket-Extensions: 这个头字段用于客户端声明它支持的WebSocket扩展。扩展可以改变WebSocket协议的行为,例如通过压缩消息或添加自定义规则
  • Sec-Websocket-Key:这是一个由客户端生成的随机值,用于安全地验证WebSocket握手。服务器接收这个密钥后,会使用它来生成一个"Sec-WebSocket-Accept"响应头的值。
  • Sec-Websocket-Protocol:这个头字段允许客户端指定它希望使用的子协议。WebSocket协议可以支持多个子协议,客户端可以通过这个头字段声明它支持的子协议列表。

服务器响应

服务器接收到HTTP请求后,会检查请求头字段,确认它是一个WebSocket升级请求
如果服务器同意升级连接,它会发送一个HTTP 101 Switching Protocols响应。

响应头字段

  • Upgrade: websocket:确认服务器同意升级到WebSocket协议。
  • Connection: Upgrade:确认连接类型将从HTTP升级。
  • Sec-WebSocket-Accept:服务器根据客户端发送的Sec-WebSocket-Key生成的值,通过SHA-1哈希然后Base64编码得到。

使用Go gorilla实现Websocket服务端

启动文件

package main

import (
   "goLearning/gin/example/router"
)

func main() {

   r := router.InitRouter()
   
   r.Run(":18080") // 监听并在 0.0.0.0:8080 上启动服务
}

使用gin http路由功能

package router

import (
   "github.com/gin-gonic/gin"
   "goLearning/gin/example/controller/ws"
)

func InitRouter() *gin.Engine {
   r := gin.Default()
   r.GET("/echo", ws.Echo)
   gin.Recovery()
   return r
}

使用gorilla实现websocket功能

package ws

import (
   "github.com/gin-gonic/gin"
   "github.com/gorilla/websocket"
   "log"
)

var upgrader = websocket.Upgrader{} // use default options
func Echo(context *gin.Context) {
   //http协议升级为websocket协议
   conn, err := upgrader.Upgrade(context.Writer, context.Request, nil)
   if err != nil {
      log.Print("upgrade:", err)
      return
   }
   defer conn.Close()
   for {
      //默认情况下,接收一个ping事件,会自动回复一个pong帧。也可以通过监听ping事件,自定义回复内容
      conn.SetPingHandler(func(appData string) error {
       log.Printf("Received ping: %s", appData)
       return conn.WriteControl(websocket.PongMessage, []byte("hello..."), time.Now().Add(time.Second))
      })
      
      //这里可以在关闭事件添加处理逻辑
      conn.SetCloseHandler(func(code int, text string) error {
         log.Println("code : %s", code, "  text: %s", text)
         return nil
      })

      if err != nil {
         return
      }

      //接受消息
      mt, message, err := conn.ReadMessage()
      if err != nil {
         log.Println("read:", err)
         break
      }
      log.Printf("recv: %s", message)


      //发送消息
      err = conn.WriteMessage(mt, append([]byte("hello "), message...))
      if err != nil {
         log.Println("write:", err)
         break
      }
   }

}

更加详细例子及使用说明可以查看gorilla官网
https://pkg.go.dev/github.com/gorilla/websocket#section-readme

抓包分析

后续补充…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值