epoll循环断开客户端

时间:2013-01-28 13:30:08

标签: c++ linux sockets server-side epoll

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

  1. 收听传入连接
  2. 在屏幕上写下客户端发送的数据。
  3. 对于我的测试,我将客户端和服务器放在同一台机器上,运行3或4个客户端。 服务器工作正常,直到我没有通过发出CTRL-C来杀死其中一个客户端:一旦我这样做,服务器开始循环并以非常快的速率从其他客户端打印数据。奇怪的是那个

    1. 客户端每2秒发送一次数据,但服务器的速率更高
    2. epoll_wait也应该在其中一个客户端断开连接时打印,因为它也检查EPOLLHUP或EPOLLERR
    3. epoll_wait应该在打印前稍等一下,因为我给了他3000毫秒的超时时间。
    4. 你能帮忙吗?可能是因为我以错误的方式将epoll描述符传递给另一个线程吗?我无法理解,因为代码看起来类似于很多例子。

      非常感谢

      的Mn

       // 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 %m\n");
               }else if( retpw == 0 ){
               printf("nothing is ready yet\n");
               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 %m\n");
                           }
                           printf("%s\n",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 %m\n");
            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("%m\n");
            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 %m\n");
                     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 %m\n");
                  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("%s\n",buf);
            }
       }
       // end of client.cpp
      

2 个答案:

答案 0 :(得分:3)

另一方彻底关闭的插座将变得可读,read(2)将返回0,您必须检查它。现在编码 - 级别触发的轮询 - epoll_wait(2)每次都返回,而不会等待您仍然没有读取该流末尾。

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

答案 1 :(得分:3)

客户端断开连接由文件描述符上的EOF条件发出信号。系统认为EOF是文件描述符“可读”的状态。但是,当然,无法读取EOF条件。这是循环的来源。 epoll的作用类似于断开连接的客户端的文件描述符始终可读。您可以通过检查read何时返回0字节读取来检测您是否具有EOF条件。

处理EOF条件的唯一方法是以某种方式close文件描述符。具体取决于事物的流向,可能是shutdown(sockfd, SHUT_RD)shutdown(sockfd, SHUT_RDWR)close(sockfd);

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