2017-12-02 2 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; 
} 

는 출력 : at the noted question 댓글을 달았습니다

>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 

EJP은이 상황에서 같은 오랫동안의 select() 블록 "대상 TCP는 핸드 쉐이크입니다."주목

이 "TCP 핸드 셰이 킹"프로세스를 길게하는 방법이 있습니까? 나는. select() 블록이 소켓 s을 기다리는 동안 쓰기가 가능하게되는 시간을 늘리고 싶습니다. 이 작업을 수행 할 수 있습니까? 수락하기 전에 sleep()을 추가하여이 작업을 시도했지만 작동하지 않는 것을 볼 수 있습니다.

답변

2

핸드 쉐이크는 다음과 같은 단계를 다음과

  • 1 단계 : 연결은 클라이언트에서 실행됩니다. 이것은 적어도 클라이언트가 서버로부터 SYN + ACK 패킷을받을 때까지 connect를 호출 할 때부터 실행됩니다. 적어도 클라이언트 커널이 패킷을 처리하는 데 시간이 걸릴 수 있으므로 적어도 말합니다. 내 이해는 소켓 1 단계가 끝날 때까지 쓸 수 없다는 것입니다.

  • 2 단계 : 소켓이 클라이언트에서 쓰기 가능; 연결이 완료되었습니다. 서 v가 승인에서 리턴하지 않았습니다. 이것은 서버가 ACK와 accept를 호출하는 서버 응용 프로그램을 수신 (및 처리하려면 accept에 그 전화를 기다리고)

  • 3 단계 사이의 시간뿐만 아니라 클라이언트에서 ACK를 수신 대기하는 시간이 포함됩니다 : 양면 다.

나는이 단계들을 구성했다. 나는 그 차이를 TCP 상태 머신의 어떤 부분도 따르지 않도록 당신의 질문에 대해 쉽게 토론 할 수 있도록 고안되었다.

스테이지 1을 길게 만드는 유용한 방법은 없으며 스테이지 1이 스테이지 2를 포함하도록 만들 수 없습니다. 클라이언트의 커널에 약간의 지연을 추가 할 수는 있지만 이는 네트워크에서 일어나는 일과 관련이 없으며 임의적입니다.

1 단계와 2 단계 사이의 시간차는 모두 서버 시스템에서 네트워크 대기 시간과 처리에 관한 것입니다. 클라이언트가 행동 할 수있는 방아쇠가 없습니다.

많은 응용 프로그램에서 소켓 쓰기 가능 상태가되는 즉시 쓰기를 시작하는 것이 바람직합니다. 그렇지 않은 경우 일반적으로 허용되는 솔루션은 프로토콜에서 서버가 클라이언트에 초기 인사말을 보내도록하는 것입니다. 그런 다음 소켓이 읽을 수있을 때까지 기다렸다가 연기하기 전에 인사말을 처리합니다.

관련 문제