让我们学习如何使用 Redis、WebSocket 和 Go 构建聊天应用程序[第 1 部分]
该WebSocket协议提供双向(服务器和客户端均可交换消息)和全双工(服务器或客户端可同时发送消息)通信通道,使其适用于聊天应用程序等实时场景。已连接的聊天用户(客户端)可以向应用程序(WebSocket服务器)发送消息,也可以彼此交换消息——类似于点对点设置。
在本博客中,我们将探讨如何使用WebSocket和构建一个简单的聊天应用程序Go。该解决方案还将使用Redis(稍后会详细介绍)。
后续博文(第二部分)将演示如何将此应用程序部署到Azure 应用服务,该应用服务将使用虚拟网络集成与 Azure Redis 缓存通信。
你将学到:
- Redis 数据结构 - 此应用程序使用
SET和PUBSUB - 使用
go-redis客户端与 Redis 交互 - 该
gorilla WebSocket库提供了完整且经过测试的 WebSocket 协议实现。 - Azure Cache for Redis是云端托管的 Redis 服务。
为什么选择 Redis?
我们来看一个聊天应用。当用户首次连接时,WebSocket应用(WebSocket服务器)内部会创建一个相应的连接,并将其与特定的应用实例关联起来。WebSocket正是这个连接使得我们可以在用户之间广播聊天消息。我们可以通过运行多个实例来扩展应用(例如,为了应对庞大的用户群)。现在,如果有新用户加入,他们可能会连接到一个新的实例。因此,我们遇到了这样的情况:不同的用户(以及他们各自的WebSocket连接)与不同的实例关联。结果,他们将无法互相交换消息——即使对于我们这个简单的聊天应用来说,这也是不可接受的 😉
Redis是一款功能强大的键值存储设备,支持多种丰富的数据结构,例如List列表、消息流、Set消息队列等等。它的一项特性是允许发布者向 Redis 通道发送消息,订阅者监听这些通道上的消息——两者完全独立且互不耦合。这可以用来解决我们遇到的问题。现在,我们不再仅仅依赖于连接,而是可以使用一个 Redis 服务,每个聊天应用都可以订阅该服务。这样,发送到连接的消息就可以通过 Redis 通道进行传输,确保所有应用实例(以及关联的聊天用户)都能收到这些消息。Sorted SetHashPubSubWebSocketchannelWebSocket
下一节深入代码时会详细介绍这一点。代码可在 Github 上找到。
请注意,除了普通的推送通知之外,您还可以使用Azure SignalR
WebSocket等技术,这些技术允许应用将内容更新推送给已连接的客户端,例如单页 Web 应用或移动应用。这样,客户端无需轮询服务器或提交新的 HTTP 更新请求即可获得更新。
要跟随教程并将此解决方案部署到 Azure,您需要一个Microsoft Azure帐户。如果您还没有帐户,可以免费获取一个!
聊天应用概述
现在来快速浏览一下代码。以下是应用程序的结构:
.
├── Dockerfile
├── chat
│ ├── chat-session.go
│ └── redis.go
├── go.mod
├── go.sum
├── main.go
在 `<script>`标签内main.go,我们注册WebSocket处理程序并启动 Web 服务器——这里使用的只是普通的net/http包。
http.Handle("/chat/", http.HandlerFunc(websocketHandler))
server := http.Server{Addr: ":" + port, Handler: nil}
go func() {
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatal("failed to start server", err)
}
}()
处理WebSocket程序处理聊天用户(也就是WebSocket客户端),并启动新的聊天会话。
func websocketHandler(rw http.ResponseWriter, req *http.Request) {
user := strings.TrimPrefix(req.URL.Path, "/chat/")
peer, err := upgrader.Upgrade(rw, req, nil)
if err != nil {
log.Fatal("websocket conn failed", err)
}
chatSession := chat.NewChatSession(user, peer)
chatSession.Start()
}
A ChatSession(的一部分chat/chat-session.go)代表用户及其对应的WebSocket连接(在服务器端)。
type ChatSession struct {
user string
peer *websocket.Conn
}
当会话启动时,它会启动一个 goroutinegoroutine来接收来自刚刚加入聊天的用户的消息。它通过循环调用 `(from)` 来实现这一点。ReadMessage()如果websocket.Conn用户断开连接(连接关闭)或应用程序关闭(例如使用 ` catch` 命令) for,则此 goroutine会停止运行。总而言之,每个用户都会生成一个单独的 goroutine 来处理其聊天消息。exitsWebSocketctrl+c
func (s *ChatSession) Start() {
...
go func() {
for {
_, msg, err := s.peer.ReadMessage()
if err != nil {
_, ok := err.(*websocket.CloseError)
if ok {
s.disconnect()
}
return
}
SendToChannel(fmt.Sprintf(chat, s.user, string(msg)))
}
}()
一旦收到用户发送的消息(通过连接WebSocket),就会使用该函数将其广播给其他用户。SendToChannel该函数的作用chat/redis.go是将消息发布到 Redispubsub通道。
func SendToChannel(msg string) {
err := client.Publish(channel, msg).Err()
if err != nil {
log.Println("could not publish to channel", err)
}
}
关键在于sub等式中的(订阅者)部分。与为每个已连接的聊天用户创建一个专用 goroutine 的情况不同,我们使用一个single(应用程序范围的)goroutine 来订阅 Redis 通道,接收消息,并通过各自的服务器端连接将其广播给所有用户WebSocket。
func startSubscriber() {
go func() {
sub = client.Subscribe(channel)
messages := sub.Channel()
for message := range messages {
from := strings.Split(message.Payload, ":")[0]
for user, peer := range Peers {
if from != user {
peer.WriteMessage(websocket.TextMessage, []byte(message.Payload))
}
}
}
}()
}
当应用程序实例关闭时,订阅即终止——这反过来又会终止通道
for-range循环,goroutine 也随之退出。
该函数由内部函数startSubscriber调用。该函数首先连接到 Redis,如果连接失败,应用程序将退出。init()redis.goinit()
好了!现在是时候搭建一个 Redis 实例,用来连接我们的聊天后端了。让我们在云端创建一个 Redis 服务器吧!
Azure Redis 缓存设置
Azure Redis 缓存提供对安全、专用的 Redis 缓存的访问,该缓存托管在 Azure 中,Azure 内外的任何应用程序均可访问该缓存。
在本博客中,我们将设置一个 Azure Redis 缓存,Basic该缓存采用单节点层,非常适合开发/测试和非关键工作负载。请注意,您还可以选择其他Standard层Premium,这些层提供不同的功能,例如持久性、集群、异地复制等。
我将使用Azure CLI进行安装。如果您习惯使用浏览器,也可以使用Azure Cloud Shell !
要快速设置 Azure Redis 缓存实例,我们可以使用以下az redis create命令,例如:
az redis create --location westus2 --name chat-redis --resource-group chat-app-group --sku Basic --vm-size c0
请查看“为 Redis 创建 Azure 缓存”以获取分步指南。
完成上述步骤后,您需要获取连接到 Azure Redis 缓存实例所需的信息,例如主机、端口和访问密钥。这也可以使用 CLI 完成,例如:
//host and (SSL) port
az redis show --name chat-redis --resource-group chat-app-group --query [hostName,sslPort] --output tsv
//primary access key
az redis list-keys --name chat-redis --resource-group chat-app-group --query [primaryKey] --output tsv
请查看“获取 Azure Redis 缓存的主机名、端口和密钥”以获取分步指南。
就是这样...
……我们聊聊吧!
为了简化操作,该应用程序以Docker 镜像的形式提供。
首先,设置一些环境变量:
//use port 6380 for SSL
export REDIS_HOST=[redis cache hostname as obtained from CLI]:6380
export REDIS_PASSWORD=[redis cache primary access key as obtained from CLI]
export EXT_PORT=9090
export NAME=chat1
该应用程序内部使用静态端口
8080(用于 Web 服务器)。我们使用指定的外部端口,并将其映射到容器内部的EXT_PORT端口(使用)。8080-p $EXT_PORT:8080
启动 Docker 容器
docker run --name $NAME -e REDIS_HOST=$REDIS_HOST -e REDIS_PASSWORD=$REDIS_PASSWORD -p $EXT_PORT:8080 abhirockzz/redis-chat-go
现在可以加入聊天了!你可以使用任何 WebSocket 客户端。我个人比较喜欢wscat在终端中使用,或者在浏览器中使用 Chrome WebSocket 扩展程序。
wscat我将通过终端演示这一步骤。打开两个独立的终端来模拟不同的用户:
//terminal 1 (user "foo")
wscat -c ws://localhost:9090/chat/foo
//terminal 2 (user "bar")
wscat -c ws://localhost:9090/chat/bar
foo以下是两人之间的聊天示例bar
foo第一个加入的人收到了消息,后来加入的人Welcome foo!也收到了消息。请注意,有人收到了加入的通知,并在离开前交换了几条消息(也收到了离开的通知)。barfoofoobarfoobarbarfoo
EXT_PORT作为练习,您可以启动另一个聊天应用程序实例。使用不同的`name` 和 `container`值启动另一个 Docker 容器,例如:
//use port 6380 for SSL
export REDIS_HOST=[redis cache host name as obtained from CLI]:6380
export REDIS_PASSWORD=[redis cache primary access key as obtained from CLI]
export EXT_PORT=9091
export NAME=chat2
docker run --name $NAME -e REDIS_HOST=$REDIS_HOST -e REDIS_PASSWORD=$REDIS_PASSWORD -p $EXT_PORT:8080 abhirockzz/redis-chat-go
现在连接到端口9091(或您选择的端口)以模拟另一个用户
//user "pi"
wscat -c ws://localhost:9091/chat/pi
由于foo它仍然处于活动状态,因此会收到通知:pi并且foo可以互致问候。
检查 Redis
让我们通过查看 Redis 数据结构来确认一下。你可以使用 `Redis Cache`redis-cli来实现这一点。如果你使用的是 Azure Redis Cache,我建议你使用一个非常方便的基于 Web 的 Redis 控制台。
我们有一个SET(名称chat-users)来存储活跃用户。
SMEMBERS chat-users
您应该看到结果——这意味着用户foo当前bar已连接到聊天应用程序,并且具有关联的活动WebSocket连接。
1) "foo"
2) "bar"
PubSub频道呢?
PUBUSB CHANNELS
由于所有用户都使用同一个通道,您应该从 Redis 服务器获得以下结果:
1) "chat"
本篇博文就到这里。敬请期待第二部分!
如果您觉得这篇文章有用,请别忘了点赞和关注哦🙌 我很想听听您的反馈:欢迎在此留言或在推特上联系我🙏🏻
文章来源:https://dev.to/azure/let-s-learn-how-to-to-build-a-chat-application-with-redis-websocket-and-go-5cck
