简介
dubbo-getty 是一个用 go 编写的异步网络 I/O 库,提供了三种通信模式,tcp、udp 以及 websocket,并为其设计了相对统一的流程和 API,目前 dubbogo 等知名项目都使用 getty 作为底层的网络通信库,其特点是封装性好,使用简单。下图是 getty 核心结构的类图,基本囊括了整个 getty 框架的设计。
灰色部分为 go 内置库
下面以 TCP 为例介绍下 getty 如何使用以及该类图里各个接口或对象的作用。
TCP Server 端
server
其中 server/client 是提供给用户使用的封装好的结构,client 的逻辑与 server 很多程度上一致,因此这里只讲 server。
在 getty 中,server 服务的启动流程只需要两行代码
server := getty.NewTCPServer(options...)
server.RunEventLoop(NewHelloServerSession)
第一行非常明显是一个创建 server 的过程,options 是一个个 func(*ServerOptions) 函数,用于给 server 添加一些额外功能设置,如启用 ssl,使用任务队列提交任务的形式执行任务等。
第二行的 server.RunEventLoop(NewHelloServerSession) 则是启动 server,同时也是整个 server 服务的入口,它的作用是监听某个端口(具体监听哪个端口可以通过 options 指定),并处理 client 发来的数据。RunEventLoop 方法需要提供一个参数 NewSessionCallback,该参数的类型定义如下:
type NewSessionCallback func(Session) error
这是一个回调函数,将在成功建立和 client 的连接后被调用,一般提供给用户用于设置网络参数,如设置连接的 keepAlive 参数、缓冲区大小、最大消息长度、read/write 超时时间等,但最重要的是,用户需要通过该函数,为 session 设置好要用的 Reader、Writer 以及 EventListener。
Session
Session 可以说是 getty 中最核心的接口了,每个 Session 代表着一次会话连接,向下,Session 对 go 内置的网络库做了完善的封装,包括对 net.Conn 的数据流读写、超时机制等,向上,Session 提供了业务可切入的接口,用户只需实现 EventListener 就可以将 getty 接入到自己的业务逻辑中。目前 Session 接口的实现只有 session 结构体,Session 作为接口仅仅是提供了对外可见性以及遵循面向编程接口的机制,之后我们谈到 Session,其实都是在讲 session 结构体。
Connection
Connection 则是刚提到的,Session 向下对于 go 内置网络库的抽象封装,根据不同的通信模式,Connection 分别有三种实现:
- gettyTCPConn:底层是 *net.TCPConn
- gettyUDPConn:底层是 *net.UDPConn
- gettyWSConn:底层使用第三方库实现
EventListener
EventListener 则对应 Session 向上开放给用户的接口,用户需要在该接口的实现中封装好业务逻辑,由 session 在运行的过程中调用。EventListener 接口的定义如下
// EventListener is used to process pkg that received from remote session
typeEventListenerinterface{
// invoked when session opened
// If the return error is not nil, @Session will be closed.
OnOpen(Session) error
// invoked when session closed.
OnClose(Session)
// invoked when got error.
OnError(Session, error)
// invoked periodically, its period can be set by (Session)SetCronPeriod
OnCron(Session)
// invoked when getty received a package. Pls attention that do not handle long time
// logic processing in this func. You'd better set the package's maximum length.
// If the message's length is greater than it, u should should return err in
// Reader{Read} and getty will close this connection soon.
//
// If ur logic processing in this func will take a long time, u should start a goroutine
// pool(like working thread pool in cpp) to handle the processing asynchronously. Or u
// can do the logic processing in other asynchronous way.
// !!!In short, urOnMessage callback func should return asap.
//
// If this is a udp event listener, the second parameter type isUDPContext.
OnMessage(Session, interface{})
}
这五个接口中最核心的是 OnMessage 方法,该方法有一个 interface{} 类型的参数,用于接收 client 端发来的数据。可能大家有个疑惑,网络连接最底层传输的是二进制,到我们使用的协议层一般以字节流的方式对连接进行读写,那这里为什么要使用 interface{} 呢?这是 getty 为了让我们能够专注编写业务逻辑,将序列化和反序列化的逻辑抽取到了 EventListener 外面,也就是前面提到的 Reader/Writer 接口,session 在运行过程中,会先从 net.Conn 中读取字节流,并通过 Reader 接口进行反序列化,再将反序列化的结果传递给 OnMessage 方法。
Writer/Reader
Writer 和 Reader 分别是序列化/反序列化接口,两者的定义非常简单,都只有一个方法。
type Reader interface{
Read(Session, []byte) (interface{}, int, error)
}
type Writer interface{
Write(Session,interface{}) ([]byte, error)
}
具体的序列化/反序列化逻辑则交给了用户手动实现,其中 Reader 接口之前提过,当 server 端读取了 client 发送的字节流后,会调用它的 Read 方法进行反序列化。而 Writer 接口则是在 client 端被使用,当 client 发送数据时,需要调用 Write 方法将发送的数据序列化为字节流,再写入到 net.Conn 中。
至此,getty 中 server 的处理流程大体如下图