沙滩星空的博客沙滩星空的博客

WebSocket协议解析(基于Go语言)

简介

WebSocket在2008年诞生,2011成为国际标准,其定义在RFC6455,
最大特点就是服务器可以向客户端推送消息,是真正的双向平等对话.

特点:

建立在TCP协议之上,服务端实现比较容易。
与HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手采取HTTP协议。
可以发送文本也可以发送二进制数据。
没有同源限制,客户端可以与任意服务器通信。
协议标识符是WS,加密则为wss,服务器网址就是URL。

基于Go实现

官方库: golang.org/x/net/websocket

客户端

conn, err := websocket.Dial(url, subprotocol, origin)
websocket.Message.Send(conn, "") // 发送一个 string
var b []byte
websocket.Message.Receive(conn, &b) // 接收一个 []byte

客户端用 websocket.Dial() 来创建连接 *websocket.Conn。其中:

  • subprotocol 表示细分的协议格式(如多种不同的序列化方法),默认可为空
  • origin 表示发起请求的网站(只需要 http://<;host> 这样)

虽然 *websocket.Conn 有 Read/Write 等方法,但使用 websocket.Message 更方便,因为可以保证一个封包的完整性。

服务端

var recv func([]byte)
var err error
f := func(conn *websocket.Conn) {
    for {
        var b []byte
        err = websocket.Message.Receive(conn, &b)
        if err != nil {
            return
        } else {
            recv(b)
        }
    }
}

websocket.Handler(f).ServeHTTP(w, r)

用 websocket.Handler 或者 websocket.Server 两个类来升级(Upgrade) HTTP 请求,在回调中会收到一个 *websocket.Conn 以供业务方使用。

  • Handler 或 Server 均可注册到 net.http 中使用,但也可以自行调用 ServeHTTP 方法
  • Handler 只有一个简单回调的函数接口,使用闭包可以使用更多上下文

连接收发

如前文所示,可以用 websocket.Message 来进行简单的二进制或者字符串的收发,并且一次是一个完整的封包。

websocket.Codec 还可以支持序列化与反序列化,直接收发 golang 对象,只需要定义两个函数就好了,一个序列化,一个反序列化。websocket.JSON 是预置的解码器。另外 websocket.Message 也是一个解码器。

当然 *websocket.Conn 本身也实现了 net.Conn,拥有 RemoteAddr、Read、Write 等方法。只是使用 Read/Write 会模糊 WebSocket 协议的封装,没有必要。

TCP升级WebSocket

TCP 私有协议与 WebSocket 握手协议有完全不同的协议头。所以判断头三个字节是不是 GET,就可以区分要不要转 WebSocket。

服务端创建 websocket.Conn 可以通过 Handler.ServeHTTP(),但 TCP 协议,只有一个 net.TCPConn,而且已经读取了一些内容了。现在需要把一个 []byte + net.TCPConn 变成 http.ResponseWriter + http.Request。

http.ResponseWriter : 是一个接口,可以简单模拟,而且 WebSocket 会通过 Hijack 转走,所以可以暴力实现之:

type wsFakeWriter struct {
    conn *net.TCPConn
    rw   *bufio.ReadWriter
}

func makeHttpResponseWriter(conn *net.TCPConn) *wsFakeWriter {
    w := new(wsFakeWriter)
    w.conn = conn
    w.rw = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))

    return w
}

func (w *wsFakeWriter) Header() http.Header {
    return nil
}

func (w *wsFakeWriter) WriteHeader(int) {
    // 处理升级失败情况??
}

func (w *wsFakeWriter) Write(b []byte) (int, error) {
    return 0, nil
}

func (w *wsFakeWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    return w.conn, w.rw, nil
}

*http.Request : 很幸运地有 http.ReadRequest() 可用:

 func ReadRequest(b *bufio.Reader) (*Request, error)

只是需要把 []byte 和 *net.TCPConn 打包成 bufio.Reader:

func joinBufferAndReader(buffer []byte, reader io.Reader) io.Reader {
    return io.MultiReader(bytes.NewReader(buffer), reader)
}

func takeHttpRequest(buffer []byte, conn *net.TCPConn) (*http.Request, error) {
    r := joinBufferAndReader(buffer, conn)
    return http.ReadRequest(bufio.NewReader(r))
}

[]byte 可以变成 io.Reader,*net.TCPConn 自然就是 io.Reader,标准库还能串联 io.Reader

使用方法

func WebsocketOnTCP(buffer []byte, conn *net.TCPConn, recv func(*Package)) error {
    req, err := takeHttpRequest(buffer, conn)
    if err != nil {
        return err
    }

    f := func(ws *websocket.Conn) {
        err = doWebSocket(ws, recv)
    }

    w := makeHttpResponseWriter(conn)
    websocket.Handler(f).ServeHTTP(w, req)

    return err
}

func doWebSocket(conn *websocket.Conn, recv func(*Package)) error {
    remoteAddr := conn.RemoteAddr()
    reply := func(b []byte) error {
        websocket.Message.Send(conn, b) // 此处线程不安全
    }

    for {
        var b []byte
        err := websocket.Message.Receive(conn, &b)
        if err != nil {
            return err
        }

        pack := new(Package)
        pack.Addr = remoteAddr
        pack.Content = b
        pack.Reply = reply

        recv(pack)
    }
}

WebSocket协议解析 https://blog.csdn.net/hzb869168467/article/details/122275738
GO语言的websocket数据解析 https://blog.csdn.net/a53818742/article/details/109067150
golang实现整型和字节数组之间的转换操作 https://www.zhangshengrong.com/p/AvN6YMdZam/
Go实现websocket协议 https://juejin.cn/post/6963872619632263175
golang 从 TCP 升级为 WebSocket https://zhuanlan.zhihu.com/p/337565377

未经允许不得转载:沙滩星空的博客 » WebSocket协议解析(基于Go语言)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址