构建长连接服务器
遇到的问题
通道阻塞:缓冲通道设置了 10 ,消费消息跟不上生产消息,导致通道阻塞
解决方法:增大通道缓冲到 10000,监控程序性能
数据拥塞问题
一个更好的写数据阻塞解决方案:
一般来说,我们开发 websock 会为客户端开启两个协程,一个读,一个写,实现全双工通信。
通常写协程会监听一个channel,来接收其他工作协程的写数据推送,然后依次写入。
如果客户端网慢写入慢,就会造成写任务阻塞,这里我们可以用一个阻塞管道处理:
一个阻塞的 PIPE 管道在读取时是支持阻塞的 - 如果 PIPE 没有数据,程序会处于阻塞状态,
这个功能可以通过 go 基础包 sync 的 Cond 实现(信号量)。
连接的写协程接收到待写入数据后不直接写入客户端,而是写入 PIPE,
单独启动一个协程,负责从 PIPE 拉取任务进行写,
这里就可以在客户端较慢时选择性的写入数据,比如MMO网络游戏,同步延迟可以丢弃部分过期包。
PIPE 实现源码:
package tools
import "sync"
// 不限制大小,添加不发生阻塞,接收阻塞等待
type Pipe struct {
list []interface{} // todo 可以使用 list.List 链表,实现数据优先发送
length int
listGuard sync.Mutex
listCond *sync.Cond
}
// 添加时不会发送阻塞
func (p *Pipe) Add(msg interface{}) {
p.listGuard.Lock()
p.list = append(p.list, msg)
p.length++
p.listGuard.Unlock()
p.listCond.Signal()
}
func (p *Pipe) Reset() {
p.list = p.list[0:0]
p.length = 0
}
// 如果没有数据,发生阻塞
func (p *Pipe) Pick(retList *[]interface{}) (exit bool) {
p.listGuard.Lock()
for len(p.list) == 0 {
p.listCond.Wait()
}
p.listGuard.Unlock()
p.listGuard.Lock()
// 复制出队列
for _, data := range p.list {
if data == nil {
exit = true
break
} else {
*retList = append(*retList, data)
}
}
p.Reset()
p.listGuard.Unlock()
return
}
func NewPipe() *Pipe {
self := &Pipe{}
self.listCond = sync.NewCond(&self.listGuard)
return self
}