简单,方便,高效的跨平台服务器网络库
Go Protocol Buffer Batchfile
Permalink
Failed to load latest commit information.
benchmark 修复io编译, 更新性能测试数据 Jan 8, 2017
db 增加: dispatcher清理回调接口 Jan 17, 2017
proto 添加: close, connect, accept的错误理由 Jan 19, 2017
protoc-gen-msg 简化rpc接口, 无需再调用install系列 Sep 27, 2016
rpc 调整rpc, EventDispatcher以适配热更新需求 Jan 18, 2017
sample Merge branch 'master' of https://github.com/davyxu/cellnet Jan 19, 2017
socket Merge branch 'master' of https://github.com/davyxu/cellnet Jan 19, 2017
util 消息id值修改为大小写敏感, golang/protobuf升级到最新版,重新生成protoc-gen-msg并重新生成proto代码 Jul 18, 2016
.gitignore 消息id生成, 调整注册消息接口 Feb 17, 2016
CHANGES.md V2版, 涉及大量的API变化及更新, 详细查看CHANGES.md. 老版本参见v1分支 Jan 7, 2017
LICENSE 标记授权类型 Jan 21, 2016
README.md 增加:根据id查消息名接口.封包解析错误放出详细封包内容 Jan 12, 2017
dispatcher.go 调整rpc, EventDispatcher以适配热更新需求 Jan 18, 2017
dispatcher_test.go 调整rpc, EventDispatcher以适配热更新需求 Jan 18, 2017
log.go 消息id生成, 调整注册消息接口 Feb 17, 2016
msgmeta.go 调整: BuildPacket时, 用名字进行meta查找. 避免go1.8 plugin中type不一致问题 Jan 18, 2017
packet.go 调整: BuildPacket时, 用名字进行meta查找. 避免go1.8 plugin中type不一致问题 Jan 18, 2017
peer.go V2版, 涉及大量的API变化及更新, 详细查看CHANGES.md. 老版本参见v1分支 Jan 7, 2017
queue.go V2版, 涉及大量的API变化及更新, 详细查看CHANGES.md. 老版本参见v1分支 Jan 7, 2017
session.go 网关实现采用pb转发, 简化结构,提高传输效率 Oct 6, 2015
timer.go V2版, 涉及大量的API变化及更新, 详细查看CHANGES.md. 老版本参见v1分支 Jan 7, 2017

README.md

cellnet

简单,方便,高效的跨平台服务器网络库

特性

异步单线程多进程架构

  • 无需处理繁琐的多线程安全问题

  • 底层IO仍然使用goroutine进行处理, 保证IO吞吐率

  • 发送时自动合并封包(性能效果决定于实际请求和发送比例)

数据协议

  • 封包类型采用Type-Length-Value的私有tcp封包, 自带序列号防御简单的封包复制

  • 消息统一使用Protobuf格式进行通信, 有自定义需求可以修改实现

  • 自动生成消息ID

RPC

  • 异步远程过程调用

日志

  • 分级日志

  • 可以方便的通过日志查看收发消息(Protobuf)的每一个字段消息

第三方库依赖

  • github.com/golang/protobuf/proto

  • github.com/davyxu/golog

  • gopkg.in/mgo.v2

性能测试

命令行: go test -v github.com/davyxu/cellnet/benchmark/io

平台: Windows 7 x64/CentOS 6.5 x64

测试用例: localhost 1000连接 同时对服务器进行实时PingPong测试

配置1: i7 6700 3.4GHz 8核

QPS: 12.5w

配置2: i5 4590 3.3GHz 4核

QPS: 10.1w

例子

Echo

func server() {

    queue := cellnet.NewEventQueue()

    evd := socket.NewAcceptor(queue).Start("127.0.0.1:7201")

    socket.RegisterMessage(evd, "gamedef.TestEchoACK", func(content interface{}, ses cellnet.Session) {
        msg := content.(*gamedef.TestEchoACK)

        log.Debugln("server recv:", msg.String())

        ses.Send(&gamedef.TestEchoACK{
            Content: msg.String(),
        })

    })

    queue.StartLoop()

}

func client() {

    queue := cellnet.NewEventQueue()

    evd := socket.NewConnector(queue).Start("127.0.0.1:7201")

    socket.RegisterMessage(evd, "gamedef.TestEchoACK", func(content interface{}, ses cellnet.Session) {
        msg := content.(*gamedef.TestEchoACK)

        log.Debugln("client recv:", msg.String())

        signal.Done(1)
    })

    socket.RegisterMessage(evd, "gamedef.SessionConnected", func(content interface{}, ses cellnet.Session) {

        ses.Send(&gamedef.TestEchoACK{
            Content: "hello",
        })

    })

    queue.StartLoop()
}

文件夹功能

benchmark\ 性能测试用例

db\ db封装

proto\ cellnet内部的proto

protoc-gen-msg\ 消息id生成

rpc\ 异步远程过程调用封装

socket\ 套接字,拆包等封装

sample\ 测试用例/例子

close\      发送消息后保证消息送达后再断开连接

echo\       常见的pingpong测试, 最简单的例子

mgo\        mongodb异步读取例子

rpc\        异步远程过程调用

timer\      异步计时器

util\ 工具库

FAQ

问: 为什么接收消息回调必须需要手动转换类型, 例如: msg := content.(*gamedef.TestEchoACK), 而不是参数上写成参数类型?

答: cellnet这么设计是考虑到可以将参数进行多层传递, 如果写成不同消息类型, 传递就麻烦很多

这里鼓励消息注册者可以进行消息注册函数的封装, 在网关里, 就对socket.RegisterSessionMessage进行封装

func RegisterMessage(msgName string, userHandler func(interface{}, cellnet.Session, int64)) {

    msgMeta := cellnet.MessageMetaByName(msgName)

    if msgMeta == nil {
        log.Errorf("message register failed, %s", msgName)
        return
    }

    for _, conn := range routerConnArray {

        conn.RegisterCallback(msgMeta.ID, func(data interface{}) {

            if ev, ok := data.(*relayEvent); ok {

                rawMsg, err := cellnet.ParsePacket(ev.Packet, msgMeta.Type)

                if err != nil {
                    log.Errorln("unmarshaling error:\n", err)
                    return
                }

                msgContent := rawMsg.(interface {
                    String() string
                }).String()             

                userHandler(rawMsg, ev.Ses, ev.ClientID)

            }

        })
    }

}

再来一个外层封装

func RegisterExternMessage(msgName string, userHandler func(interface{}, *Player)) {

    backend.RegisterMessage(msgName, func(content interface{}, routerSes cellnet.Session, clientid int64) {

        if player, ok := PlayerByID[clientid]; ok {

            userHandler(content, player)
        }
    })

}

版本历史

2017.1 v2版本 详细请查看

2015.8 v1版本

Wiki

https://github.com/davyxu/cellnet/wiki

这里有文档和架构,设计解析

贡献者

欢迎提供dev分支的pull request

bug请直接通过issue提交

凡提交代码和建议, bug的童鞋, 均会在下列贡献者名单者出现

viwii(viwii@sina.cn), 提供一个可能造成死锁的bug

IronsDu(duzhongwei@qq.com), 大幅度性能优化

Chris Lonng(chris@lonng.org), 提供一个最大封包约束造成服务器间连接断开的bug

备注

感觉不错请star, 谢谢!

博客: http://www.cppblog.com/sunicdavy

知乎: http://www.zhihu.com/people/xu-bo-62-87

提交bug及特性: https://github.com/davyxu/cellnet/issues

贡献代码: https://github.com/davyxu/cellnet/pulls