监听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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
epoll是一种I/O多路复用技术,主要用于在一个进程中管理多个socket监听。它的作用是通过注册和监听多个socket,实现同处理多个网络事件的能力。在常用的TCP/UDP程序中,如果只有一个socket,不需要使用epoll。但是如果有多个socket,就可以使用epoll来管理这些socketepoll主要由两个结构体组成:eventpoll和epitem。eventpoll是每一个epoll所对应的事件,而epitem是每一个IO所对应的事件。当调用epoll_ctl的EPOLL_CTL_ADD操作,需要创建一个epitem来注册一个socketepoll中。而通过调用epoll_wait方法可以获取已经监听到的事件。 下面是一个示例代码,展示了如何使用epoll来实现socket监听: ``` // 创建一个epoll对象 int epollFd = epoll_create(1); // 创建一个socket int listenSocket = socket(AF_INET, SOCK_STREAM, 0); // 设置socket为非阻塞模式 fcntl(listenSocket, F_SETFL, O_NONBLOCK); // 创建一个epoll_event结构体 struct epoll_event listenEvent; listenEvent.data.fd = listenSocket; listenEvent.events = EPOLLIN | EPOLLET; // 监听读事件,并设置为边沿触发模式 // 将socket注册到epollepoll_ctl(epollFd, EPOLL_CTL_ADD, listenSocket, &listenEvent); // 开始监听事件 struct epoll_event events[MAX_EVENTS]; while (true) { int readyEventCount = epoll_wait(epollFd, events, MAX_EVENTS, -1); if (readyEventCount == -1) { // 发生错误,处理错误逻辑 break; } // 处理就绪的事件 for (int i = 0; i < readyEventCount; ++i) { if (events[i].data.fd == listenSocket) { // 监听socket有新的连接请求,处理连接逻辑 } else { // 其他socket有数据可读,处理读取逻辑 } } } // 关闭epollsocket close(epollFd); close(listenSocket) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值