网络服务器的实现不外乎两种情况,一是为某个业务单端开发服务器,二是实现通用网络服务器框架,前者的设计可能很大程度的受业务需求的影响,而后者则要保证简单易用,稳定服务,最好还有不错的性能。
服务器框架的优势在于让使用者快速的进行开发,只需要做很少的事情即可完成服务器的开发。在整个服务模型中,只有对请求任务的处理是预先不可知的,其他的逻辑基本上是固定的,故在实现框架时,将实际的处理部分以回调的形式空出来,使用者只需要实现回调函数并传递给框架即可,模型如下:
框架在实现时,我倾向的方式是使用单独的线程线程处理连接任务,接收到连接后就将其加入EPOLL事件集中,当请求任务发生时,调用用户实现的回调函数进行处理。
服务器调用回调的方式有很多种,主要包括:
(1) 串行处理每个请求,即每接受到一个请求就调用回调,当前请求服务完毕后,才能接受下一个请求。
(2) 使用多进程服务请求,对这种方式还没有探究过。
(3) 使用多线程服务请求,每接受到一个请求,创建一个线程为其服务,在线程中调用回调。这种方法的主要弊端在于:【1】OS创建线程的数目往往有限制,当有大量并发连接时,可能存在问题;【2】当实服务请求的时间比创建销毁线程的时间小时,这种方式显得效率很低。下一种方式则能避免这两种弊端。
(4) 使用线程池服务请求,这种方式通常需要借助任务队列实现,主线程接收到请求,将请求加至任务队列,多个线程从任务队列中读取任务并服务,通常,线程池的线程数可根据业务类型进行调整。
服务器可以使用任意一种方式实现,不同的处理方式适合于不同的业务,为了给框架使用者提供灵活的支持,服务逻辑这一部分也是可以从框架中分离的,如下图:
服务框架中拥有一个server_tasker_t类型的实例_tasker,server_tasker_t需要实现process接口。服务器框架在服务请求时,直接调用_tasker的process方法即可(传递回调给process),process中以什么方式、什么时候调用回调函数,框架是不需要关心的,用户指定哪种方式就使用哪种方式。
上面附件中的代码为以上模型的简单实现,代码在出错处理、注释方面都处理得很粗糙,另外在使用common_thread_t类时,没有找到合适的地方delete(参考上一篇博文),会存在内存泄露的情况(已在代码中注释标明),求达人指点。每个客户端的请求包含信息,服务器从某个磁盘分区中(在mserver.cpp中的do_task回调中指定)读取指定偏移处的数据,为了模拟真实请求情况,客户端的请求会播放一段真实的trace(我截取了trace的前一段信息,需要完整trace的可以跟我联系),整个项目的总体架构如下: