在阅读了TLPI和深刻理解计算机系统以后,学会了如何使用linux系统api,想在写代码的过程当中来加深本身对知识的理解,更想用这些知识来去作一个更酷的东西,而不只仅是教课书上的简单服务器。并且在实现过程当中每每能学到教科书外的东西。
私觉得项目为导向是学习编程的最好方法。并且没有什么比本身创造一个东西有趣。
“将一个实际的浏览器指向本身的服务器,看着他显示一个复杂的带有文本和图片的web页面,真是很是使人兴奋。"html
首先下载源码:源码地址react
而后将web页面所需的html文件放在/var/www目录下linux
$ cd /src
, 进入到src目录$ make
, 产生可执行文件HttpServer$ ./HttpServer \<ipv4 address> \<port number> \<process number> \<connect number per process>
例如:./HttpServer 127.0.0.1 8080 5 1000 ,这一步是开启web-server服务。git
Unbtun 16.04.2 内核版本是4.8github
1.本服务器采用进程池,epoll和非阻塞I/O实现高效的半同步/半异步模式。以下图:web
主进程只管理监听socket,链接socket都由进程池中的worker进行管理。当有新的链接到来时,主进程会经过socketpair建立的套接字和worker进程通讯,通知子进程接收新链接。子进程正确接收链接以后,会把该套接字上的读写事件注册到本身的epll内核事件表中。以后该套接字上的任何I/O操做都由被选中的worker来处理,直到客户关闭链接或超时。编程
2.每一个子进程都是一个reactor,采用epoll和非阻塞I/O实现事件循环。以下图: api
a. epoll负责监听事件的发生,有事件到来将调用相应的事件处理单元进行处理浏览器
i. 对一个链接来讲,主要监听的就是读就绪事件和写就绪事件。缓存
ii. 统一事件源:
1). 信号:信号是一种异步事件,信号处理函数和程序的主循环是两条不一样的执行路线,很显然,信号处理函数须要尽量的执行完成,以确保信号不被屏蔽(信号是不会排队的)。一个典型的解决方案是把信号的主要处理逻辑放到事件循环里,当信号处理函数被触发时只是经过管道将信号通知给主循环接收和处理信号,只须要将和信号处理函数通讯的管道的可读事件添加到epoll里。这样信号就能和其余I/O事件同样被处理。
2). 定时器事件。使用timefd,一样经过监听timefd上的可读事件来统一事件源。将其设置为边沿触发,否则timefd水平触发将一直告知该事件。
b. 链接池和内存池的实现:
bool Init(int connfd,size_t recv_buffer_size,size_t send_buffer_size);
函数,一个Return_Code process(OptType status)
函数。前一个函数会在第一次被调用时分配内存,后一个函数将根据操做类型,来决定要进行的是读仍是写操做。同时根据操做结果返回相应的状态,来决定要给epoll添加什么事件。f. Http报文请求行和头部解析:
1. 为何采用多进程而不是单进程多线程:
a. 虽说多线程的切换开销比多进程低。若是每个进程都工做在一个cpu上,那么切换的开销彻底能够省去,并且由于咱们采用的是进程池,进程的数目在启动时是能够设置的,并且并不会在程序的执行过程当中频繁的开新进程和销毁就进程,因此进程销毁和产生这块开销也避免了。
b. 同时,多进程的编码难度比多线程要低的多,并且也不用过多的考虑到线程安全问题。
c. 综上,我选择了多进程。
2. 为何采用时间堆?
a. 首先和双链表相比,最小堆的时间复杂度是优于他的。和时间轮比,虽然添加和删除定时器的时间复杂度是O(1),可是其执行一个定时的时间复杂度是O(n),同时其精度和时间轮的槽间隔有关。而最小堆则更适合处理这种每次timer模块须要频繁找最小的key(最先超时的事件)而后处理后删除的场景。其删除一个定时器是O(lgk)(若是考虑延迟删除的话,会是O(1),可是考虑到我要复用定时器,因此执行了严格的删除),添加是O(lgn),执行则时O(1)。nigix使用的是红黑树,可是“memory locality比heap要差一些,实际速度略慢”,即便用最小堆更容易命中cache。libev使用的是更高效的4叉堆。为了简化实现,我采用了二叉堆来实现timer的功能。
3. 为何采用链接池和内存池?
a. 和上面所说的同样,为了更好的利用资源,减小内存碎片,下降频繁的申请和销毁内存的开销。
epoll_ctl(int epollfd,int option,int fd,struct epoll_event *evlist)
函数中,将option和fd参数位置换了,致使一直epoll_ctl失败。调试了一天,最后才发现,参数位置写错了,然而其余地方的调用位置都写对了。感谢和感叹于大牛的智慧,编码的路上,还须要继续努力。 《linux多线程服务端编程》 《深刻理解计算机系统》 《Linx/unix编程手册》 《linux高性能服务器编程》 《深刻理解Ngix模块开发与架构解析》