最近重构了去年造的一个轮子 Vino。Vino 旨在实现一个轻量而且可以或许担保机能的 Web Server,仅存眷 Web Server 的本质部门。在重构进程中,Vino 警惕了很多优秀开源项目标思想,如 Nginx、Mongoose 和 Webbench。因此,比拟上一个版本的 Vino,此刻的 Vino 不只机能获得晋升,并且设计也更为优雅、结实 :D。
本文将会对 Vino 今朝所具备的要害特性举办叙述,并总结开拓进程中的一点心得。
单线程 + Non-Blocking
Vino 整体回收了基于事件驱动的单线程 + Non-Blocking 模子。回收单线程模子,制止了系统分派多线程及线程之间通信的开销,同时低落了内存的耗用。由于回收了单线程模子,为了更好的提高线程操作率,Vino 将默认 Blocking 的 I/O 配置为 Non-Blocking I/O,即在线程读/写数据的进程中,假如缓冲区为空/缓冲区满,线程不会阻塞,而是当即返回,并配置 errno。
Vino 最初的灵感来历于 Computer Systems: A Programmer’s Perspective 一书报告网络编程时实现的一个简朴的 Web Server,每到来一个请求,Web Server 城市 fork 一个历程去处理惩罚。显然,在高并发的场景下,这种模子是不公道的。每次 fork 历程会带来庞大的开销,而且系统中历程的数量是有限的。同时,陪伴多历程带来的历程调治的开销也不行小觑,CPU 会耗费大量的时间用于抉择挪用哪一个历程。历程调治激发的历程上下文之间的切换,也需要淹灭相当大的资源。
很容易遐想到回收多线程模子来替代多历程模子,对比于多历程模子,多线程模子占用的系统资源会大大低落,可是本质上并没有减小线程调治带来的开销。为了减小由线程调治导致的开销,我们可以回收线程池模子,即牢靠线程的数量,可是问题依旧存在:因为 Linux 默认 I/O 是阻塞(Blocking)的,假如线程池中所有的线程同时阻塞于正在处理惩罚的请求,那么新到来的请求就没有线程去处理惩罚了。因此,假如我们用 Non-Blocking 的 I/O 替换默认的 Blocking I/O,线程将不会阻塞于数据的读写,昆山软件开发,问题便可获得办理。
HTTP Keep-Alive
Vino 支持 HTTP 长毗连(Persistent Connections),即多个请求可以复用同一个 TCP 毗连,以此淘汰由 TCP 成立/断开毗连所带来的机能开销。每到来一个请求,Vino 会对请求举办理会,判定请求头中是否存在 Connection: keep-alive 请求头。假如存在,在处理惩罚完一个请求后会保持毗连,并对数据缓冲区(用于生存请求内容,响应内容)及状态标志举办重置,不然,封锁毗连。
关于 HTTP Keep-Alive 的优势,RFC 2616 有着更完善的总结,引用如下。
按时器 Timer
假如一个请求在成立毗连后迟迟没有发送数据,可能对方溘然断电,应该如那里理惩罚?我们需要实现按时器来处理惩罚超时的请求。Vino 按时器的实现参考了 Nginx 的设计,Nginx 利用一颗红黑树来存储各个按时事件,每次事件轮回时从红黑树中不绝找出最小(早)的事件,假如超时则触发超时处理惩罚。为了简化实现,在 Vino 中,我实现了一个小顶堆来存储按时事件,假如被处理惩罚的按时事件同时支持长毗连,那么在该请求处理惩罚完毕后会更新该请求对应的按时器,也就是从头计时。按时器相关代码见 vn_event_timer.h 和 vn_event_timer.c。
HTTP Parser
由于网络的不确定性,我们并不能担保一次就能读取所有的请求数据。因此,对付每一个请求,我们城市开发一段缓冲区用于生存已经读取到的数据。同时,昆山软件开发,我们需要同时对读取到的数据举办理会,以担保读取到的数据都是公道的数据,譬喻,假设今朝缓冲区内的数据为 GET /index.html HTT,那么下一次读取到的字符必需为 P,不然,该当即检测出当前请求是一个异常的请求,并主动封锁当前的毗连。