半个月前动手写了web服务器用webbench进行压测导致epoll_wait阻塞不返回,一直查bug历经很长时间才查出,总结一下踩到的坑点…
web服务器框架简述
采用reactor模型,主线程调用epoll进行监听,分发给线程池进行解析,IO事件由主线程进行发送。
采用epoll ET工作模式。
分析bug
-
ET工作模式注意的事项点我,这是之前总结的在写代码需要注意的事项,并且和LT模式在代码逻辑处理上的不同,但在写web服务器的时候将自己总结的点都用到还是出现了问题,在费尽心思定位bug查找各种错误和博客后,定位到了错误的根源。
-
在最后定位到是epoll_wait阻塞住,我决定抓包进行查看,发现处理的连接数量变多,连接队列就会溢出,导致后来的连接会收到服务器发来的RST字段,然后将自己所学的知识结合起来,发现首先是listen的backlog设置的队列满(不知道概念的可以点这里),然后accept函数一直也没有将其从可连接队列取出,首先想到的backlog参数太小导致accept没有及时将其取出(可笑的想法),然后我将backlog参数更改为更大的数,发现压测所处理的连接数确实变多了但还是会最后导致连接队列满的问题,这时就发现根源不再backlog参数上,而是别的问题(这时候应该想到问题出在哪的),还是知识没有掌握牢靠。然后我将epoll的工作模式换成LT,接着进行压测发现bug完全没有了…,这是我发现可能是我ET的代码处理有了问题,这时我就将我之前学到的ET的注意事项全部进行了查看,发现代码完全处理的没有问题,一头雾水,然后我想到应该是accept出了问题,后来经过查找终于找到了问题所在。
问题出现在,我将listenfd设置为ET,如果listenfd触发了可读事件证明有新的连接到来,这时我就简单的调用了accept进行接收,如果同时有很多新的连接到了,listenfd只会触发一次,而我也只将一个新连接接收进来,导致accept连接队列满,然后就导致了后面一系列的bug。然后将accept用while循环接收直到返回错误为EAGAIN或者这EWOULDBLOCK为止。
....
int fd = events[i].data.fd;
//有新的连接
if(fd == listenfd)
{
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
//循环调用accpet,因为ET模式下当多个连接到达时只会触发一次listenfd可读事件
while(true)
{
int connfd = accept(listenfd, (sockaddr*)&cli_addr, &cli_len);
if(connfd < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)
break;
else
{
printf("errno is: %d\n", errno);
exit(1);
}
}
此篇博客记录自己踩的坑,希望对看到这篇博客的朋友有帮助。
自己写的web服务器源码地址