Git Product home page Git Product logo

cellnet's Introduction

cellnetlogo

Build Status MIT licensed GoDoc

cellnet是一个组件化、高扩展性、高性能的开源服务器网络库

使用领域

cellnet经过多个版本的迭代,无论是作为初学者学习的范例,还是作为私用、商用项目的基础构建乃至核心技术层已经在业内广受了解及使用。

主要使用领域:

  • 游戏服务器

    方便定制私有协议,快速构建逻辑服务器、网关服务器、服务器间互联互通、对接第三方SDK、转换编码协议等

  • ARM设备

    设备间网络通讯

  • 证券软件

    内部RPC

特性 (Features)

传输协议支持

  • TCP

    TCP连接器的重连,侦听器的优雅重启。

  • UDP

    纯UDP裸包收发

  • HTTP(测试中)

    侦听器的优雅重启, 支持json及form的收发及封装。编写游戏服务器时,不再需要使用第三方HTTP服务器对接SDK。

    注: 如需要对接大规模网页, 请使用第三方专业网络库,如(https://github.com/gin-gonic/gin), cellnet的http支持主要目的在用统一的地址及peer管理

  • WebSocket

    采用(github.com/gorilla/websocket)实现

编码(Codec)

  • cellnet内建支持以下数据编码:

    可以通过codec包自行添加新的编码格式

  • 支持混合编码收发

    无需改动代码,只需调整消息注册方式,即可达成运行期同时收发不同编码的封包

    • 与其他语言编写的服务器使用protobuf

    • 与web服务器使用json通信

    • 与Unity3D(C#)使用ProtoPlus(github.com/davyxu/protoplus)协议通信

    优点:

    • 享受不同通信协议格式的优点,避免缺点。如Protobuf适合服务器间的传输,封包小,适应面广。

    • 私有的二进制协议方便加密和客户端处理,低GC和实现复杂度。

队列实现各种单线程/多线程,异步/同步业务处理模型

使用队列消息处理结构,方便实现以下效果:

  • 单线程异步逻辑,适用于MMORPG复杂交互,免加锁处理共享数据。

  • 多线程同步逻辑,适用于机器人逻辑,每个机器人使用独立的goroutine收发处理,机器人间互不干扰。

  • 多线程并发逻辑,适用于网关,消息转发,HTTP服务器,每条消息处理都会在完全并发下。

远程过程调用(RPC)

  • 支持同步RPC,适用于后台服务器向其他服务器请求数据后再顺处理事务。

  • 支持异步RPC,适用于单线程服务器逻辑。

消息日志

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

cellnetlogo

获取+编译

编译和使用cellnet,请下载Go1.10以上版本

  go get -u -v github.com/davyxu/cellnet

  go get -u -v github.com/davyxu/golog

  go get -u -v github.com/davyxu/goobjfmt

  go get -u -v github.com/davyxu/protoplus

第三方库

cellnet 使用Protobuf时,需要使用附带的pb插件生成一个绑定代码,下面的链接可以处理这个问题 Google Protobuf 编码安装

WebSocket实现

架构

cellnet架构层次由如下图所示:

architecture

cellnet对Processor和Peer进行高度抽象,用户可以从这两个层面横向扩展以满足不同的网络封装需求

红色框内的部分为cellnet实现部分

消息处理流程

cellnet的消息收发处理流程如下图所示:

procflow

本图对应的接口为cellnet/processor.go

样例

const peerAddress = "127.0.0.1:17701"

// 服务器逻辑
func server() {

    // 创建服务器的事件队列,所有的消息,事件都会被投入这个队列处理
	queue := cellnet.NewEventQueue()

    // 创建一个服务器的接受器(Acceptor),接受客户端的连接
	peerIns := peer.NewGenericPeer("tcp.Acceptor", "server", peerAddress, queue)

    // 将接受器Peer与tcp.ltv的处理器绑定,并设置事件处理回调
    // tcp.ltv处理器负责处理消息收发,使用私有的封包格式以及日志,RPC等处理
	proc.BindProcessorHandler(peerIns, "tcp.ltv", func(ev cellnet.Event) {

        // 处理Peer收到的各种事件
		switch msg := ev.Message().(type) {
		case *cellnet.SessionAccepted: // 接受一个连接
			fmt.Println("server accepted")
		case *TestEchoACK: // 收到连接发送的消息

			fmt.Printf("server recv %+v\n", msg)

            // 发送回应消息
			ev.Session().Send(&TestEchoACK{
				Msg:   msg.Msg,
				Value: msg.Value,
			})

		case *cellnet.SessionClosed: // 会话连接断开
			fmt.Println("session closed: ", ev.Session().ID())
		}

	})

    // 启动Peer,服务器开始侦听
	peerIns.Start()

    // 开启事件队列,开始处理事件,此函数不阻塞
	queue.StartLoop()
}

// 模拟客户端逻辑
func client() {

    // 例子专用的完成标记
	done := make(chan struct{})

    // 创建客户端的事件处理队列
	queue := cellnet.NewEventQueue()

    // 创建客户端的连接器
	peerIns := peer.NewGenericPeer("tcp.Connector", "client", peerAddress, queue)

    // 将客户端连接器Peer与tcp.ltv处理器绑定,并设置接收事件回调
	proc.BindProcessorHandler(peerIns, "tcp.ltv", func(ev cellnet.Event) {

		switch msg := ev.Message().(type) {
		case *cellnet.SessionConnected: // 已经连接上
			fmt.Println("client connected")
			ev.Session().Send(&TestEchoACK{
				Msg:   "hello",
				Value: 1234,
			})
		case *TestEchoACK: //收到服务器发送的消息

			fmt.Printf("client recv %+v\n", msg)

			// 完成操作
			done <- struct{}{}

		case *cellnet.SessionClosed:
			fmt.Println("client closed")
		}
	})

    // 开启客户端Peer
	peerIns.Start()

    // 开启客户端队列处理
	queue.StartLoop()

	// 等待客户端收到消息
	<-done
}

目录功能

目录及功能一览

运行聊天例子

运行 服务器

cd examples/chat/server

go run main.go

运行 客户端

cd examples/chat/client

go run main.go

随后, 在命令行中输入hello后打回车, 就可以看到服务器返回


sid1 say: hello

基本概念及使用说明

理解下面链接中的概念,可以迅速使用cellnet做基本的网络通讯及消息处理

扩展及定制

若cellnet内建的Peer, Codec及Processor流程不能满足你的需求,可以阅读下面链接内容,添加并扩展cellnet功能

FAQ

常见问题及回答

这里应该有你想知道的答案

贡献者

按贡献时间排序,越靠前表示越新的贡献

superikw(https://github.com/superikw), 在v3中测试出一个websocket接口并发发送问题,wss支持,修复会话管理。

bruce.hu(https://github.com/hxdhero), 在v3中测试出一个竞态冲突的bug

M4tou(https://github.com/mutousay), 在v3中协助解决RPC异步超时回调处理

chuan.li(https://github.com/blade-226), 在v3中提供一个没有在io线程编码的bug

Chris Lonng(https://github.com/lonnng), 在v3中提供一个最大封包约束造成服务器间连接断开的bug

IronsDu(https://github.com/IronsDu), 在v2中大幅度性能优化

viwii([email protected]), 在v2中,提供一个可能造成死锁的bug

版本历史

2018.5 v4版本 详细请查看

2017.8 v3版本 详细请查看

2017.1 v2版本 详细请查看

2015.8 v1版本

备注

感觉不错请star, 谢谢!

知乎: http://www.zhihu.com/people/sunicdavy

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

cellnet's People

Contributors

blade-226 avatar bwangelme avatar davyxu avatar dependabot[bot] avatar haraldnordgren avatar ironsdu avatar jeremy1108 avatar liangjiangui avatar pal301x avatar smilefisher avatar superikw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cellnet's Issues

建议工程的描述加上 server framework关键字

因为这几天在不断搜索golang相关的游戏服务器技术,但却很难搜到cellnet,是因为一般人都是搜的 game, server framework这几个关键字。。而出来的都是些几百k的项目。 很少有人会去搜索 network library吧~ 所以只是个建议,让更多人发现cellnet.

TCP 性能问题

func (self *tcpSession) sendLoop() {

tcp里面发送数据能一次多发几个包吗?游戏里面很多小包,这样服务于服务之间性能很低,日志可以在这个循环里面输出。

func WriteFull(writer io.Writer, p []byte) error {

这个没必要for ,以golang的尿性,再大的包都是一次性发送完成才返回。如果实际发送字节小于[]byte数组的长度,那基本上socket已经异常了,应该执行Close(). 发送缓冲区满了会阻塞在Write()

还有,tcp的设置一下发送和接收超时啊。这个东西没得的话,阻塞了都不知道咋回事

为啥不用io.ReadAtLeast ,这样不会粘包么

pktstream.go里
Read函数部分
直接readFull有点不靠谱吧,这样封包没发全会直接断开的吧,tcp搞的和udp一样一个包一个包发有什么特殊含义么。
主要我用websocket发送消息的时候,send我不知道是不是writeFull....内部实现又看不到。
用ReadAtLeast不就没啥问题了么。

concurrent write to websocket connection

hi, davyxu

我在使用 cellnet 做 websocket 服务的时候,发现 event.send 会出现写并发的情况。
例如,在 websocket 例子中复制发送的内容几行:
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger1"})
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger2"})
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger3"})
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger4"})
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger5"})
触发会报 concurrent write to websocket connection.

另外,目前 websocket 链接接收的是文本,以后会考虑加入二进制,支持 protobuf 呢?

cellnet 很棒:)

回调地狱 Callback Hell

cellnet 是回调模式。如果用cellnet 实现逻辑服务器,一般需要访问其他服务(例如 查询好友信息, 锁服务端等) 。 这样很容易掉入Callback Hell。 请问,实际的项目中,你是如何解决这个问题的?

这一块不是很能看懂,为什么要执行两次DecodeMessage

func (self *DecodePacketHandler) Call(ev *Event) {

	var err error
	ev.Msg, err = DecodeMessage(ev.MsgID, ev.Data)

	r := errToResult(err)
	if r != Result_OK {
		ev.Msg, _ = DecodeMessage(ev.MsgID, ev.Data)

		ev.SetResult(r)
	}

}

这一块不是很能看懂,为什么要执行两次DecodeMessage, 望解答

relay功能?

tcp 里面支持

// 带有RPC和relay功能
type MsgHooker struct {
}

请问什么是relay? 适用于怎么样的场景?

Send的问题

const sendTotalTryCount = 100

func (self *ltvStream) Flush() error {

	var err error
	for tryTimes := 0; tryTimes < sendTotalTryCount; tryTimes++ {

		err = self.outputWriter.Flush()

		// 如果没写完, flush底层会将没发完的buff准备好, 我们只需要重新调一次flush
		if err != io.ErrShortWrite {
			break
		}
	}

	return err
}

假设要发送唯一一个非常大的包,而接收端接收非常缓慢,导致tryTimes超过sendTotalTryCount

这种情况下,是否会出现sendThtread永远阻塞在BeginPick上,而对端永远接收不到完整的包

问题和建议

1,问题
客户端socket 断了, 我知道是哪个socket 端了吗?
Send发消息是堵塞的吗? 如果我有几千个链接,发到后面不是要延迟好久? 然后ses 发东西的时候类似线程安全吗?
Peer 在客户端断开的时候, 发消息会宕机 , 这个还需要自己处理好多东西,
2, 建议,
感觉网络通信框架 和protobuf 还有其他的 协议要分开,没必要都支持了, 就做纯的网络框架,其他功能通过插件的方式实现
感觉golang 的特色 chan 用的很少, 总觉得不能发挥go 的优势

哈哈哈哈
很赞的设计

支持一下自定义listener

listener net.Listener

标准的库的net.Listener是很好,但是遇到CC攻击获取其他高并发的情况下就麻烦了
比如我想用用这个库 https://github.com/valyala/tcplisten

所以支持一下自定义Listener,

还有网络库怎么能少得了这个方便的东西:
func setMaxConn() error { var RLimit syscall.Rlimit err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &RLimit) if err != nil { return err } log.Info("get setrlimit success", zap.Uint64("cur", RLimit.Cur), zap.Uint64("max", RLimit.Max)) RLimit.Cur = 800000 RLimit.Max = 800000 err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &RLimit) if err != nil { return err } err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &RLimit) if err != nil { return err } log.Info("set setrlimit success", zap.Uint64("cur", RLimit.Cur), zap.Uint64("max", RLimit.Max)) return nil }

BroadcastToClientList 有一个小BUG

不是大问题,只是新创建空间后,地址没有赋给self对象造成的。
表现 BroadcastToClientList 指定用户不能正常收到广播。

review源码中发现问题出在:
router\routerconn.go 187行,


list = make([]int64, 0)
改成
self[routerSes] = make([]int64, 0)
list = self[routerSes]

@davyxu

client构建脏包,导致server崩溃

client构建的tcp包,如果有意将ltv中的包长度小于实际长度,则server会崩溃。server在对数据copy的时候,轻信ltv注明长度,引发range溢出。

【错误】lineraPipe 没有友好的退出

func (self *lineraPipe) Stop(result int) {
self.exitSignal <- result
}
func (self *lineraPipe) Wait() int {
return <-self.exitSignal
}

Stop 执行后,并没有友好地停止之前的读取 datachan 循环。

目前master的example websocket 無作用

依照index.html發送封包
server無法解析

[DEBU] websocket_server 2018/11/15 10:17:58 session closed: 2
[DEBU] gorillawsproc 2018/11/15 10:17:58 #ws.recv(server)@3 len: 0 SessionAccepted | {}
[DEBU] websocket_server 2018/11/15 10:17:58 server accepted
[DEBU] gorillawsproc 2018/11/15 10:17:58 #ws.recv(server)@3 len: 0 |

換回8月中抓的版本可正常運行

封包长度有限制

@davyxu 你好,我在使用cellnet的时候,发现V5版本有默认封包长度限制,客户端发送的是完整封包(大约30K),而服务端收到的就大概只有10K左右,然后服务端就会报错,Session会断开再连接,具体错误提示如下,请问一下该如何解决?

[ERRO] tcppeer 2018/06/27 20:54:58 session closed, sesid: 2, err: msg not exists, '26479'
[DEBU] tcpproc 2018/06/27 20:54:58 #tcp.recv(server)@2 len: 0 SessionClosed | {}
[DEBU] tcpproc 2018/06/27 20:55:00 #tcp.recv(server)@3 len: 0 SessionAccepted | {}

PS:V4版本没这个问题。

websocket session.close

websocket 中调用 session.close 会引起并发写入的 panic

msg := ev.Msg.(*jsongamedef.TestEchoJsonACK)
ev.Send(&jsongamedef.TestEchoJsonACK{Content: "roger"})
ev.Ses.Close()

panic: concurrent write to websocket connection
goroutine 19 [running]:
github.com/gorilla/websocket.(*messageWriter).flushFrame(0xc042127d28, 0xc04204c501, 0xc04204c518, 0x0, 0x0, 0x0, 0x0)
github.com/davyxu/cellnet/websocket.(*wsSession).Close(0xc04210a340)

peer mysql如何获取db连接池对象

type mysqlConnector struct {
	peer.CorePeerProperty
	peer.CoreContextSet
	peer.CoreSQLParameter

	db      *sql.DB
	dbGuard sync.RWMutex
}
func NewWrapper(drv *sql.DB) *Wrapper {

	return &Wrapper{
		drv: drv,
	}
}

db连接池对象是私有的,与数据库交互的封装需要传入连接池对象,是否应该提供接口获取连接池对象

not enough arguments in call to codec.EncodeMessage

# github.com/davyxu/cellnet/proc/gorillaws \github.com\davyxu\cellnet\proc\gorillaws\transmitter.go:79:40: not enough arguments in call to codec.EncodeMessage have (interface {}) want (interface {}, cellnet.ContextSet)

Pick 函数的规范实现

更简洁的实现是

func (self *Pipe) Pick(retList *[]interface{}) (exit bool) {
self.listGuard.Lock()
defer self.listGuard.UnLock()

for len(self.list) == 0 {
	self.listCond.Wait()
}

// 复制出队列
   // 下面的代码保持一样的

}

建议给封包那边写个文档说明

写客户端通信组件的时候也方便点不是吗。最近我在写个js和wx小程序的项目。客户端发包和解包的时候得和cellnet对应一下。我这里是写完了。但是要是有个文档我估计会更方便别人做一些后续开发。
只是提一下建议。

proc gorillaws接收消息崩溃

客户端发送消息包到服务器,服务器崩溃

func (WSMessageTransmitter) OnRecvMessage(ses cellnet.Session) (msg interface{}, err error) {

	conn, ok := ses.Raw().(*websocket.Conn)

	// 转换错误,或者连接已经关闭时退出
	if !ok || conn == nil {
		return nil, nil
	}

	var messageType int
	var raw []byte
	messageType, raw, err = conn.ReadMessage()

	if err != nil {
		return
	}

	switch messageType {
	case websocket.BinaryMessage:
		msgID := binary.LittleEndian.Uint16(raw)
		msgData := raw[MsgIDSize:]

		msg, _, err = codec.DecodeMessage(int(msgID), msgData)
	}

	return
}
msgID := binary.LittleEndian.Uint16(raw)

应改为

msgID := binary.LittleEndian.Uint16(raw[:MsgIDSize])

缺少peer_websocket.go

您好,克隆下来有peer_tcp.go,但找不到 peer_websocket.go,目前比较常跟client连接的是websocket
可否补上?

StringHash not unique, may cause msgid same

这是测试代码
func TestStringHash(t *testing.T) {
a:="msg_protos.PKT_S2C_Reconnection_World_Status"
b:="msg_protos.ShipEquipFireReq"
t.Logf("a:%d,b:%d",StringHash(a),StringHash(b))
}
这是结果
strhash_test.go:8: a:7809,b:7809
如此下来,StringHash会有重复,会导致file.go MsgID 重复。
希望能换个MsgID的实现方式

socket中有个错误

`连不上
if err != nil {

		if self.tryConnTimes <= reportConnectFailedLimitTimes {
			log.Errorf("#connect failed(%s) %v", self.name, err.Error())
		}

		if self.tryConnTimes == reportConnectFailedLimitTimes {
			log.Errorf("(%s) continue reconnecting, but mute log", self.name)
		}

		// 没重连就退出
		if self.autoReconnectSec == 0 {
			break
		}

		// 有重连就等待
		time.Sleep(time.Duration(self.autoReconnectSec) * time.Second)

		// 继续连接
		continue
	}`

if self.tryConnTimes <= reportConnectFailedLimitTimes应该是>=吧

rpc调用修改建议

当同一个消息使用rpc在不同的地方多次调用时, 会发生callback函数错乱调用,即A处的结果会调用到B处的callback函数
建议:为每一个rpc指定一个唯一的rpcid,通过rpcid找到callback函数,那么A处的结果必然调用A自己的callback函数

HTTP(测试中)

我看HTTP(测试中),请问一下,这个有发布计划吗?

websocket 客服端关闭服务端无法释放 Goroutine

我尝试建立大量链接,并关闭后,统计链接数量,一直持续增长
后来发现是github.com/davyxu/cellnet/peer/gorillaws/session.go
cleanup() 时没有清理干净导致修改后代码为
// 清理资源
func (self *wsSession) cleanup() {

self.cleanupGuard.Lock()

defer self.cleanupGuard.Unlock()

// 关闭连接
if self.conn != nil {
	self.conn.Close()
	self.conn = nil
}
   //没有清理这个
self.Close()
// 通知完成
self.exitSync.Done()

}
之后就正常,希望修复下这个问题。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.