2017-12-02 4 views
1

この質問はWhy is nonblocking socket writable before connect() or accept()?に続きます。受理サーバでTCPハンドシェイクの時間を長くすることはできますか?

次のコードは、TCP接続をリッスンするスレッドを生成します。メインスレッドは、サーバーが待機しているアドレスに接続します。

#include <iostream> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <fcntl.h> 
#include <unistd.h> 
#include <cerrno> 
#include <cstring> 
#include <pthread.h> 
#include <semaphore.h> 

class SafeSocket 
{ 
public: 

    /** Ctor. 
    * Creates a nonblocking socket at the specified IP in the AF_INET family and 
    * at a dynamic port. 
    */ 
    SafeSocket(const std::string& ip) 
    { 
    in_addr_t host_ip = inet_network(ip.c_str()); 
    if ((socket_ = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
     std::cout << "socket() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
     socket_ = -1; 
    } 
    sockaddr_in si; 
    memset(&si, 0, sizeof(si)); 
    si.sin_family = AF_INET; 
    si.sin_port = 0; // Dynamic port 
    si.sin_addr.s_addr = htonl(host_ip); 
    if (bind(socket_, (sockaddr*)&si, sizeof si)) 
    { 
     std::cout << "bind() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
     close(socket_); 
     socket_ = -1; 
    } 
    // Make the socket do nonblocking connect(). 
    int flags = fcntl(socket_, F_GETFL, 0); 
    fcntl(socket_, F_SETFL, flags | O_NONBLOCK); 
    } 

    ~SafeSocket() 
    { 
    if (socket_ >= 0) 
    { 
     shutdown(socket_, SHUT_RDWR); 
     close(socket_); 
    } 
    } 

    operator int() const 
    { 
    return socket_; 
    } 

private: 

    int socket_; 
}; 

int connectToClient(const SafeSocket& sock, const std::string& clientIp, 
        const int clientPort) 
{ 
    struct sockaddr_in clientAddr; 
    memset(&clientAddr, 0, sizeof clientAddr); 
    inet_pton(AF_INET, clientIp.c_str(), &clientAddr.sin_addr); 
    clientAddr.sin_family = AF_INET; 
    clientAddr.sin_port = htons(clientPort); 
    return connect(sock, (sockaddr*)&clientAddr, sizeof clientAddr); 
} 

std::string serverIp("127.0.0.200"); 
int serverPort = 9099; // Random, hopefully unused. 
sem_t listenSem; 

/** Entry point to pthread. 
*/ 
void* acceptConnection(void* arg) 
{ 
    int listenSock = socket(PF_INET, SOCK_STREAM, 0); 
    if (listenSock < 0) 
    { 
    std::cout << "socket() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
    return NULL; 
    } 

    sockaddr_in si; 
    si.sin_family = AF_INET; 
    inet_aton(serverIp.c_str(), &si.sin_addr); 
    si.sin_port = htons(serverPort); 

    int optval = 1; 
    setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval); 

    int result = bind(listenSock, (sockaddr*)&si, sizeof si); 
    if (result) 
    { 
    std::cout << "bind() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
    close(listenSock); 
    return NULL; 
    } 

    std::cout << "listening on socket " << listenSock << std::endl; 
    if (listen(listenSock, 3)) 
    { 
    std::cout << "listen() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
    close(listenSock); 
    return NULL; 
    } 

    sem_post(&listenSem); 

    fd_set readfds; 
    FD_ZERO(&readfds); 
    FD_SET(listenSock, &readfds); 
    struct timeval ts = { 5, 0 }; 
    if (-1 != select(listenSock + 1, &readfds, NULL, NULL, &ts)) 
    { 
    if (FD_ISSET(listenSock, &readfds)) 
    { 
     sockaddr_in peerSi; 
     socklen_t peerAddrLen = sizeof peerSi; 
     memset(&peerSi, 0, peerAddrLen); 
     sleep(3); 
     int acceptSock = accept(listenSock, (sockaddr*)&peerSi, &peerAddrLen); 
     if (acceptSock > 0) 
     { 
     std::cout << "accepted connection on socket " << acceptSock 
        << std::endl; 
     close(acceptSock); 
     } 
    } 
    else 
    { 
     std::cout << "did not receive a connection to accept." << std::endl; 
    } 
    } 
    close(listenSock); 
    return NULL; 
} 

int main(int argc, char* argv[]) 
{ 
    sem_init(&listenSem, 0, 0); 

    SafeSocket s("127.0.0.100"); 
    std::cout << "Created socket " << s << std::endl; 

    pthread_t tid; 
    pthread_create(&tid, NULL, acceptConnection, NULL); 

    timespec listenTimeout; 
    clock_gettime(CLOCK_REALTIME, &listenTimeout); 
    listenTimeout.tv_sec += 5; 
    sem_timedwait(&listenSem, &listenTimeout); 

    fd_set readFds; 
    fd_set writeFds; 

    FD_ZERO(&readFds); 
    FD_ZERO(&writeFds); 

    FD_SET(s, &writeFds); 

    timeval timeout = { 5, 0 }; 
    int result = connectToClient(s, serverIp, serverPort); 
    std::cout << "connectToClient() returned " << result << " " 
      << errno << " " << strerror(errno) << std::endl; 

    if (-1 == select(s+1, &readFds, &writeFds, NULL, &timeout)) 
    { 
    std::cout << "select() failed: " << errno << " " << strerror(errno) 
       << std::endl; 
    } 

    if (FD_ISSET(s, &writeFds)) 
    { 
    std::cout << s << " is writable!" << std::endl; 
    int result = -1; 
    socklen_t result_len = sizeof result; 
    getsockopt(s, SOL_SOCKET, SO_ERROR, &result, &result_len); 
    std::cout << "result: " << result << " " << strerror(result) << std::endl; 
    } 

    pthread_join(tid, NULL); 
    return 0; 
} 

出力:

>g++ --version 
g++ (GCC) 4.8.3 20140911 (Red Hat 4.8.3-7) 
Copyright (C) 2013 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

>g++ -g main.cpp 
> 
>./a.out 
Created socket 3            // Immediate 
listening on socket 4          // Immediate 
connectToClient() returned -1 115 Operation now in progress // Immediate 
3 is writable!            // Immediate 
result: 0 Success           // Immediate 
accepted connection on socket 5        // Delayed 

at the noted questionコメントEJPは、限りのため、このような状況では、select()ブロックと指摘、 "ターゲットTCPハンドシェイクです。"

この「TCPハンドシェイク」プロセスを長くする方法はありますか?私。 select()ブロックがソケットsを待つブロックが書き込み可能になるまでの時間を長くしたいと思います。これはできますか?受け入れる前にsleep()を追加することでこれを行おうとしましたが、うまくいかないことがあります。

答えて

2

ハンドシェイクは次の段階に従います。

  • 段階1:接続するには、クライアント上で実行されています。これは、少なくともクライアントがサーバーからSYN + ACKパケットを取得するまで、connectを呼び出すときに実行されます。少なくとも、クライアントカーネルがそのパケットを処理するのに時間がかかるからです。私の理解は、ステージ1の終わりまでソケットが書き込み可能でないことです。

  • ステージ2:ソケットはクライアント上で書き込み可能です。接続が行われます。サーバーは受け入れから戻っていません。これは、サーバがそのACKとacceptを呼び出すサーバーアプリケーションを受け取る(プラス加工し得るためにacceptへの呼び出しを待っている)

  • ステージ3の間の任意の時間だけでなく、クライアントからのACKを受信するために待機している時間を含み:両面を行った。

私はこれらのステージを作ってきました。私が描く区別はTCPステートマシンのあらゆる側面を追跡しないようにあなたの質問を議論することが容易に行えるように設計されています。

ステージ1を長くする有効な方法はありません。ステージ1にステージ2を含める方法はありません。クライアントのカーネルに遅延を追加することはできますが、それはネットワーク上で起こっていることとは無関係で任意です。

ステージ1とステージ2の時間の違いは、すべてサーバーのマシン内でのネットワーク遅延と処理に関するものです。クライアントが行動する可能性はありません。

多くのアプリケーションでは、ソケットが書き込み可能になるとすぐに書き込みを開始することが望ましいです。そうでない場合は、一般的に受け入れられている解決策は、サーバーにクライアントに最初の挨拶を送信させることです。次に、ソケットが読み込み可能になるのを待ってから、動作する前にその挨拶を処理します。

関連する問題