忙活了一个多星期,差不多把基于TCP的高并发连接网络架构测试稳定了。
目的:利用多线程把网络连接及数据包压缩/解压、加密/解密等等耗时的操作分流(asio对这些没有原生的支持),顺带提供线程池框架。只对游戏逻辑层暴露出单线程的外观,隔离底层多线程的复杂度。
结构如下图(未遵循什么标准,将就着看吧):
TCPSessionHandler:暴露给逻辑层的类,内部负责通过TCPIOThreadManager跟挂载于某个线程的TCPSession进行交互,对上层屏蔽多线程细节。声明如下:class TCPSessionHandler : public std::enable_shared_from_this<TCPSessionHandler>, public boost::noncopyable { public: // ==================== TYPEDEFS ======================================= // ==================== LIFECYCLE ======================================= TCPSessionHandler(); virtual ~TCPSessionHandler() {} // ==================== OPERATIONS ======================================= // sends message to remote endpoint, the content of message would be consumed void SendMessage(NetMessage& message); // sends message to remote endpoint, the content of message would be consumed void SendMessage(NetMessage&& message); // closes the session void Close(); // true if the session is closed. bool IsClosed() { return kInvalidTCPSessionID == session_id_; } // called when connection complete. virtual void OnConnect() = 0; // called when NetMessage received. virtual void OnMessage(const NetMessage& message) = 0; // called when TCPSession closed. virtual void OnClose() = 0; };
(注:本文代码风格尽量遵循 google c++ style guide )
NetMessageList:NetMessage定义为逻辑上有明确分界的网络消息,一个或多个NetMessage组成NetMessageList。
TCPIOThreadManager:管理一个或多个TCPIOThread,其中一个TCPIOThtread作为主线程逻辑运行。
CommandList:线程间交互的命令队列,也是整个框架中唯一的线程间同步方式,稍候详述。
TCPIOThread:IO线程,通过CommandList也可作为工作线程使用,每个线程使用asio的io_service处理多个TCPSession。
NetMessageFilterInterface:网络消息过滤器接口(上图省略了Interface因为太长了),可以自定制,通常封装组包、压缩、加密等流程,以适应不同的逻辑层协议需求。
TCPSession:后台网络连接,不区分服务端/客户端,负责处理网络数据的发送和接收。
外层还有两个类:TCPServer和TCPClient,可以关联到同一个TCPIOThreadManager,以适应多服架构中某台服务器既是TCP服务器又是其它服务器的客户端的需求。
示例代码:
int main(int argc, char** argv) { TCPIOThreadManager manager(1, // thread num boost::posix_time::millisec(2)); // sync interval unsigned short int port = 20000; TCPServer server({boost::asio::ip::tcp::v4(), port}, manager, &MyHandler::Create, &MyFilter::Create); manager.Run(); return 0; }
客户端也类似。
线程同步策略:
上述线程间同步采用了Command模式,借助了c++ 0x的function。CommandList的定义:
typedef std::list<std::function<void ()>> CommandList;
每个线程每隔一段时间就把要发往其它线程的Command批量发送,因为list的splice只是几个指针的操作,这个过程可以通过自旋锁高效的完成(psydo code):
CommandList thread1.commands_to_be_sent_; CommandList thread2.commands_received_; //thread1: { thread1.commands_to_be_sent_.push_back(command); ...; thread2.spinlock_.lock(); thread2.commands_received_.splice(thread2.commands_received_.end(), thread1.commands_to_be_sent_); //把thread1.commands_to_be_sent_连接到thread2.commands_received_的尾部 thread2.spinlock_.unlock(); } //thread2: { CommandList templist; thread2.spinlock_.lock(); templist.swap(thread2.commands_received_); //把commands_received_跟临时队列交换再处理, //减少lock的时间 thread2.spinlock_.unlock(); for (auto it = templist.begin(); it != templist.end(); ++it) (*it)(); templist.clear(); }
从TCPSessionHandler到TCPSession的NetMessageList的发送,就是通过这套机制来实现,几乎可以忽略线程锁开销。
剩下的问题:NetMessage内部变长缓冲用了vector来实现,动态内存分配可能会带来效率上的隐患。实际应用如果确证,可以用内存池,但是多线程的高效内存池稍微复杂。相关思路改天再写。