我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。
背景
在专栏《Go WebSocket》里,有一些前置文章:
第一篇文章:《为什么我选用Go重构Python版本的WebSocket服务?》,介绍了我的目标。
第二篇文章:《你的第一个Go WebSocket服务: echo server》,介绍了一下怎么写一个WebSocket server。
第三篇文章:《单房间的聊天室》,介绍了如何实现一个单房间的聊天室。
第四篇文章:《多房间的聊天室(一)思考篇》,介绍了实现一个多房间的聊天室的思路。
第五篇文章:《多房间的聊天室(二)代码实现》,介绍了实现一个多房间的聊天室的代码。
如果你没阅读上面的文章,一定要先看一下,因为这篇文章更复杂,如果你不弄懂上面几篇,这篇可能跟不上节奏噢。
上篇文章我们提到:
现在房间数只会源源不断的增多,house这个map会越来越大,终将造成内存不足,这不是一个好事情。
所以我们后续需要加一个优化:当最后一个客户端断开连接时,回收(删除)这个房间。
今天,我们实现它。
思路
有一个重要的问题需要想清楚:
是在哪个地方执行这个【回收】操作?是哪个goroutine?什么时机?若有多个地方,有没有竞争关系?
回顾一下之前绘制的图:
可以发现:每个客户端连接会常驻2个goroutine:Read和Write。其中Read重要的职责就是unregister
,这点我之前在《单房间的聊天室》强调过。
unregister
就是把客户端连接从hub中删除掉。这个时候,我们就可以检查一下hub内是否还有其它客户端,若无,则删除。
注意,unregister
只是个channel,真正的处理逻辑是写在goroutine中的,是哪个gotoutine负责接收unregister
并执行逻辑呢?就是Hub
。所以我们需要修改Hub
代码。
直接看源码
多房间聊天室案例代码的地址:github.com/HullQin/go-websocket-examples
在chat-multi-rooms
文件夹中,文章可配套commit记录阅读:
- delete empty room 就是清理无人房间的逻辑。
开始开发
我们以《多房间的聊天室(二)代码实现》的代码为基础,做改动。
关注hub goroutine
的代码:
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
可以看到case client := <-h.unregister:
这段代码,就是处理unregister
逻辑的。
这里删除了hub
中的对应客户端。删除时,我们检查一下h.clients
是否为空即可,若为空,把hub
从house
(房间集合)删掉,再结束这个hub goroutine
即可。
但是,有个问题,这里我们要在house
中删掉,是需要知道key的,key是roomId
,最好从hub的属性中获得,目前还不支持,所以还需要给hub增加一个roomId
属性,方便做删除。
if len(h.clients) == 0 {
delete(house, h.roomId)
break
}
下面,我们增加roomId
属性:
type Hub struct {
// Identity of room.
roomId string
// Registered clients.
clients map[*Client]bool
// Inbound messages from the clients.
broadcast chan []byte
// Register requests from the clients.
register chan *Client
// Unregister requests from clients.
unregister chan *Client
}
func newHub(roomId string) *Hub {
return &Hub{
roomId: roomId,
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
}
此外,还需要修改main.go
,新建hub时,传入roomId
:
测试一下,大功告成!(可以在delete逻辑增加个日志输出)现在断开连接时,无人房间会自动清除掉!并且下次进入时,也会新建房间,不影响正常使用!
真的没问题了吗?
我又绘制了一个图(以一个房间为例),更加完整:
我用连线,表明了goroutine的启动关系:
- User连接WebSocket服务器时,会先启动
serveWs goroutine
。 - 在
serveWs goroutine
中,会执行register
操作,这一点之前的图中并没画出来。 - 随后
serveWs goroutine
启动了Read goroutine
和Write goroutine
,并结束自己。
这里真的是完美方案不会出错吗?留个悬念,我们下篇文章,继续讲解。
写在最后
我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。