webserver总结
可设置参数:连接池最大连接数,最大线程数,任务队列最大值,timeslot
epoll
epoll监听listen_fd(接受新客户端和断开连接), pipefd(将信号输入到管道用epoll统一管理), client_fd(读:客户端请求, 写:响应请求)。线程池里的线程将客户读了之后,重新将客户在epoll设置为写监听。
数据库连接池
单例模式,连接池中的数据库连接用链表连接起来。用信号量实现同步,典型的生产者消费者模型:
MYSQL* get_conn()
{
sem_full.wait();
mutex.lock();
MYSQL *ret = conn_list.front();
conn_list.pop();
mutex.unlock();
sem_empty.signal();
return ret;
}
bool return_conn(MUSQL* conn)
{
if(NULL == conn)
return false;
sem_empty.wait();
lock.lock();
conn_list.push_front(conn);
sem_full.post();
lock.unlock();
return true;
}
http解析
由于epoll采用edge trigger模式,需要用read_once()先将缓冲区的数据全部读进read_buffer,然后用process()分析报文。
分析报文时用状态机模型,有三个状态:初始(读完数据)是REQUESTLINE状态,分析完请求行后是HEADER状态,分析请求头,最后是CONTENT状态,分析正文。分析时用从状态机,表示当前行状态,有LINE_OK, LINE_BAD, LINE_OPEN,只有当前行是LINE_OK才能进行分析。
分析完后,如果分析正常就将返回报文写到write_bufffer里,修改当前client_fd在epoll树上为EPOLLOUT,监听写。当可写时,会有线程处理write任务。如果分析不正常会关闭client_fd,并epoll上下树。
write过程主要是用writev()函数,返回报文由两部分组成,返回头和正文,由于正文是一个html或者其他文件,而返回头存在write_buffer里,所以用writev()最合适,不需要将两个部分写到一个文件里再统一发出去。
最后根据请求头是否有keep-alive来决定是否关闭该连接。
线程池
所有线程获取任务队列里的任务,因为连接池用的是信号量,所以线程池就使用了条件变量。为了避免惊群现象,需要将条件变量的判断if变成while。
定时器
处理客户端的超时事件。
正好前段时间复习了堆,所以定时器就用时间堆实现。实现小技巧:当一个节点的大小变化需要调整时,为了避免判断是变大还是变小,调整函数内把up和down都运行一下,这两个只有一个会运行。
定时信号处理
创建一个pipe,定时信号处理函数功能为:把收到的信号写入管道的写端。然后用epoll监听管道的读端,当监听到管道可读,读出信号,运行真正的信号处理函数。这样就能统一信号处理和文件描述符的处理。

浙公网安备 33010602011771号