Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。
网络游戏
要成功的运营一款网游,很大程度上依赖于玩家自发形成的社区。只有玩家自发形成一个稳定的生态系统,游戏才能持续下去,避免鬼城的出现。而这就需要多次大量导入用户,在同时在线用户量达到某个临界点的时候,才有可能完成。因此,多人同时在线十分有必要。
网络游戏程序特点
网游的常见玩法,除了排行榜这类统计和数据汇总的功能外,基本没有需要大量CPU时间的应用。以前的项目里,即时战斗产生的各种伤害计算对CPU的消耗也不大。玩家要完成一次操作,需要通过客户端-服务器端-客户端这样一个来回,为了获得高响应速度,满足玩家体验,服务器端的处理也不能占用太多时间。所以,每次请求对应的CPU占用是比较小的。
网络IO与磁盘IO
网络IO,这里主要分析游戏逻辑的IO。游戏逻辑的IO跟CPU占用的情况相似,每次请求的字节数很小,但由于多人同时在线,因此并发数相当高。另外,地图信息的广播也会带来比较频繁的网络通信。
磁盘IO方面,主要是游戏数据的保存。采用不同的数据库,会有比较大的区别。以前的项目里,就经历了从MySQL转向MongoDB这种内存数据库的过程,磁盘IO不再是瓶颈。总体来说,还是用内存做一级缓冲,避免大量小数据块读写的方案。
Golang服务器开发
针对网游的这些特点,golang的语言特性十分适合开发游戏服务器端,go语言提供goroutine机制作为原生的并发机制,每个goroutine所需的内存很少,实际应用中可以启动大量的goroutine对并发连接进行响应,遇到IO阻塞的时候,调度器就会自动切换到另一个goroutine执行,保证CPU不会因为IO而发生等待,就不需要利用多进程来榨取多核机器的性能了
每个玩家由自己的协程提供服务
// 网络接口
type IAgent interface {
OnMessage
}
// 提供Message,序列化&反序列
type IMessage interface{
}
// 基于携程的Session封装
type Agent struct {
recvChan chan IMessage
sendChan chan IMessage
}
func (agent *Agent) recv() {
// IO recv
// recvChan<-IMessage
}
func (agent*Agent) send() {
//msg := <- sendChan
// IO send
}
func (agent *Agent) Send(msg IMessage) {
// sendChan <- msg(
}
// 玩家Session网络
type Player struct {
Agent
}
func (player* Player) OnMessage(){
}
通过上面的代码模型实现了一个单独玩家IO对象,这里也体现了一个chan的用法, go语言提供的这种协程间通信机制,十分优雅地揭示了协程通信的本质,避免了以往锁的显式使用带给程序员的心理负担,确是一大优势
GC问题
go语言提供的gc机制,以及对指针的保护式使用,可以大大减轻程序员的开发压力,提高开发效率,同时处理不好对象的管理将对影响程序的执行效率,GC的过程是很复杂的耗时的,网络游戏通信是非常频繁的而且基本都是小包传输,这个时候网络层就会出现不停New跟GC,可以通过golang的sync.Pool来管理对象
type Buffer struct {
Data []byte // buff
bsize int // 大小
refcnt int32 // 引用次数
}
type bufCacheInfo struct {
maxbody int
pool *sync.Pool // 没错就是golang自带的Pool来管理对象
}
func newBuffer(sz int) *Buffer {
m := &Buffer{}
m.Data = make([]byte, sz)
m.bsize = sz
return m
}
// 放入不同大小的buff,64,128...
var bufCache = []bufCacheInfo{
{
maxbody: 64,
pool: &sync.Pool{
New: func() interface{} {
return newBuffer(64)
},
},
}
}
// 使用
func NewBuffer(sz int) *Buffer {
var buf *Buffer
for i := range bufCache {
if sz < bufCache[i].maxbody {
buf = bufCache[i].pool.Get().(*Buffer)
break
}
}
}