基于go-zero和websocket的聊天室demo

操作步骤

使用go-zero的goctl工具,新建名为chat的api服务:

ps:goctl生成api使用的handler模板使用默认的即可,如果没有修改过tpl文件,可以不用确认tpl文件是否为初始的默认值

1
2
3
mkdir chat
cd chat
touch chat.api

修改chat.api,修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type WsConnectRequest {
ID int `path:"id"`
}

type WsRequestItem {
Message string `json:"message"`
ToID   []int `json:"to_id,omitempty"`
}

type WsResponseItem {
Message string `json:"message"`
FromID int   `json:"from_id"`
Time   int64 `json:"time"`
}

service chat-api {
@handler WsHandler
get /:id
}

根据新的api文件重新生成代码:

1
2
goctl api go -api chat.api -dir .
go mod tidy

此时chat目录内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> tree chat                          
chat
├── chat.api
├── chat.go
├── etc
│   └── chat-api.yaml
└── internal
  ├── config
  │   └── config.go
  ├── handler
  │   ├── routes.go
  │   └── wshandler.go
  ├── logic
  │   └── wslogic.go
  ├── svc
  │   └── servicecontext.go
  └── types
      └── types.go

7 directories, 9 files

在chat/internal/handler新建websocket.go文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package handler

import (
"encoding/json"
"log"
"time"

"github.com/gorilla/websocket"
"github.com/zeromicro/go-zero/core/logx"

"github.com/nrbackback/zero-chatroom/chat/internal/types"
)

type Hub struct {
clients map[int]*Client

broadcast chan Msg
}

type Msg struct {
Msg    string
FromID int
ToIDs []int
Time   int64
}

type Client struct {
ID   int
conn *websocket.Conn
send chan Msg
}

var h *Hub

func InitHub() {
h = &Hub{
broadcast: make(chan Msg),
clients:   make(map[int]*Client),
}
}

func RunHub() {
for {
select {
case message := <-h.broadcast:
for _, client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client.ID)
}
}
}
}
}

func register(c *Client) {
h.clients[c.ID] = c
}

func unregister(c *Client) {
delete(h.clients, c.ID)
close(c.send)
}

var maxMessageSize = int64(512)
var pongWait = 60 * time.Second

func (c *Client) readPump() {
logx.Errorw("test close writer error", logx.Field("error", "fdfsdfds"))
defer func() {
unregister(c)
c.conn.Close()
}()
c.conn.SetReadLimit(maxMessageSize)
c.conn.SetReadDeadline(time.Now().Add(pongWait))
c.conn.SetPongHandler(func(string) error {
c.conn.SetReadDeadline(time.Now().Add(pongWait))
return nil
})

for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
var req types.WsRequestItem
if err := json.Unmarshal(message, &req); err != nil {
logx.Errorw("unmarshal message when read error", logx.Field("message", string(message)), logx.Field("error", err))
continue
}
h.broadcast <- Msg{
Msg:    req.Message,
FromID: c.ID,
ToIDs:  req.ToID,
Time:   time.Now().Unix(),
}
}
}

var pingPeriod = (pongWait * 9) / 10
var writeWait = 10 * time.Second

func (c *Client) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
w, err := c.conn.NextWriter(websocket.TextMessage)
if err != nil {
logx.Errorw("NextWriterd error", logx.Field("error", err))
continue
}
resp := types.WsResponseItem{
Message: message.Msg,
FromID:  message.FromID,
Time:    message.Time,
}
v, _ := json.Marshal(resp)
w.Write(v)
if err := w.Close(); err != nil {
logx.Errorw("close writer error", logx.Field("error", err))
continue
}
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
logx.Errorw("write ping message error", logx.Field("error", err))
continue
}
}
}
}

修改chat/internal/handler/wshandler.go,更改为如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package handler

import (
"log"
"net/http"

"github.com/gorilla/websocket"
"github.com/zeromicro/go-zero/rest/httpx"

"github.com/nrbackback/zero-chatroom/chat/internal/svc"
"github.com/nrbackback/zero-chatroom/chat/internal/types"
)

var upgrader = websocket.Upgrader{
ReadBufferSize:  1024,
WriteBufferSize: 1024,
}

func WsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.WsConnectRequest
if err := httpx.Parse(r, &req); err != nil {
httpx.Error(w, err)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
if err := conn.WriteMessage(1, []byte("connected")); err != nil {
return
}
c := &Client{
ID:   req.ID,
conn: conn,
send: make(chan Msg),
}
register(c)

go c.writePump()
go c.readPump()
}
}

修改chat/chat.go,更改为如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"flag"
"fmt"

"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"

"github.com/nrbackback/zero-chatroom/chat/internal/config"
"github.com/nrbackback/zero-chatroom/chat/internal/handler"
"github.com/nrbackback/zero-chatroom/chat/internal/svc"
)

var configFile = flag.String("f", "etc/chat-api.yaml", "the config file")

func main() {
flag.Parse()

var c config.Config
conf.MustLoad(*configFile, &c)

ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()

handler.InitHub()
go handler.RunHub()

handler.RegisterHandlers(server, ctx)

fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

测试

WX20221029-151628@2x

WX20221029-151713@2x

项目

github项目地址 https://github.com/nrbackback/zero-chatroom


基于go-zero和websocket的聊天室demo
https://nrbackback.github.io/2022/10/28/基于go-zero和websocket的聊天室demo/
作者
John Doe
发布于
2022年10月28日
许可协议