Go WebSocket 200 行代码开发一个简易聊天室 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
Nazz

Go WebSocket 200 行代码开发一个简易聊天室

  •  
  •   Nazz 2023 年 1 月 30 日 3021 次点击
    这是一个创建于 1179 天前的主题,其中的信息可能已经有所发展或是发生改变。

    lib

    github.com/lxzan/gws

    效果图

    Go WebSocket 200 行代码开发一个简易聊天室

    Go WebSocket 200 行代码开发一个简易聊天室

    服务端 main.go

    package main import ( _ "embed" "encoding/json" "github.com/lxzan/gws" "log" "net/http" "time" ) const PingInterval = 15 * time.Second // 客户端心跳间隔 //go:embed index.html var html []byte func main() { var handler = NewWebSocket() var upgrader = gws.NewUpgrader(func(c *gws.Upgrader) { c.CompressEnabled = true c.EventHandler = handler // 在 querystring 里面传入用户名 // 把 Sec-WebSocket-Key 作为连接的 key // 刷新页面的时候, 会触发上一个连接的 OnClose/OnError 事件, 这时候需要对比 key 并删除 map 里存储的连接 c.CheckOrigin = func(r *gws.Request) bool { var name = r.URL.Query().Get("name") if name == "" { return false } r.SessionStorage.Store("name", name) r.SessionStorage.Store("key", r.Header.Get("Sec-WebSocket-Key")) return true } }) http.HandleFunc("/connect", func(writer http.ResponseWriter, request *http.Request) { socket, err := upgrader.Accept(writer, request) if err != nil { log.Printf("Accept: " + err.Error()) return } socket.Listen() }) http.HandleFunc("/index.html", func(writer http.ResponseWriter, request *http.Request) { _, _ = writer.Write(html) }) if err := http.ListenAndServe(":3000", nil); err != nil { log.Fatalf("%+v", err) } } func NewWebSocket() *WebSocket { return &WebSocket{sessions: gws.NewConcurrentMap(16)} } type WebSocket struct { sessions *gws.ConcurrentMap // 使用内置的 ConcurrentMap 存储连接, 可以减少锁冲突 } func (c *WebSocket) getName(socket *gws.Conn) string { name, _ := socket.SessionStorage.Load("name") return name.(string) } func (c *WebSocket) getKey(socket *gws.Conn) string { name, _ := socket.SessionStorage.Load("key") return name.(string) } // 根据用户名获取 WebSocket 连接 func (c *WebSocket) GetSocket(name string) (*gws.Conn, bool) { if v0, ok0 := c.sessions.Load(name); ok0 { if v1, ok1 := v0.(*gws.Conn); ok1 { return v1, true } } return nil, false } // RemoveSocket 移除 WebSocket 连接 func (c *WebSocket) RemoveSocket(socket *gws.Conn) { name := c.getName(socket) key := c.getKey(socket) if mSocket, ok := c.GetSocket(name); ok { if mKey := c.getKey(mSocket); mKey == key { c.sessions.Delete(name) } } } func (c *WebSocket) OnOpen(socket *gws.Conn) { name := c.getName(socket) if v, ok := c.sessions.Load(name); ok { var cOnn= v.(*gws.Conn) conn.Close(1000, []byte("connection replaced")) } socket.SetDeadline(time.Now().Add(3 * PingInterval)) c.sessions.Store(name, socket) log.Printf("%s connected\n", name) } func (c *WebSocket) OnError(socket *gws.Conn, err error) { name := c.getName(socket) c.RemoveSocket(socket) log.Printf("onerror, name=%s, msg=%s\n", name, err.Error()) } func (c *WebSocket) OnClose(socket *gws.Conn, code uint16, reason []byte) { name := c.getName(socket) c.RemoveSocket(socket) log.Printf("onclose, name=%s, code=%d, msg=%s\n", name, code, string(reason)) } func (c *WebSocket) OnPing(socket *gws.Conn, payload []byte) {} func (c *WebSocket) OnPong(socket *gws.Conn, payload []byte) {} type Input struct { To string `json:"to"` Text string `json:"text"` } func (c *WebSocket) OnMessage(socket *gws.Conn, message *gws.Message) { defer message.Close() // chrome websocket 不支持 ping 方法, 所以在 text frame 里面模拟 ping if b := message.Bytes(); len(b) == 4 && string(b) == "ping" { socket.WriteMessage(gws.OpcodeText, []byte("pong")) socket.SetDeadline(time.Now().Add(3 * PingInterval)) return } var input = &Input{} _ = json.Unmarshal(message.Bytes(), input) if v, ok := c.sessions.Load(input.To); ok { v.(*gws.Conn).WriteMessage(gws.OpcodeText, message.Bytes()) } } 

    客户端 index.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ChatRoom</title> <style> #app { width: 400px; margin: 50px auto 0; } .form { margin: 10px auto; } #app input { width: 300px; height: 20px; float: right; } #app span { height: 26px; line-height: 26px; } textarea { width: 400px; } </style> </head> <body> <div id="app"> <div class="form"><span>From</span> <input type="text" id="from"></div> <div class="form"><span>To</span> <input type="text" id="to"></div> <div><textarea id="text" cols="30" rows="10"></textarea></div> <button Onclick="connect()">Connect</button> <button Onclick="send()">Send</button> </div> <script> function connect() { let from = document.getElementById("from").value; window.ws = new WebSocket(`ws://127.0.0.1:3000/connect?name=${from}`); window.ws.Onclose= function (event) { console.log(event); } if (window.interval !== undefined) { clearInterval(window.interval) } window.interval = setInterval(function () { window.ws.send("ping"); }, 15 * 1000) } function send() { let to = document.getElementById("to").value; let text = document.getElementById("text").value; ws.send(JSON.stringify({to, text})); } </script> </body> </html> 
    11 条回复    2023-01-31 11:17:05 +08:00
    puzzle9
        1
    puzzle9  
       2023 年 1 月 30 日
    那啥 我不知道这个应该怎么形容 祝你新年快乐 start 多多
    Nazz
        2
    Nazz  
    OP
       2023 年 1 月 30 日 via Android
    @puzzle9 新年快乐,祝你升职涨薪:)
    maocat
        3
    maocat  
       2023 年 1 月 31 日
    那啥,让我先学下 gws 这个包
    Ranying
        4
    Ranying  
       2023 年 1 月 31 日
    客户端 ws 断开连接没有明显提示信息,没有看到 ws 的 onmessage 方法。
    Nazz
        5
    Nazz  
    OP
       2023 年 1 月 31 日 via Android
    @Ranying 断开连接触发的是 onclose/onerror
    Nazz
        6
    Nazz  
    OP
       2023 年 1 月 31 日 via Android
    @maocat 重点是学习长连接生命周期管理,API 很简单
    lizhenda
        7
    lizhenda  
       2023 年 1 月 31 日
    API 清晰,不过 ping pong 是不是反过来了?
    linauror
        8
    linauror  
       2023 年 1 月 31 日
    @lizhenda 客户端发 ping ,服务端回 pong ,应该没问题啊
    Nazz
        9
    Nazz  
    OP
       2023 年 1 月 31 日
    @lizhenda 主动方发送 ping, 被动方回复 pong
    quicksand
        10
    quicksand  
       2023 年 1 月 31 日
    最近正好在看 go ,学习一下
    Nazz
        11
    Nazz  
    OP
       2023 年 1 月 31 日
    @quicksand 学习使我快乐
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4865 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 64ms UTC 09:39 PVG 17:39 LAX 02:39 JFK 05:39
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86