找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 5787|回复: 0

关于在大并发链接下的epoll处理问题

[复制链接]
发表于 2013-4-9 11:34:32 | 显示全部楼层 |阅读模式
最近今天被一个压力测试的问题困扰,一个很奇怪的问题,每次10000个链接的压力测试,我的服务器都会在9995个链接之后数据发送出现异常。
一开始怀疑自己的发包问题,又怀疑链接问题,加了很多日志,最后还是没有找到问题的地方,问题的表现是,10000个链接客户端经常超时,明明我服务器发送出去的数据,客户端就像没收到一样(因为客户端不是我写的,所以无法跟踪为问题)。
后来经过详细测试,发现了一个规律。
那就是永远是最后的几个socket建立accept非常慢。
于是我开始怀疑我的epoll accept接口的代码是否有问题。
代码如下:
  1.         /*        * 主线程用于处理接收TCP的连接请求        */        struct epoll_event events[MAX_EVENTS];        while (1)        {                int nfds = epoll_wait(epfd, events, MAX_EVENTS, EPOLL_TIME_OUT);                                 //如果事件没有发生                if (nfds <= 0)                {                   continue;                }                                //处理所发生的所有事件                                 for(int i = 0; i < nfds; i++)                {                        if(events[i].data.fd == local_sock_tcp)                        {                                // 接受客户端的连接请求                                if ((remote_sock_tcp = accept(local_sock_tcp, (struct sockaddr *)&client_addr, &clen)) < 0)                                {                                        if (errno != EINTR)                                        {                                                perror("accept");                                                slog("lkyw_service", "accept error = %d.", errno);                                                exit(1);                                        }                                }                                                                                        sockaddr_in sin;                                memcpy(&sin, &client_addr, sizeof(sin));                                slog("lkyw_accept", "[accept]IP=%s, port=%d, remote_sock_tcp=%d.", inet_ntoa(sin.sin_addr), sin.sin_port, remote_sock_tcp);                                char client_ip[30] = {'\0'};                                int client_port = 0;                                                                sprintf(client_ip, "%s", inet_ntoa(sin.sin_addr));                                client_port = (int)sin.sin_port;                                                                // 与客户端连接socket不能采用立即释放的 方式来处理                                g_sock_mgr.accept_sock_tcp(remote_sock_tcp, client_ip, client_port);                                                                setnonblocking(remote_sock_tcp);                                                                struct epoll_event ev_client;                                //设置用于读操作的文件描述符                                 ev_client.data.fd = remote_sock_tcp;                                 //设置用于注测的读操作事件                                 ev_client.events = EPOLLIN|EPOLLET|EPOLLERR|EPOLLHUP;                                //注册ev到client_epfd事件中去                                epoll_ctl(client_epfd, EPOLL_CTL_ADD, remote_sock_tcp, &ev_client);                                        }                }
复制代码
后来更加详细的测试,发现我设置的epoll_wait在我大量数据包上来的时候,几乎不会触发。这有两种可能,一种是,epoll优先处理数据到达的事件,而等没有数据到达的时候才会处理链接请求。另一种是,epoll的et模式只负责告诉数据到达的消息,需要自己去遍历。
分析后觉得,如果是后者,那么应该是并不是每次链接请求都会记录events数组。它需要你在这个事件触发的时候,自己去遍历所有的accept有没有完成。那么,如果真是这样,链接请求就会在某些情况下丢失(在以上的代码下),实际上的测试结果却是,链接请求并没有丢失,而是有的过了6-8分钟才到达,所以我开始偏向前者的理论。
那么,既然如此,我在数据连接建立的时候,是否可以不用epoll,只在数据到达的时候用epoll呢?
于是,我尝试改写了一下自己的程序
  1.         /*
  2.         * 主线程用于处理接收TCP的连接请求
  3.         */
  4.         while(true)
  5.         {       
  6.                
  7.                 remote_sock_tcp = accept(local_sock_tcp, (struct sockaddr *)&client_addr, &clen);
  8.                 if(remote_sock_tcp <=  0)
  9.                 {
  10.                         slog("lkyw_accept", "[accept]wait.");
  11.                         continue;
  12.                 }
  13.                
  14.                 sockaddr_in sin;
  15.                 memcpy(&sin, &client_addr, sizeof(sin));
  16.                 slog("lkyw_accept", "[accept]IP=%s, port=%d, remote_sock_tcp=%d.", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), remote_sock_tcp);
  17.                 char client_ip[30] = {'\0'};
  18.                 int client_port = 0;
  19.        
  20.                 sprintf(client_ip, "%s", inet_ntoa(sin.sin_addr));
  21.                 client_port = sin.sin_port;
  22.        
  23.                 // 与客户端连接socket不能采用立即释放的 方式来处理
  24.                 g_sock_mgr.accept_sock_tcp(remote_sock_tcp, client_ip, client_port);
  25.        
  26.                 setnonblocking(remote_sock_tcp);
  27.        
  28.                 struct epoll_event ev_client;
  29.                 //设置用于读操作的文件描述符
  30.                 ev_client.data.fd = remote_sock_tcp;
  31.                 //设置用于注测的读操作事件
  32.                 ev_client.events = EPOLLIN|EPOLLET|EPOLLERR|EPOLLHUP;
  33.                 //注册ev到client_epfd事件中去
  34.                 epoll_ctl(client_epfd, EPOLL_CTL_ADD, remote_sock_tcp, &ev_client);               
  35.         }
复制代码
在测试,发现链接建立信息处理非常快,问题得到了解决。其实,一开始我怀疑C10K的问题,担心是多余的。
总结而言,在大并发链接下,epoll是非常适合处理数据到达和发送的,而由于优先级的问题,在accept事件可能会被搁置。所以,在写大并发处理的时候,有时候要结合实际用例去看问题。有时候需要结合去处理问题。
在这里记录一下,存档。
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-12-4 01:26 , Processed in 0.026651 second(s), 7 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表