因为目标之一就是能够隔离具体协议格式。所以对协议做了抽象。只需要实现PacketProtocol接口即可: // PacketReader is used to unmarshal a complete packet from bufftype PacketReader interface {// Read data from conn and build a complete packet.// How to read from conn is up to you. You can set read timeout or other option.// If buff"s capacity is small, you can make a new buff, then return it,// so can reuse to reduce memory overhead.ReadPacket(conn net.Conn, buff []byte) (interface{}, []byte, error)}// PacketWriter is used to marshal packet into bufftype PacketWriter interface {// Build a complete packet. If buff"s capacity is too small,you can make a new one// and return it to reuse.BuildPacket(packet interface{}, buff []byte) ([]byte, error)// How to write data to conn is up to you. So you can set write timeout or other option.WritePacket(conn net.Conn, buff []byte) error}// PacketProtocol just a composite interfacetype PacketProtocol interface {PacketReaderPacketWriter} 也就是实现PacketReader/PacketWriter两个接口。为了让内存尽量的复用,减少内存压力,所以在ReadPacket方法和BuildPacket方法的返回值中需要返回一个切片。框架会在第一次调用时传入一个默认大小的切片到这两个方法中,如果容量不够,使用者可以自己重新建立切片,然后写入数据后返回该切片。下一次再实用时就使用这个返回出来的切片。其中ReadPacket方法是在一个专门用于接收数据的goroutine中调用。实现者可以自己根据自己的策略进行读取,因为传入了net.Conn,所以使用者可以自己设置I/O Timeout。实现者有责任返回一个完整的请求包。如果中间出了错误,有必要返回一个error。当发现有error后,会关闭该Session。这样做的原因是当读取或者构建一个请求包失败时,可能是数据错误,可能是链路错误,或者其他原因,总之,个人认为这种情况下没有必要继续处理,直接关闭链接。而且这里还有一个需要注意的事项,返回出来的请求包中的数据如果有包含切片类型的数据,建议重新分配一个切片,然后从buff中拷贝进去,尽量不要对buff切片做复用,否则可能会产生额外的BUG。BuildPacket方法是在一个专门处理发送的goroutine中调用。当发送goroutine收到数据包后,会调用BuildPacket,实现者就可以按照自己的私有格式进行序列化。同样的,buff不够,就自己重新构造一个buff,然后填充数据,并返回这个buff。WritePacket是给予实现者自己个性化发送的需求。可能实现者需要设置I/O Timeout.
请求包路由
基于event-based的实现,总是少不了要做的事情就是把一个请求包转发到对应的处理函数中。但是具体怎么转,怎么做是取决于具体的用例情景和实现的。所以我这里做的非常简单,就是定义了一个PacketHandler接口:// PacketHandler is used to process packet that recved from remote sessiontype PacketHandler interface {// When got a valid packet from PacketReader, you can dispatch it.Handle(s *Session, packet interface{})}使用者自己实现对应的Handle方法即可。当接收数据的goroutine收到对端发来的数据并调用PacketReader.ReadPacket后,会调用Handle方法 ,传入该Session实例与请求包。传入Session的目的是方便使用者不用去维护一个Session的实例。因为有的程序员要实现的逻辑可能比较简单,他仅仅用Session就满足了他的需求,他只需要实现对应的处理函数就好了。处理完成后,就调用Session.AsyncSend发送回应包。这里其实可以提供一个简单的默认版本的实现的。但是考虑到协议的不同,那么就导致调度的key的不同,所以还是让使用者自己发挥吧。使用者其实在这里有很大的自由度,他可以做基于map关系的回调分发逻辑,也可以做一个简单的实现逻辑,然后通过type assert做相应的实现。具体也是看各自的口味而定。我是比较喜欢后者,可以减少很多的Register,实现出Actor Model + Pattern Match味道的东西。