构建长连接服务器

遇到的问题

通道阻塞:缓冲通道设置了 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
}