freeeyes 发表于 2013-4-9 11:34:32

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

最近今天被一个压力测试的问题困扰,一个很奇怪的问题,每次10000个链接的压力测试,我的服务器都会在9995个链接之后数据发送出现异常。
一开始怀疑自己的发包问题,又怀疑链接问题,加了很多日志,最后还是没有找到问题的地方,问题的表现是,10000个链接客户端经常超时,明明我服务器发送出去的数据,客户端就像没收到一样(因为客户端不是我写的,所以无法跟踪为问题)。
后来经过详细测试,发现了一个规律。
那就是永远是最后的几个socket建立accept非常慢。
于是我开始怀疑我的epoll accept接口的代码是否有问题。
代码如下:
        /*        * 主线程用于处理接收TCP的连接请求        */        struct epoll_event 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.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", "IP=%s, port=%d, remote_sock_tcp=%d.", inet_ntoa(sin.sin_addr), sin.sin_port, remote_sock_tcp);                                char client_ip = {'\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呢?
于是,我尝试改写了一下自己的程序
        /*
        * 主线程用于处理接收TCP的连接请求
        */
        while(true)
        {       
               
                remote_sock_tcp = accept(local_sock_tcp, (struct sockaddr *)&client_addr, &clen);
                if(remote_sock_tcp <=0)
                {
                        slog("lkyw_accept", "wait.");
                        continue;
                }
               
                sockaddr_in sin;
                memcpy(&sin, &client_addr, sizeof(sin));
                slog("lkyw_accept", "IP=%s, port=%d, remote_sock_tcp=%d.", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), remote_sock_tcp);
                char client_ip = {'\0'};
                int client_port = 0;
       
                sprintf(client_ip, "%s", inet_ntoa(sin.sin_addr));
                client_port = 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);               
        }
在测试,发现链接建立信息处理非常快,问题得到了解决。其实,一开始我怀疑C10K的问题,担心是多余的。
总结而言,在大并发链接下,epoll是非常适合处理数据到达和发送的,而由于优先级的问题,在accept事件可能会被搁置。所以,在写大并发处理的时候,有时候要结合实际用例去看问题。有时候需要结合去处理问题。
在这里记录一下,存档。
页: [1]
查看完整版本: 关于在大并发链接下的epoll处理问题