ab测试:
$ ab -v4 -c1 -n100 "http://127.0.0.1:19988/getprice"
ab的并发数(-c选项)是1的话,程序运转正常;并发数大于1时,很容易出现阻塞现象。
我自己用epoll(ET)写了个测试程序,服务器收到客户端连接后,把accept下来的socket扔到epoll中去。
socket = s.accept(session_addr); Socket::fcntl(socket, Socket::NONBLOCK); pEpoll->ctl(Epoll::IN | Epoll::LET, Epoll::ADD, socket);
之后服务器收到某个client的http请求,发回应,关闭连接。
int recv_num = recv(sockfd, buf, len, 0); string head = "HTTP/1.1 200 OK\r\nContent-Type: text\r\nr\n"; string body = "hi"; string resp = head + body; int slen = send(sockfd, resp.c_str(), resp.size(), 0); close(sockfd);
开始测试,发现了和同事一样的情况,并发数为2时,有时ab就会阻塞。
在服务器端用tcpdump抓包,过滤出问题连接。
12:45:54.119845 IP 127.0.0.1.53438 > 127.0.0.1.19988: S 2551433995:2551433995(0) win 32792 <mss 16396,sackOK,timestamp 1208357995 0,nop,wscale 7>
12:45:54.119855 IP 127.0.0.1.19988 > 127.0.0.1.53438: S 2538700704:2538700704(0) ack 2551433996 win 32768 <mss 16396,sackOK,timestamp 1208357995 1208357995,nop,wscale 7>
12:45:54.119862 IP 127.0.0.1.53438 > 127.0.0.1.19988: . ack 1 win 257 <nop,nop,timestamp 1208357995 1208357995>
12:45:54.120110 IP 127.0.0.1.53438 > 127.0.0.1.19988: P 1:92(91) ack 1 win 257 <nop,nop,timestamp 1208357995 1208357995>
12:45:54.120115 IP 127.0.0.1.19988 > 127.0.0.1.53438: . ack 92 win 256 <nop,nop,timestamp 1208357995 1208357995>
可以看到tcp连接已经建立完毕,服务器也收到了客户端的http请求,并发了回应,但应用层并没有反应。
后来想到了LT和ET的区别:edge-triggered mode only delivers events when changes occur on the monitored file descriptor
之前看用ET的程序时,更多的关注点会放在accepted socket,收数据时要保证循环收直到收完,但忽略了listening socket。看了下代码,发现listening socket的确是ET的。
Socket s; s.open(Socket::TCP); Address server_addr = Address("127.0.0.1", 19988); s.bind(server_addr); s.listen(10240); pEpoll->ctl(Epoll::IN | Epoll::LET, Epoll::ADD, s);
但listening socket收到事件通知时只处理了一次。
if (sock == s) { if (!accept(s, pEpoll)) continue; }
有两种解决方法
1. listening socket使用LT模式
pEpoll->ctl(Epoll::IN, Epoll::ADD, s);
2. listening socket仍使用ET模式,收到事件通知时,循环处理直到没有事件
while (true) { event_num = pEpoll->wait(1000); for (int32_t n=0; n<event_num; n++) { int32_t* type = pEpoll->getPtr(n); int sock = pEpoll->getSocketDesc(n); if (sock == s) { // 用while循环处理listening socket的事件 while (accept(s, pEpoll)); } else if (pEpoll->getEvent(n) & Epoll::IN) { int sockfd = pEpoll->getSocketDesc(n); process(sockfd); } else { assert(false); } } }
用上述两种方法用100个并发进行压力测试,未出现阻塞
$ ab -v4 -c100 -n1000 "http://127.0.0.1:19988/getprice"
最后打印一下100并发,1000个请求时,listening socket上的每个epoll事件处理了多少了accept请求。
$ egrep 'accept_count' log | sort -nk2 | tail
accept_count: 16
accept_count: 19
accept_count: 23
accept_count: 30
accept_count: 41
accept_count: 57
accept_count: 69
accept_count: 73
accept_count: 86
accept_count: 97