大家好,我是公衆号「線下聚會遊戲」作者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邏輯增加個日志輸出)現在斷開連接配接時,無人房間會自動清除掉!并且下次進入時,也會建立房間,不影響正常使用!
真的沒問題了嗎?
我又繪制了一個圖(以一個房間為例),更加完整:
- User連接配接WebSocket伺服器時,會先啟動
。serveWs goroutine
- 在
中,會執行serveWs goroutine
操作,這一點之前的圖中并沒畫出來。register
- 随後
啟動了serveWs goroutine
和Read goroutine
,并結束自己。Write goroutine