我的理解是为了保证消息的顺序性和防止老消息饿死
这段代码的设计意图,本质上是两个字:
补发
它把消息队列分成两层:
ChatServer.Transmit:全局主队列,服务端真正消费这个队列c.SendTo:当前客户端自己的“积压队列”
为什么要先用 for 把 SendTo 里的旧消息往 Transmit 搬,再处理这次新消息?因为作者想保证:
旧消息优先于新消息
不然会出现这种情况:
- 之前因为
Transmit满了,客户端有 3 条旧消息堆在SendTo - 现在用户又发来一条新消息
- 如果不先清空
SendTo,而是直接把新消息塞进Transmit - 那么新消息就可能比旧消息更早被服务端处理,顺序就乱了
所以这一段:
for len(ChatServer.Transmit) < constants.CHANNEL_SIZE && len(c.SendTo) > 0 {
sendToMessage := <-c.SendTo
ChatServer.SendMessageToTransmit(sendToMessage)
}意思是:
- 只要全局主队列还有空位
- 并且这个客户端本地还有积压消息
- 就优先把旧消息补进主队列
这一步做完之后:
- 如果
SendTo已经空了,说明旧账还完了 - 那当前这条新消息就可以直接进
Transmit
也就是这段:
if len(ChatServer.Transmit) < constants.CHANNEL_SIZE {
ChatServer.SendMessageToTransmit(jsonMessage)
}如果这时候 Transmit 还是满的,说明主队列实在塞不进去,那当前新消息只能先暂存在这个客户端自己的 SendTo:
else if len(c.SendTo) < constants.CHANNEL_SIZE {
c.SendTo <- jsonMessage
}所以整个逻辑顺序是:
- 先补发旧消息
- 再投递当前新消息
- 如果主队列满了,就退而求其次,先存在本地队列
- 两边都满了,才报发送失败
你可以把它理解成:
Transmit是高速公路主路SendTo是匝道等待区
规则是:
- 匝道里已经排队的车先并回主路
- 主路有空位时,新来的车直接上主路
- 主路堵死了,新车先去匝道排队
- 匝道也堵死了,就不让进了
不过这段代码有个明显问题:
SendTo 里的旧消息,只有当这个客户端再次发送新消息时,才会触发补发。
也就是说,如果一个用户有消息堆在 SendTo,然后他不再发消息了,这些积压消息可能会一直留在那里。这也是这套写法不太合理的地方。