Skip to content

sanbit/link

 
 

Repository files navigation

介绍

最初开发这个包的目的是提炼一套可以在公司内多个项目间共用的网络层,因为在项目中我发现不同的网络应用一直重复一些相同或相类似的东西,比如最常用到的就是会话管理,不管是做游戏的前端连接层还是做服务器和服务器之间的RPC层或者是游戏的网关,虽然协议不一样但是它们都会需要会话的管理。会话管理看似简单,但是涉及到并发简单的需求就变得复杂起来,所以看似简单的会话管理每次实现起来都得再配套做单元测试甚至线上实际运行几个版本才能放心。所以我决定提取这些公共的部分,避免那些容易引入BUG的重复劳动。

但是在提取这些公共部分的时候并没有期初想象的那么容易,因为不同的应用场景有不同的需求,比如有的场景需要异步,有的场景需要同步,有的协议需要握手过程,有的则需要keepalive。从代码的提交历史里面可以看出这个包前后经过了很多次大的调整,因为要做一个能满足所有需求的通用底层真的很难。

经过不断的提炼,就像在简化公式一样的,link变得十分的简单,同时它的定位也很清楚。link不是一个网络层也不是一个框架,它只是一个脚手架,但它可以帮助你快速的实现出你所需要的网络层或者框架,帮你约束网络层的实现方式,不至于用不合理的方式实现网络层,除此之外它不会管更多的事情。

link是协议无关的,使用link只需要理解CodecType的概念,你可以加入任何你需要的通讯协议实现。

基础

link包核心由ServerSessionCodecType组成。ServerSession很容易理解,分别用于实现网络服务和连接管理。CodecType则提供具体的协议实现和io逻辑。

Server在使用的时候很简单,可以用 link.Serve()创建,也可以用link.NewServer()的方式创建。这样设计的目的是可以支持更多类型的Listener,不受限于net包。

Session在使用上分为两种情况,一种是由Server.Accept()产生的服务端会话,一种是由link.Dial()link.NewSession()产生的客户端会话。

CodecType的设计目的是让每个Session都有各自的EncoderDecoder用于消息的收发,这样才有机会实现有状态的通讯协议,比如有多阶段握手的通讯协议。

ServerSession上都有一个interface{}类型的State字段,可用于存储自定义状态。

Session上提供了关闭事件的监听机制,有一些应用场景需要在会话关闭时对一些资源做回收,就可以利用这个机制。

EncoderDecoder都可以选择性的实现Dispose()方法,Session关闭时将会尝试调用这个方法,这可以可以做到EncoderDecoder的资源回收利用,内置的BufioCodecType就利用这个机制引入了sync.Pool来提高对象的重用性。

内置类型

link的核心部分代码是极少的,link另外提供了一些常用到的工具类型,下面一一对其进行介绍。

这个文件里实现了一个Channel类型,用于手工管理Session或者对一组Session发送广播。广播发送方式可以通过实现BroadcastProtocol接口来自定义,默认的广播方式只是简单的逐个Session发送。

这个文件中实现了一个用于支持异步消息发送的CodecType。之前的版本中Session有一个AsyncSend()方法用于异步消息发送。我一直很不满意AsyncSend()的设计,从link包的历史版本中可以看到AsyncSend()经过了多次修改。

原因是不同的应用场景会有不同的异步消息发送需求,比如我们在游戏里很简单粗暴的把异步发送时出现chan阻塞的Session关闭掉,但是别的应用场景可能会需要等待一段时间后再重试,或者丢弃阻塞的消息,又或者阻塞允许一段时间,等到超时再做进一步处理。

所以AsyncSend()怎么改都不可能满足所有需求,最后我干脆删除它,由CodecType来决定消息是否异步发送,以及怎么进行异步发送。目前内置的asyncCodecType的逻辑是一旦阻塞就返回错误。如果这个设计不符合你的需要,你也可以参考它实现出自己所需的异步发送逻辑。

这个文件中实现了带缓冲的io以及缓冲的对象重用,这是网络层很常用到的优化。缓冲读和缓冲写可以显著的降低实际的io调用次数,在Go语言中一次实际的net.Conn.Read()调用开销并不低,它需要给文件句柄加锁然后放入事件循环里等待io事件,这里面有一系列的系统调用。所以实际项目中,强烈建议使用bufio来降低io调用次数。

有一个细节需要注意sync.Pool是跟着BufioCodecType实例的,所以在实际项目使用中,特别是创建客户端Session时,需要重用BufioCodecType而不是每次调用link.Dial()时都创建一个新的BufioCodecType实例。服务端不容易出现这个问题是因为BufioCodecType会被存在Server对象里,反复赋值给新建的服务端Session

里面实现了常见的Json、Gob、Xml格式的消息封包解包,正好这三种消息类型都不涉及到分包协议,所以可以很容易的内置到link包里。

早版本的link包曾经尝试加入分包协议的支持,但是分包协议变化众多,有点像前面提到的AsyncSend()一样众口难调,所以就去掉了。

将来会考虑像Erlang一样内置几个简单易用的分包形式,作为参考顺便满足一些要求不高的应用场景。

里面实现了线程安全的CodecType,旧版本的link里,Session内置了收发锁,让Session.Receive()和Session.Send()可以被并发调用。但是实际项目中并接受或者并发发送的场景很少,如果一开始就内置到Session`里,这部分调用开销就是多余的了。

所以后来我删除了Session里面加锁的逻辑,引入了ThreadSafe()。在需要对收发过程进行加锁保护的适合可以用它。

实际使用

从link的核心代码和内置类型可以看出,核心其实很简单,IO调用方式和协议实现都靠CodecType解耦了。我建议在实际项目中根据项目需求,参考内置类型的设计实现针对项目的CodecType,这样可以得到最好的执行效率和使用体验。

欢迎加技术交(xian)流(liao)群一起讨论link的使用和改进:188680931

About

Go语言网络层脚手架

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Go 100.0%