2

ノンブロッキング ソケットの特性をテストするための簡単な C/S アプリケーションを作成しました。サーバーとクライアントに関する簡単な情報を以下に示します。

//On linux The server thread will send 
//a file to the client using non-blocking socket       
void *SendFileThread(void *param){
    CFile* theFile = (CFile*) param;
    int sockfd = theFile->GetSocket();
    set_non_blocking(sockfd);
    set_sock_sndbuf(sockfd, 1024 * 64); //set the send buffer to 64K

    //get the total packets count of target file
    int PacketCOunt = theFile->GetFilePacketsCount();
    int CurrPacket = 0;
    while (CurrPacket < PacketCount){
        char buffer[512];
        int len = 0;

        //get packet data by packet no.
        GetPacketData(currPacket, buffer, len); 

        //send_non_blocking_sock_data will loop and send
        //data into buffer of sockfd until there is error
        int ret = send_non_blocking_sock_data(sockfd, buffer, len);
        if (ret < 0 && errno == EAGAIN){
            continue;
          } else if (ret < 0 || ret == 0 ){
             break;
         } else {
             currPacket++;
         }


         ......
     }
 }

//On windows, the client thread will do something like below
//to receive the file data sent by the server via block socket
void *RecvFileThread(void *param){
    int sockfd = (int) param; //blocking socket
    set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256

    while (1){
        struct timeval timeout;
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;

        fd_set rds;
        FD_ZERO(&rds);
        FD_SET(sockfd, &rds)'

        //actually, the first parameter of select() is 
        //ignored on windows, though on linux this parameter
        //should be (maximum socket value + 1)
        int ret = select(sockfd + 1, &rds, NULL, NULL, &timeout );
        if (ret == 0){
            // log that timer expires
            CLogger::log("RecvFileThread---Calling select() timeouts\n");
        } else if (ret) { 
            //log the number of data it received
            int ret = 0;
            char buffer[1024 * 256];
            int len = recv(sockfd, buffer, sizeof(buffer), 0);
            // handle error
            process_tcp_data(buffer, len);
        } else {
            //handle and break;
            break;
        }

    }
}

私が驚いたのは、サーバー スレッドがソケット バッファがいっぱいであるために頻繁に失敗することです。たとえば、14M サイズのファイルを送信すると、errno = EAGAIN で 50000 回の失敗が報告されます。ただし、ロギングを介して、転送中に数十回のタイムアウトがあることがわかりました。フローは次のようになります。

  1. N 回目のループでは、select() が成功し、256K のデータを正常に読み取ります。
  2. (N+1) 番目のループで、select() がタイムアウトで失敗しました。
  3. (N+2) 番目のループで select() が成功し、256K のデータを正常に読み取ります。

受信中にタイムアウトがインターリーブされるのはなぜですか? 誰でもこの現象を説明できますか?

[更新]
1. 14M のファイルをサーバーにアップロードするのに 8 秒しかかかりません
2. 1) と同じファイルを使用すると、サーバーはすべてのデータをクライアントに送信するのに約 30 秒かかります。
3. クライアントが使用するすべてのソケットがブロックされています。サーバーが使用するすべてのソケットはノンブロッキングです。

#2 に関しては、#2 が #1 よりもはるかに時間がかかる理由はタイムアウトであると思います。また、クライアントがデータの受信でビジー状態のときに、なぜこれほど多くのタイムアウトが発生するのだろうかと思います。

[UPDATE2]
@Duck、@ebrob、@EJP、@ja_mesa からのコメントに感謝します。今日はさらに調査を行い、この投稿を更新します。
サーバー スレッドでループごとに 512 バイトを送信する理由については、サーバー スレッドがデータを受信するクライアント スレッドよりもはるかに高速にデータを送信することがわかったためです。クライアントスレッドでタイムアウトが発生した理由について、私は非常に混乱しています。

4

2 に答える 2

0

recv()最初に電話をかけてから、そうするように指示された場合にselect()のみ電話する必要があります。recv()最初に呼び出さないでくださいselect()。処理の無駄です。 recv()データがすぐに利用可能かどうか、またはデータの到着を待つ必要があるかどうかがわかります。

void *RecvFileThread(void *param){
    int sockfd = (int) param; //blocking socket
    set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256

    char buffer[1024 * 256];

    while (1){

        int ret = 0;
        int len = recv(sockfd, buffer, sizeof(buffer), 0);
        if (len == -1) {
            if (WSAGetLastError() != WSAEWOULDBLOCK) {
                //handle error
                break;
            }

            struct timeval timeout;
            timeout.tv_sec = 1;
            timeout.tv_usec = 0;

            fd_set rds;
            FD_ZERO(&rds);
            FD_SET(sockfd, &rds)'

            //actually, the first parameter of select() is 
            //ignored on windows, though on linux this parameter
            //should be (maximum socket value + 1)
            int ret = select(sockfd + 1, &rds, NULL, &timeout );
            if (ret == -1) { 
                // handle error
                break;
            }

            if (ret == 0) {
                // log that timer expires
                break;
            }

            // socket is readable so try read again
            continue;
        }

        if (len == 0) {
            // handle graceful disconnect
            break;
        }

        //log the number of data it received
        process_tcp_data(buffer, len);
    }
}

送信側でも同様のことを行います。send()最初に呼び出し、次に書き込み可能になるまでselect()待機するようsend()に指示された場合にのみ呼び出します。

于 2013-07-02T00:12:15.240 に答える