服务器端高性能网络编程的核心在于架构,而架构的核心在于进程-线程模型的选择。
作为服务器需要做网络数据的收发,需要做数据库拉取和保存,需要做日志存储,需要做常规的游戏逻辑处理.....在这里我把这些功能划分为三个大的线程类型:IO线程,事件线程,第三方库线程。
I/O线程,这种线程的主循环是多路复用,阻塞地等待在select/poll/epoll_wait系统调用上。这类线程也处理定时事件。当然它的功能不止IO,有些简单计算也可以放在其中,比如消息的编码或者解码(序列化和反序列化)。在这里我之前有文章介绍过一种设计模式:Reactor 模式,不明白的看这里游戏服务器架构:设计模式之Reactor反应堆,
其中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程阻塞在多路复用器上,一旦有I/O事件到来或是准备就绪,多路复用器将返回并将相应I/O事件分发到对应的处理器中。
编辑
Reactor是一种事件驱动机制,和普通函数调用不同的是应用程序不是主动的调用某个API来完成处理,恰恰相反的是Reactor逆置了事件处理流程,应用程序需提供相应的接口并注册到Reactor上,如果有相应的事件发生,Reactor将主动调用应用程序注册的接口(回调函数)。
事件线程,这类线程的主循环是阻塞的事件队列,阻塞地等在条件变量上,这样的线程可以有多个位于线程池中,这种线程通常不涉及I/O,一般要避免任何阻塞操作(尽量不要有系统调用函数,如read write之类的函数)。当然根据实际情况也可以试非阻塞的事件循环,不断轮询从事件队列中取得事件执行,在我们平时的游戏服务器端中,一般从事件队列里处理完事件后,会根据执行事件进行极短时间(毫秒级的)的sleep,这样做是因为用来计算的事件线程要处理一些timetick,所以不能一直阻塞直到有新的事件道来,至于为什么做,是防止如果事件不来进入busy-wait状态,sleep是系统函数,调用会陷入内核态让出CPU。
在实际的应用开发中,事件线程通常作为游戏逻辑线程,从网络线程里取网络包,放到逻辑线程里进行逻辑运算,将计算结果或者状态再通过网络IO线程返回给客户端或其他类型的服务器。
第三方库所用的线程,比如日志线程,数据库线程。无论日志线程还是数据库线程一般涉及写硬盘的操作,比较慢,如果放到事件线程中,会造成一定的阻塞,直观的感受就是,客户端操作一个指令,明显感觉到卡顿。