实现并发服务器的方式有多种,下面说一下我了解到的几种解决方案。
方案一:多进程并发服务器
主进程监听、accept()连接,子进程负责处理业务逻辑和流的读取。
缺点:进程需要占用系统资源,存在硬件资源瓶颈,且调度,管理资源等系统开销较大。
方案二:多线程并发服务器
主线程监听、accept()连接,子线程负责处理业务逻辑和流的读取。
缺点是:
多线程的适用场景是:提高响应速度,让IO和计算相互重叠,降低延时。虽然多线程不能提高绝对性能,但是可以提高平均响应性能。
- 会频繁地创建、销毁线程,这对系统也是个不小的开销。这个问题可以用线程池来解决。线程池是预先创建一部分线程,由线程池管理器来负责调度线程,达到线程复用的效果,避免了反复创建线程带来的性能开销,节省了系统的资源。
- 要处理同步的问题,当多个线程请求同一个资源时,需要用锁之类的手段来保证线程安全。同步处理不好会影响数据的安全性,也会拉低性能。
- 一个线程的崩溃会导致整个进程的崩溃。
(方案一、二均不适宜高并发)
方案三:IO复用+多线程
利用epoll(或select、poll)监听socket,每当有请求接入时,创建一个线程accept()连接请求,并在其独立线程中与对应客户端通信。
缺点:同纯粹的多线程一致,只是将主线程的阻塞等待连接,换成了事件轮询,对单一进程来说并无区别。
方案四:单进程+多路IO复用(事件轮询)模型(select、poll、epoll)
首先通过 socket() 来创建一个 sock 文件描述符用来监听客户端的连接,然后将已连接的socket描述符加入epoll事件监听池。
缺点:当监听池中的多个事件同时触发时,各个事件的处理并非是真正的并发进行的,而是按先后顺序循环执行的。但是epoll相对于select、poll来说,这一点已经有很大优势了,后两者当检测到事件时还需要循环检测事件描述符集,需要消耗更多的时间。
方案五:利用事件驱动库(libevent、libev、libuv等)来实现高并发
常见的事件驱动库有 libevent 库,还有作为 libevent 替代者的 libev 库。这些库会根据操作系统的特点选择最合适的事件探测接口,并且加入了信号 (signal) 等技术以支持异步响应,这使得这些库成为构建事件驱动模型的不二选择。Linux下可使用 libev 库替换 select 或 epoll 接口,实现高效稳定的服务器模型。
Libev是一个基于Reactor模式的事件库,效率较高、代码精简(4.15版本8000多行,c语言编写),是一个值得学习的轻量级事件驱动库。 它的官网(http://libev.schmorp.de/)在国内已经没法访问了。但是我们仍然可以从github上下载其源码(https://github.com/enki/libev)。