epoll 在客户端断开连接时循环

epoll loops on disconnection of a client

本文关键字:循环 连接 断开 客户端 epoll      更新时间:2023-10-16

我正在尝试使用 epoll 来实现套接字服务器。我有 2 个线程执行 2 个任务:

  1. 侦听传入连接
  2. 在屏幕上写入客户端正在发送的数据。

对于我的测试,我将客户端和服务器放在同一台计算机上,运行 3 或 4 个客户端。服务器工作正常,直到我没有通过发出CTRL-C来杀死其中一个客户端:一旦我这样做,服务器就会开始循环并以非常快的速度打印来自其他客户端的数据。奇怪的是,

  1. 客户端每 2 秒发送一次数据,但服务器的速率更高
  2. epoll_wait也应该在其中一个客户端断开连接时打印一些东西,因为它也在检查 EPOLLHUP 或 EPOLLERR
  3. epoll_wait打印前应该等一会儿,因为我给了他 3000 毫秒的超时。

你能帮忙吗?可能是我以错误的方式将 epoll 描述符传递给另一个线程吗?我无法理解,因为代码看起来与周围的许多示例相似。

多谢

 // server.cpp
 #include <iostream>
 #include <cstdio>
 #include <cstring>
 extern "C" {
 #include <sys/epoll.h>    
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <netdb.h>
 #include <pthread.h>
 }
 #define MAX_BACKLOG 10
 void* readerthread(void* args){
     int epfd = *((int*)args);
     epoll_event outwait[10];
     while(true){
         int retpw = epoll_wait( epfd, outwait,20, 3000 );
         if( retpw == -1 ){
         printf("epoll error %mn");
         }else if( retpw == 0 ){
         printf("nothing is ready yetn");
         continue;
         }else{
             for( int i=0;i<retpw;i++){
                 if( outwait[i].events & EPOLLIN ){
                     int fd = outwait[i].data.fd;   
                     char buf[64];
                     if( -1 == read(fd,buf,64) ){
                         printf("error reading %mn");
                     }
                     printf("%sn",buf);
                 }else{
                     std::cout << "other event" << std::endl;
                 }                  
             }
         }
     }  
 }
 int main(){ 
      int epfd = epoll_create(10);
      if( -1 == epfd ){
      std::cerr << "error creating EPOLL server" << std::endl;
      return -1;
      }
      pthread_t reader;
      int rt = pthread_create( &reader, NULL, readerthread, (void*)&epfd );
      if( -1 == rt ){
      printf("thread creation %mn");
      return -1;
      }

      struct addrinfo addr;
      memset(&addr,0,sizeof(addrinfo));
      addr.ai_family   = AF_INET;
      addr.ai_socktype = SOCK_STREAM;
      addr.ai_protocol = 0;
      addr.ai_flags    = AI_PASSIVE;
      struct addrinfo * rp,* result;
      getaddrinfo( "localhost","59000",&addr,&result );
      for( rp = result; rp != NULL; rp = rp->ai_next ){
      // we want to take the first ( it could be IP_V4 
      // or IP_V6 )
      break;
      }     
      int sd = socket( AF_INET, SOCK_STREAM, 0 );
      if(-1==sd ){
      std::cerr << "error creating the socket" << std::endl;
      return -1;        
      }
      // to avoid error 'Address already in Use'
      int optval = 1;
      setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));         
      if( -1==bind( sd, result->ai_addr, result->ai_addrlen ) ){
      printf("%mn");
      std::cerr << "error binding" << std::endl;
      return -1;
      }
      while(true){
           std::cout << "listen" << std::endl;
           if( -1== listen(sd, MAX_BACKLOG ) ){
               std::cerr << "listen didn't work" << std::endl;
               return -1;
           }
           std::cout << "accept" << std::endl;
           sockaddr peer;
              socklen_t addr_size; 
           int pfd = accept( sd, &peer ,&addr_size );
           if( pfd == -1 ){
               std::cerr << "error calling accept()" << std::endl;
               return -1;
           }
           epoll_event ev;
           ev.data.fd = pfd;
           ev.events =  EPOLLIN;
           std::cout << "adding to epoll list" << std::endl;
           if( -1 == epoll_ctl( epfd, EPOLL_CTL_ADD, pfd, &ev ) ){
               printf("epoll_ctl error %mn");
               return -1;
           }
      }
 }
 // end of server.cpp

 // client.cpp
     #include <iostream>
 #include <cstring>
 #include <cstdio>
 extern "C"{
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <netdb.h>
 }
 int main(){
      const char* servername = "localhost";
      const char* serverport = "59000";
      struct addrinfo server_address;
      memset( &server_address, 0, sizeof(struct addrinfo) );
      server_address.ai_family  =  AF_INET;
      server_address.ai_socktype =  SOCK_STREAM;
      server_address.ai_protocol  =  0; // any protocol
      server_address.ai_flags    =  0;
      struct addrinfo * result, * rp;
      int res = getaddrinfo( servername, serverport, &server_address, &result );
      if( -1 == res ){
         std::cout << "I cannot getaddress " << servername << std::endl;
         return -1;
      }
      int fd = socket( server_address.ai_family
                , server_address.ai_socktype
                , server_address.ai_protocol );
      if( -1 == fd ){
      printf("I cannot open a socket %mn");
            return -1;
      } 
     for( rp = result; rp != NULL; rp = rp->ai_next ){
         std::cout << "************" << std::endl;
        if( -1 == connect( fd, rp->ai_addr, rp->ai_addrlen ) ){
             close(fd);
         }else{
         std::cout << "connected" << std::endl;
         break;
       }
     }
     if( rp == NULL ){
         std::cerr << "I couldn't connect server " << servername << std::endl;
     }
     while(true){
         sleep(2);
         pid_t me = getpid();
         char buf[64];
         bzero( buf,sizeof(buf));
         sprintf( buf,"%ld",me );
         write(fd,buf,sizeof(buf));
         printf("%sn",buf);
      }
 }
 // end of client.cpp

客户端断开连接由文件描述符上的 EOF 条件发出信号。系统将 EOF 视为文件描述符"可读"的状态。但是,当然,无法读取EOF条件。这是循环的来源。 epoll就像断开连接的客户端的文件描述符始终可读一样。您可以通过检查read何时返回 0 字节读取来检测您具有 EOF 条件。

处理 EOF 条件的唯一方法是以某种方式close文件描述符。根据事物的确切进展方式,这可能是 shutdown(sockfd, SHUT_RD)shutdown(sockfd, SHUT_RDWR)close(sockfd);

除非您知道出于任何原因需要shutdown(2)调用,否则我建议您使用 close 。当然,您应该记住在close epoll之前告诉文件描述符不再感兴趣。我不确定如果你不这样做会发生什么,但一种可能性是epoll会出错。另一个是,epoll将神秘地开始报告具有相同数值的新文件描述符的事件,然后再将其添加到epoll应该关注的列表。

另一

侧干净关闭的套接字将变得可读,read(2)将返回0,您必须检查这一点。正如现在编码的那样 - 级别触发的轮询 - epoll_wait(2)每次都会返回,而无需等待告诉您仍未读取流的结束。

或者,您可以切换到边缘触发的轮询(EPOLLET)并对EPOLLRDHUP做出反应。