http://studygolang.com/articles/4350

简介:
本文描述了使用Go语言实现的、适应于Go语言并发模型的一种支持多路复用的网络协议处理框架,并提供了框架的代码实现。作者将这种框架用于网络游戏服务器中的协议处理,但也可用于其他领域。

应用背景:

在网络游戏服务器设计中,一般都会遇到协议多路复用的场景。比如登录服务器和玩家客户端之间有1:N的多个TCP连接;登录服务器和游戏服务器之间是1:1的TCP连接。玩家登录游戏的大致流程是这样的:
  1. 玩家连接登录服务器
  2. 登录服务器向数据库请求玩家数据
  3. 登录服务器获取到玩家数据,把玩家数据转发给游戏服务器进行加载包括创建玩家对象等
  4. 登录服务器获取到加载成功回应后,通知玩家客户端可以进入游戏世界
在3和4中,因为登录服务器和游戏服务器通常只有一个TCP连接,所有玩家数据都是通过这个连接进行传输,所以需要从协议包中区分出是哪个玩家的数据。通常这个区分的依据可以是玩家的角色名,但是也可以更通用一些,用一个数字ID来区分,这样就把协议包的分发处理和协议包中与游戏逻辑有关的内容分离开来。
协议说明:
通常网游的网络协议都是报文的形式,即使底层是使用TCP,也会用一些方法把数据拆分成一个个的报文(本文中称为协议包)。因此,本文也基于这一假设,但是对于具体的协议包格式,本文没有特别限制,只是要求协议包中能够容纳一个32字节的ID。
协议包的处理大概可以分为以下两种类型。其他更复杂的会话可以由以下两种类型组合而成。
  1. 发送一个数据包并等待回应。比如登录服务器等待游戏服务器加载玩家数据的结果通知。
  2. 发送一个数据包,不需要回应。比如游戏服务器加载玩家数据后,给登录服务器发送结果通知。

框架说明:
Go语言是一种支持高并发的编程语言,它支持高并发的方式是大量轻量级的goroutine并发执行。在每个goroutine中的操作基本上都是同步阻塞的,这样可以极大地简化程序逻辑,使得代码清晰易读,容易维护。基于这点,本文实现的框架的调用接口也是使用同步方式的。
  1. 如果一个协议包需要等待回应,就在调用函数上阻塞等待。这个调用的签名为:
    func (p *Connection) Query(data []byte) ([]byte, error)
    注意:data的控制权会转交给框架,因此函数调用后不能修改data的内容。
  2. 如果发送一个协议包是对于接收到的某个协议包的回应,则调用:
    func (p *Connection) Reply(query, answer []byte) error
    注意:answer的控制权会转交给框架,因此函数调用后不能修改answer的内容。
  3. 如果一个协议包不需要回应,就直接调用发送函数:
    func (p *Connection) Write(data []byte) error
    注意:data的控制权会转交给框架,因此函数调用后不能修改data的内容。
  4. 调用者需要实现的接口:
  • Socket。用于协议包的收发。基本上是net.TCPConn的简单封装,在头部加上一个协议包的长度。
  • DataHandler。用于协议处理,即没有通过Query返回的协议包会分发给此接口处理。
  • ErrorHandler。用于错误处理。当断线时,会调用此接口。
  • IdentityHandler。用于读取和设置会话ID。
5. 关于goroutine安全的说明:
ErrorHandler和DataHandler的函数实现中不能直接调用(*Connection).Close,否则会导致死锁。
导出类型、函数和接口:


type Connection
func NewConnection(conn Socket, maxcount int, dh DataHandler, ih IdentityHandler, eh ErrorHandler) *Connection
func (p *Connection) Start()
func (p *Connection) Close()
func (p *Connection) Query(data []byte) (res []byte, err error)
func (p *Connection) Reply(query, answer []byte) error
func (p *Connection) Write(data []byte) errortype Socket interface {Read() ([]byte, error)Write([]byte) errorClose()
}type DataHandler interface {Process([]byte)
}type ErrorHandler interface {OnError(error)
}type IdentityHandler interface {GetIdentity([]byte) uint32SetIdentity([]byte, uint32)
}

完整的代码实现:


package multiplexerimport ("errors""sync""sync/atomic"
)var (ERR_EXIT = errors.New("exit")
)type Socket interface {Read() ([]byte, error)Write([]byte) errorClose()
}
type DataHandler interface {Process([]byte)
}
type ErrorHandler interface {OnError(error)
}
type IdentityHandler interface {GetIdentity([]byte) uint32SetIdentity([]byte, uint32)
}
type Connection struct {conn       Socketwg         sync.WaitGroupmutex      sync.Mutexapplicants map[uint32]chan []bytechexit     chan boolchsend     chan []bytechch       chan chan []bytedh         DataHandlerih         IdentityHandlereh         ErrorHandleridentity   uint32
}func NewConnection(conn Socket, maxcount int, dh DataHandler, ih IdentityHandler, eh ErrorHandler) *Connection {count := maxcountif count < 1024 {count = 1024}chch := make(chan chan []byte, count)for i := 0; i < count; i++ {chch <- make(chan []byte, 1)}return &Connection{conn:       conn,applicants: make(map[uint32]chan []byte, count),chsend:     make(chan []byte, count),chexit:     make(chan bool),chch:       chch,dh:         dh,ih:         ih,eh:         eh,}
}
func (p *Connection) Start() {p.wg.Add(2)go func() {defer p.wg.Done()p.recv()}()go func() {defer p.wg.Done()p.send()}()
}
func (p *Connection) Close() {close(p.chexit)p.conn.Close()p.wg.Wait()
}
func (p *Connection) Query(data []byte) (res []byte, err error) {var ch chan []byteselect {case <-p.chexit:return nil, ERR_EXITcase ch = <-p.chch:defer func() {p.chch <- ch}()}id := p.newIdentity()p.ih.SetIdentity(data, id)p.addApplicant(id, ch)defer func() {if err != nil {p.popApplicant(id)}}()if err := p.Write(data); err != nil {return nil, err}select {case <-p.chexit:return nil, ERR_EXITcase res = <-ch:break}return res, nil
}
func (p *Connection) Reply(query, answer []byte) error {// put back the identity attached to the queryid := p.ih.GetIdentity(query)p.ih.SetIdentity(answer, id)return p.Write(answer)
}
func (p *Connection) Write(data []byte) error {select {case <-p.chexit:return ERR_EXITcase p.chsend <- data:break}return nil
}
func (p *Connection) send() {for {select {case <-p.chexit:returncase data := <-p.chsend:if p.conn.Write(data) != nil {return}}}
}
func (p *Connection) recv() (err error) {defer func() {if err != nil {select {case <-p.chexit:err = nildefault:p.eh.OnError(err)}}}()for {select {case <-p.chexit:return nildefault:break}data, err := p.conn.Read()if err != nil {return err}if id := p.ih.GetIdentity(data); id > 0 {ch, ok := p.popApplicant(id)if ok {ch <- datacontinue}}p.dh.Process(data)}return nil
}
func (p *Connection) newIdentity() uint32 {return atomic.AddUint32(&p.identity, 1)
}
func (p *Connection) addApplicant(identity uint32, ch chan []byte) {p.mutex.Lock()defer p.mutex.Unlock()p.applicants[identity] = ch
}
func (p *Connection) popApplicant(identity uint32) (chan []byte, bool) {p.mutex.Lock()defer p.mutex.Unlock()ch, ok := p.applicants[identity]if !ok {return nil, false}delete(p.applicants, identity)return ch, true
}

本文来自:CSDN博客

感谢作者:abv123456789

查看原文:[Golang]网络游戏协议处理框架

Golang网络游戏协议处理框架相关推荐

  1. Golang实现简单爬虫框架(4)——队列实现并发任务调度

    前言 在上一篇文章<Golang实现简单爬虫框架(3)--简单并发版>中我们实现了一个最简单并发爬虫,调度器为每一个Request创建一个goroutine,每个goroutine往Wor ...

  2. Golang实现简单爬虫框架(5)——项目重构与数据存储

    前言 在上一篇文章<Golang实现简单爬虫框架(4)--队列实现并发任务调度>中,我们使用用队列实现了任务调度,接下来首先对两种并发方式做一个同构,使代码统一.然后添加数据存储模块. 注 ...

  3. 基于《悉尼协议》框架下Java课程案例教学研究

    文章目录 基于<悉尼协议>框架下Java课程案例教学研究 一.Java课程教学存在问题 (一)Java课程目标定位不足 (二)Java课程教学存在的问题 1. 教材内容更新滞后 2. 学习 ...

  4. Go实战--golang中使用echo框架中JSONP(labstack/echo)

    生命不止,继续 go go go !!! 继续,echo web框架,今天就聊一聊JSONP. JSONP 1.什么是JSONP? JSONP (JSON with padding) is used ...

  5. 大型网络游戏服务器的框架设计(一)

    服务器是用来处理高并发的请求,同时能够满足扩展的业务逻辑的需求,最重要的是满足三点:并发性,稳定性,扩展性. 经历过两款上线游戏产品,见识到了游戏行业的杂乱无章,虽然和传统软件行业相比,少了那么些规范 ...

  6. 一,蓝牙协议的框架图解

    一,蓝牙协议的框架 直接上图,不想再去理解一遍,之前的文档做了一些,直接搬运两个图来赋上 BLE蓝牙协议的框架图 BR/EDR蓝牙的框架图:

  7. golang Leaf 游戏服务器框架简介

    Leaf 是一个由 Go 语言(golang)编写的开发效率和执行效率并重的开源游戏服务器框架.Leaf 适用于各类游戏服务器的开发,包括 H5(HTML5)游戏服务器. Leaf 的关注点: 良好的 ...

  8. golang中的gin框架学习

    gin框架中常用方法 gin.H{ } 有这么一行c.JSON(200, gin.H{"message": "use get method"}) 这其中有一个g ...

  9. 浅谈《帧同步网络游戏》之“框架”实现思路

    版权申明: 本文为"优梦创客"原创文章,您可以自由转载,但必须加入完整的版权声明 更多学习资源请加QQ:1517069595获取(企业级性能优化/热更新/Shader特效/服务器/ ...

最新文章

  1. MicroPython支持图形化编辑了:Python Editor带你轻松玩转MicroPython
  2. 2020 操作系统第一天复习(习题总结)
  3. mybatis mapper文件找不到_MyBatis 面试题
  4. 自动驾驶中的滞后碰撞检测(lazy-collision-checking)
  5. win7怎么跳过硬盘自检_DiskGenius 5.2.1发布!增强扇区复制功能,方便坏道硬盘做镜像!...
  6. python3-字符串常用操作
  7. Win7 下安装ubuntu14.04双系统
  8. Struts2 初探
  9. 一位阿里云小哥要感谢“双11”,于是说了一段脱口秀……
  10. 【面试题】-java分布式及微服务面试题汇总
  11. 8421BCD码与十进制之间的转换
  12. 如何增加无人机的飞行时间和升力?
  13. c语言用乘法,c语言口诀(用c语言编写乘法口诀)
  14. wincc做皮带动画_wincc 如何做动画
  15. 对手游渠道商的一些看法
  16. 数据库笔记 NO.1 ------------2020.03.26
  17. 用python模拟登录12306
  18. codeblocks 10.5配置vc2008x64编译器
  19. Android Manager之AssetManager
  20. 随笔-OC获取系统时间,获取绝对时间,获得真实时间

热门文章

  1. java http服务_springboot官方例子中文翻译--RESTful服务启用CORS支持
  2. html标签学习日记之(表格table)
  3. e.target+addEventListener事件委托
  4. 想为自己设置的软件加一个属于自己的图标吗?使用AWT_Swing_图标解决你的问题(源码解析)
  5. java 圆括号,Java圆括号翻转字符串
  6. C# WebService获取天气信息
  7. mysqll索引实验
  8. linux 下的动态库制作 以及在python 中如何调用 c 函数库
  9. 中间件配置文件-nginx
  10. 【李宏毅2020 ML/DL】P24 Semi-supervised