当你需要从服务器端立即接收客户端更新时,WebSocket是一种很好的通信协议。如果没有它,你就必须执行HTTP轮询,这有很多缺点。其中一个主要缺点是轮询,特别是当你在服务器上创建了巨大的负载时。使用HTTP时,你没有一种从服务器端发起请求的机制。
你可以使用WebSockets创建聊天、新闻源、游戏等。本文将描述如何使用WebSockets、Go和React创建一个简单的Web信使。
后端技术栈
我使用Go 1.19和Gin构建Web服务器。为了启用WebSocket API,我选择了Gorilla WebSocket。
前端技术栈
我决定不在前端上花费太多时间,所以它看起来很丑。我使用TypeScript与React和Material UI一起使用。因此,如果你熟悉这些技术,你可以完成前端部分,使其看起来更美观。另外,我使用了react-use-websocket包,以实现与React兼容的WebSocket集成。
服务器端代码
我想描述一下最有趣的部分。我想从服务器端的WebSocketClient
实现开始。客户端负责简化WebSocket的工作。接口声明了以下方法:
Id()
— 返回WebSocket连接的唯一标识符的方法Launch(ctx context.Context)
— 启动客户端,以便开始侦听新消息Write(m model.WebSocketMessage)
— 将消息m
发送回客户端的方法Close()
— 关闭WebSocket连接的方法Listen()
— 返回具有传入消息的通道的方法Done()
— 返回一个通道,当工作完成时关闭(WebSocket连接关闭或应该关闭)Error()
— 返回具有在WebSocket侦听期间发生的错误的通道的方法
最有趣的方法是Launch
。这是它的样子:
func (c *WebSocketClient) Launch(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
c.cancel = cancel
go func() {
go c.read(ctx)
go c.ping(ctx)
}()
}
它启动了一个带有两个内部goroutine的goroutine:
read goroutine
负责侦听传入消息。它将它们发布到通道中(通道由Listen
方法返回)。当上下文完成或读操作返回错误时,goroutine完成:
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
c.errChan <- err
return
}
c.inChan <- message
}
ping goroutine
负责发送定期ping。当上下文完成时,goroutine完成:
for {
select {
case <-ctx.Done():
return
case <-time.After(pingPeriod):
c.write(websocket.PingMessage, []byte{})
}
}
这两个内部goroutine都由传递给Launch
方法的上下文控制。该上下文被包装在另一个取消上下文中。如果read goroutine
出现错误完成,它会取消取消ping goroutine
的上下文(反之亦然)。在这种情况下,WebSocket连接将关闭并返回到上下文ctx
,在那里你可以通过取消它来关闭WebSocket连接。
方法Write
和write
用于发送消息。公共方法用于发送客户端消息。私有方法发送服务消息(ping、关闭连接)。它们都使用互斥锁锁定,以避免并发消息发送。
让我们切换到负责WebSocket连接的Gin处理程序——方法WebSocketConnect
:
func WebSocketConnect(pool *WebSocketPool, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := NewWebSocketClient(conn, pool)
pool.Add(client)
defer client.Close()
pool.MemberJoin(client)
client.Launch(r.Context())
for {
select {
case message := <-client.Listen():
pool.HandleMessage(client, message)
case err := <-client.Error():
log.Println(err)
return
case <-client.Done():
pool.MemberLeave(client)
return
}
}
}
它切换到WebSocket协议,并启动了带有WebSocketClient
的goroutine:
client := NewWebSocketClient(conn, pool)
pool.Add(client)
defer client.Close()
pool.MemberJoin(client)
client.Launch(r.Context())
该方法创建了一个新的WebSocketClient
,并将该客户端添加到客户端池中。然后,它使用MemberJoin
方法广播MEMBER_JOIN
消息。在select
语句中,该方法侦听传入消息、客户端错误和完成信号。
传入消息处理取决于消息类型。这段代码处理的唯一消息类型是MESSAGE
。客户端发送新消息时,此消息被初始化。以下是Message
处理程序代码:
case model.MESSAGE:
message := model.Message{}
if err := json.Unmarshal(data, &message); err != nil {
log.Println(err)
return
}
pool.Broadcast(client, message)
该消息被广播到除发送者以外的每个WebSocket客户端。
当接收到完成信号时,广播MEMBER_LEAVE
消息,并从池中删除并关闭客户端(startClient
方法的defer
部分)。以下是代码:
pool.MemberLeave(client)
客户端代码
UI中最有趣的部分是ChatContext
组件,它看起来像这样:
function ChatContextProvider({ children }: Props) {
const { sendMessage, lastMessage } = useWebSocket(SERVER_WS_URL, {
shouldReconnect: () => true,
reconnectAttempts: 10,
});
const [chat, setChat] = useState<ChatState>({
messages: [],
members: [],
});
useEffect(() => {
if (!lastMessage) return;
const message = JSON.parse(lastMessage.data) as WebSocketMessage;
switch (message.type) {
case MESSAGE:
setChat((chat) => {
return { ...chat, messages: [...chat.messages, message] };
});
break;
case MEMBER_JOIN:
setChat((chat) => {
return {
...chat,
members: [...chat.members, message.data],
};
});
break;
case MEMBER_LEAVE:
setChat((chat) => {
return {
...chat,
members: chat.members.filter(
(m) => m.id !== message.data.id
),
};
});
break;
}
}, [lastMessage]);
return (
<ChatContext.Provider value={{ chat, sendMessage }}>
{children}
</ChatContext.Provider>
);
}
这是一个React组件,它为其他组件提供上下文。使用react-use-websocket
包中的useWebSocket
方法打开WebSocket连接。使用两个属性时,它返回以下内容:
sendMessage
— 用于通过WebSocket发送消息lastMessage
— 返回最新接收到的消息
后者在以下效果中使用。根据消息类型,将新消息添加到聊天中。它看起来像这样:
聊天
其他组件使用上下文来呈现消息(组件Messages
)或发送消息(组件TextInput
)。
应用程序代码
你可以在下一个存储库中找到应用程序代码:
GitHub — misikdmitriy/go-ws-api
结论
正如你所看到的,使用此Web聊天来改进客户体验需要很多工作。该应用程序仅用于演示WebSocket协议的功能以及如何在服务器端使用Go和在客户端端使用React实现它。它是一种提高整体UX并减少服务器负载的强大工具。
资源
GitHub — gorilla/websocket: A fast, well-tested and widely used WebSocket implementation for Go.
译自:https://betterprogramming.pub/building-web-chat-with-go-and-websockets-312f459c001a
评论(0)