3

を使用してソケットサーバーを実装しようとしていますepoll。2つのタスクを実行する2つのスレッドがあります。

  1. 着信接続をリッスンする
  2. クライアントが送信しているデータを画面に書き込みます。

私のテストでは、クライアントとサーバーを同じマシン上に置き、3つまたは4つのクライアントを実行しています。サーバーは、:を発行してクライアントの1つを強制終了しなくなるまで正常に動作しCTRL-Cます。強制終了すると、サーバーは他のクライアントからの非常に高速なデータのループと印刷を開始します。奇妙なことはそれです

  1. クライアントは2秒ごとにデータを送信しますが、サーバーのレートは高くなります
  2. epoll_waitまた、EPOLLHUPまたはEPOLLERRもチェックしているため、クライアントの1つが切断されたときに何かを印刷することになっています。
  3. epoll_wait私が彼に3000ミリ秒のタイムアウトを与えたので、印刷する前に少し待つ必要があります。

手伝ってくれますか?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
4

2 に答える 2

6

クライアントの切断は、ファイル記述子の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エラーが発生する可能性があります。もう1つはepoll、リストに追加する前に、同じ数値を持つ新しいファイル記述子のイベントのレポートを不思議に開始することですepoll

于 2013-01-28T14:07:16.367 に答える
3

反対側できれいに閉じられたソケットは読み取り可能になり、read(2)戻り0ます。それを確認する必要があります。今コード化されているように(レベルトリガーポーリング)epoll_wait(2)は、ストリームの終わりをまだ読んでいないことを通知するのを待たずに毎回戻ります。

または、エッジトリガーポーリング(EPOLLET)に切り替えて、それに反応することEPOLLRDHUPもできます。

于 2013-01-28T13:42:32.277 に答える