监听socket和epoll的ET模式

同事遇到一个问题,他的一个服务器,在用ab压力测试时发现有时会阻塞。
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

猜你喜欢

转载自jmw.iteye.com/blog/910622