less than 1 minute read

打算把TinyWebServer写一遍。

目标:写一个epoll LT,线程池,定时处理非活动连接的web服务器。

附加功能:ET,日志,连接MySql数据库。

Step 1. 简单epoll服务器

https://yuhengfdada.github.io/blog/server/#server-v5-epoll

在这个的基础上写得更优雅一些。

http_conn类,buffer机制

每个http_conn对象对应一个连接。对象内储存了该连接对应的client socket fd,用于唯一标识该client。整个类有一个static int来储存服务器的epfd。有了它就可以操纵epoll事件了。

当新的connection到来,除了register epoll event之外,还要init一个http_conn对象。

http_conn对象中有一个buffer和一个pointer,pointer指向未占用部分的开始。

每次收到client socket的readable事件,先调用相应http_conn对象的read_once()方法把socket全给读到对象中的buffer里。先判断是不是EOF,然后交给线程池处理刚刚读到的内容。

EPOLLONESHOT

实现的时候遇到一个问题:在LT模式,分配线程handle之后,主线程很快又会调用epoll_wait()。但如果此时子线程还没开始读socket / 没读完socket,epoll_wait()又会返回同一个socket的readable事件,主线程又会分配一个子线程去处理同一个fd。这显然8太好。

即使是ET模式,短时间内client发过来好几次数据时也会有多线程处理同一fd的情况。

可以在注册事件时加入EPOLLONESHOT flag解决。它的语义是:该fd触发一次事件之后就再也不会触发了。所以同时只会有一个线程在处理这个fd。

使用EPOLLONESHOT的注意点:线程处理完fd之后,需要再次启用该fd上的通知时,要手动使用EPOLL_CTL_MOD并加上EPOLLONESHOT

https://man7.org/linux/man-pages/man2/epoll_ctl.2.html

Step 2. 线程池

在线程池init的时候,调用多次pthread_create(),全都执行一个函数:worker()。worker()之后调用run()。

run()会一直while loop,直到事件队列中出现一个http_conn对象。此时的http_conn对象中的buffer刚刚读取完信息,处于待处理状态。run()会调用该http_conn对象的process()函数来处理。

同步事件队列

加入队列:调用append()函数。在将http_conn对象放入队列的同时将信号量++。

取出事件:将信号量–。

对队列的操作必须加锁。