|
最近今天被一个压力测试的问题困扰,一个很奇怪的问题,每次10000个链接的压力测试,我的服务器都会在9995个链接之后数据发送出现异常。
一开始怀疑自己的发包问题,又怀疑链接问题,加了很多日志,最后还是没有找到问题的地方,问题的表现是,10000个链接客户端经常超时,明明我服务器发送出去的数据,客户端就像没收到一样(因为客户端不是我写的,所以无法跟踪为问题)。
后来经过详细测试,发现了一个规律。
那就是永远是最后的几个socket建立accept非常慢。
于是我开始怀疑我的epoll accept接口的代码是否有问题。
代码如下:- /* * 主线程用于处理接收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呢?
于是,我尝试改写了一下自己的程序- /*
- * 主线程用于处理接收TCP的连接请求
- */
- while(true)
- {
-
- remote_sock_tcp = accept(local_sock_tcp, (struct sockaddr *)&client_addr, &clen);
- if(remote_sock_tcp <= 0)
- {
- slog("lkyw_accept", "[accept]wait.");
- continue;
- }
-
- 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), ntohs(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 = 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事件可能会被搁置。所以,在写大并发处理的时候,有时候要结合实际用例去看问题。有时候需要结合去处理问题。
在这里记录一下,存档。 |
|