网络编程中的Go语言协程

上图中,假设有N个客户端,那么网络模块就会开2*N个Go协程。
如果有C/C++编程基础的人,可能会感到疑惑,这种网络库模型是否有问题。
这显然受到多线程编程经验的影响了!
协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失。即协程切换时,线程没有被切出去。
代价就是1个协程需要不少内存,至少8K。即通过空间换时间的方式来模拟多线程编程模式
理解Go协程,最简单的方式是,想象每个线程上都有1个协程队列,1个协程要执行阻塞操作了,就把这个协程插到本队列最后,线程没有被切换,且下个协程得到执行。
重点提示:这里的网络Go协程模型,适用于任何多任务阻塞模块
base/gonet
base/gonet库的实现方式,就是上节提到的编程模型。
使用base/gonet的例子可以参见:mope\server\src\base\gonet\examples
也可以直接看mope项目中如何使用base/gonet
项目中网络模块代码分析 - roomserver.go
roomserver.go 中,包含了启动网络模块,以及接受建立网络会话
启动监听端口
func (this *RoomServer) Init() bool { // ... (无关代码略) // 绑定本地端口 port := env.Get("room", "listen") err := this.roomser.Bind(port) if err != nil { glog.Error("[启动] 绑定端口失败") return false } // ... (无关代码略) }接受建立网络会话
func (this *RoomServer) MainLoop() { conn, err := this.roomser.Accept() if err != nil { return } tcptask.NewPlayerTask(conn).Start() }每个会话会开2个go协程
项目中网络模块代码分析 - PlayerTask.go
PlayerTask.go,表示一个网络会话
继承gonet.TcpTask
type PlayerTask struct { gonet.TcpTask // tcp会话 // ... (无关代码略) }网络数据到达事件
func (this *PlayerTask) ParseMsg(data []byte, flag byte) bool { cmd := usercmd.MsgTypeCmd(common.GetCmd(data)) if !this.IsVerified() { return this.LoginVerify(cmd, data, flag) } // ... (无关代码略) if cmd == usercmd.MsgTypeCmd_HeartBeat { this.AsyncSend(data, flag) return true } // ... (无关代码略) return true }该函数会被自动触发,当网络数据到达后
base/gonet特别规定了,网络会话建立后,第一个消息为验证消息,处理完毕后,需要调用gonet.TcpTask.Verify(),否则会被视作验证操作,导致连接被关闭
发送protobuf消息接口
func (this *PlayerTask) SendCmd(cmd usercmd.MsgTypeCmd, msg common.Message) error { data, flag, err := common.EncodeGoCmd(uint16(cmd), msg) if err != nil { glog.Info("[玩家] 发送失败 cmd:", cmd, ",len:", len(data), ",err:", err) return err } this.AsyncSend(data, flag) return nil }
项目中网络模块代码分析 - PlayerTaskMgr.go
PlayerTaskMgr.go,管理客户端网络会话。
如定期检查踢出非活跃连接。
代码很简单,不再细讲。