V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
limpo
V2EX  ›  Go 编程语言

基于 go-netty 编写的一款 websocket 库 nettyws

  •  
  •   limpo ·
    limpo1989 · 2023-11-03 09:18:05 +08:00 · 1530 次点击
    这是一个创建于 394 天前的主题,其中的信息可能已经有所发展或是发生改变。

    nettyws是一款基于go-netty编写的高性能 websocket 库,底层协议编解码基于gobwas/ws但是针对性进行了额外的性能优化,相比较直接使用gobwas/ws库,nettyws的接口更加易于使用,性能也明显优于它

    仓库地址: https://github.com/go-netty/go-netty-ws

    API 预览

    type Websocket
        func NewWebsocket(options ...Option) *Websocket
        func (ws *Websocket) Close() error
        func (ws *Websocket) Listen(addr string) error
        func (ws *Websocket) Open(addr string) error
        func (ws *Websocket) UpgradeHTTP(w http.ResponseWriter, r *http.Request) (Conn, error)
    
    type Option
        func WithAsyncWrite(writeQueueSize int, writeForever bool) Option
        func WithBinary() Option
        func WithBufferSize(readBufferSize, writeBufferSize int) Option
        func WithCompress(compressLevel int, compressThreshold int64) Option
        func WithMaxFrameSize(maxFrameSize int64) Option
        func WithNoDelay(noDelay bool) Option
        func WithServeMux(serveMux *http.ServeMux) Option
        func WithServeTLS(tls *tls.Config) Option
        func WithValidUTF8() Option
    

    性能报告

    Framework TPS Conns Concurrency Payload CPU Avg CPU Max MEM Min MEM Avg MEM Max
    gobwas 510595 10000 10000 1024 762.87 785.80 361.89M 364.79M 366.24M
    nettyws 637288 10000 10000 1024 636.00 643.86 173.52M 178.02M 182.52M

    详细性能压测数据来源: go-websocket-benchmark

    如何使用

    服务器

    // create websocket instance
    var ws = nettyws.NewWebsocket()
    
    // setup OnOpen handler
    ws.OnOpen = func(conn nettyws.Conn) {
        fmt.Println("OnOpen: ", conn.RemoteAddr())
    }
    
    // setup OnData handler
    ws.OnData = func(conn nettyws.Conn, data []byte) {
        fmt.Println("OnData: ", conn.RemoteAddr(), ", message: ", string(data))
        conn.Write(data)
    }
    
    // setup OnClose handler
    ws.OnClose = func(conn nettyws.Conn, err error) {
        fmt.Println("OnClose: ", conn.RemoteAddr(), ", error: ", err)
    }
    
    fmt.Println("listening websocket connections ....")
    
    // listen websocket server
    if err := ws.Listen("ws://127.0.0.1:9527/ws"); nil != err {
        panic(err)
    }
    

    客户端

    // create websocket instance
    var ws = nettyws.NewWebsocket()
    
    // setup OnOpen handler
    ws.OnOpen = func(conn nettyws.Conn) {
        fmt.Println("OnOpen: ", conn.RemoteAddr())
        conn.Write([]byte("hello world"))
    }
    
    // setup OnData handler
    ws.OnData = func(conn nettyws.Conn, data []byte) {
        fmt.Println("OnData: ", conn.RemoteAddr(), ", message: ", string(data))
    }
    
    // setup OnClose handler
    ws.OnClose = func(conn nettyws.Conn, err error) {
        fmt.Println("OnClose: ", conn.RemoteAddr(), ", error: ", err)
    }
    
    fmt.Println("open websocket connection ...")
    
    // connect to websocket server
    if err := ws.Open("ws://127.0.0.1:9527/ws"); nil != err {
        panic(err)
    }
    

    从标准 http 服务器升级

    // create websocket instance
    var ws = nettyws.NewWebsocket()
    
    // setup OnOpen handler
    ws.OnOpen = func(conn nettyws.Conn) {
        fmt.Println("OnOpen: ", conn.RemoteAddr())
    }
    
    // setup OnData handler
    ws.OnData = func(conn nettyws.Conn, data []byte) {
        fmt.Println("OnData: ", conn.RemoteAddr(), ", message: ", string(data))
        conn.Write(data)
    }
    
    // setup OnClose handler
    ws.OnClose = func(conn nettyws.Conn, err error) {
        fmt.Println("OnClose: ", conn.RemoteAddr(), ", error: ", err)
    }
    
    fmt.Println("upgrade websocket connections ....")
    
    // upgrade websocket connection from http server
    serveMux := http.NewServeMux()
    serveMux.HandleFunc("/ws", func(writer http.ResponseWriter, request *http.Request) {
        ws.UpgradeHTTP(writer, request)
    })
    
    // listen http server
    if err := http.ListenAndServe(":9527", serveMux); nil != err {
        panic(err)
    }
    

    特别说明,nettyws 不支持混合数据数据模式,也就是说服务器启动时必须指定数据包格式(文本/二进制,默认文本格式)如果收到了非指定数据包格式则将会被丢弃,这样做的原因之一是nettyws使用的底层传输层抽象成 io.ReadWriter 之后无法携带除数据之外的格式信息,同时真实业务场景上同时混用数据格式的情况比比较少见,因此直接放弃了混合数据格式的支持。

    11 条回复    2023-11-06 17:09:22 +08:00
    cian
        1
    cian  
       2023-11-03 09:43:28 +08:00 via Android
    不错已星
    limpo
        2
    limpo  
    OP
       2023-11-03 09:50:26 +08:00
    @cian 感谢 ❤
    lifespy
        3
    lifespy  
       2023-11-03 10:21:40 +08:00
    看着好像很不错。我刚刚接触 go 不久,想问问,我举个例子。
    如果说我想和 goframe 框架结合使用,这个路由该如何定义呢
    limpo
        4
    limpo  
    OP
       2023-11-03 10:31:21 +08:00
    @lifespy 抱歉 我对 goframe 不是太熟悉,一般基于标准库 server 的路由都可以参考 **从标准 http 服务器升级** 这个例子进行适配
    lifespy
        5
    lifespy  
       2023-11-03 13:54:50 +08:00
    @limpo #4 试了下,可以的。都是标准 http 接口
    https://img.cdn.xiubbs.com/file/23d18395dbb4d7d24c7cc.png
    limpo
        6
    limpo  
    OP
       2023-11-03 14:08:37 +08:00
    @lifespy 👍
    yrzs
        7
    yrzs  
       2023-11-03 16:48:39 +08:00
    starred
    limpo
        8
    limpo  
    OP
       2023-11-03 17:38:20 +08:00
    @yrzs 感谢 ❤
    lifespy
        9
    lifespy  
       2023-11-06 11:09:00 +08:00
    前端 js 发送文本消息,服务端接受时提示
    OnClose: 127.0.0.1:52295 , error: flate: corrupt input before offset 6
    但是用 apipost 模拟又没有问题
    limpo
        10
    limpo  
    OP
       2023-11-06 15:42:34 +08:00
    @lifespy 是和浏览器压缩协商出了点问题,可以更新一下版本到 v1.0.2
    lifespy
        11
    lifespy  
       2023-11-06 17:09:22 +08:00
    @limpo #10 确实好了,这一些让我不敢用在项目里面了😅
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2481 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 02:38 · PVG 10:38 · LAX 18:38 · JFK 21:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.