2016-03-27 124 views
0

如果之前有人問過這個問題,我很抱歉。我正在編寫一個使用選擇多路複用的非阻塞套接字客戶端。讓我困惑的一件事是,無論服務器在線還是離線,無阻塞連接都會成功。我搜索了很多帖子,並遵循他們的解決方案,但沒有一個在我的linux ubuntu機器上工作。非阻塞套接字連接總是成功?

static void callback_on_select_write(int connect_fd) { 

    // Client write event arrived; 

    int error = -1; 
    socklen_t len = sizeof(error); 

    if(getsockopt(connect_fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { 
    return; 
    } 

    // getsockopt puts the errno value for connect into erro so 0 means no-error. 
    if(error == 0) { 
     // Connection ok. 
    } 
    else { 
    cerr << "Failed to connect\n"; 
    return; 
    } 

    // Ready to write/read 

} 

每次選擇返回並調用這個回調總是成功,即,將「準備讀/寫」塊,而不是cerring故障。爲什麼會發生?如何設計便攜式機制來檢測連接是否真的成功?以下是我創建連接器的方式。

int make_socket_client(const ::std::string& hostname, const ::std::string& port) { 

    struct addrinfo hints; 
    struct addrinfo* res {nullptr}; 
    struct addrinfo* ptr {nullptr}; 

    memset(&hints, 0, sizeof(struct addrinfo)); 
    hints.ai_family = AF_UNSPEC; 
    hints.ai_socktype = SOCK_STREAM; 
    hints.ai_protocol = IPPROTO_TCP; 

    int rv; 
    int connector; 

    if((rv = getaddrinfo(hostname.c_str(), port.c_str(), &hints, &res)) != 0) { 
    return -1; 
    } 

    // Try to get the first available client connection. 
    for(ptr = res; ptr != nullptr; ptr = ptr->ai_next) { 

    // Ignore undefined ip type. 
    if(ptr->ai_family != AF_INET && ptr->ai_family != AF_INET6) { 
     continue; 
    } 

    // Create a listener socket and bind it to the localhost as the server. 
    if((connector = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1){ 
     continue; 
    } 

    make_fd_nonblocking(connector); 

    if(connect(connector, (struct sockaddr*)ptr->ai_addr, ptr->ai_addrlen) < 0) { 
     // This is what we expect. 
     if(errno == EINPROGRESS) { 
     break; 
     } 
     else { 
     close(connector); 
     continue; 
     } 
    } 
    else { 
     break; 
    } 
    } 

    freeaddrinfo(res); 

    if(ptr == nullptr) { 
    return -1; 
    } 

    return connector; 
} 
+0

當然,你應該看看'connect_fd'上的SO_ERROR?什麼是'事件'?此代碼不夠完整,無法回答問題。 – EJP

+0

對不起。它只應該是connect_fd。 – Jes

+0

原始'connect()'返回零嗎? – EJP

回答

0

每次選擇返回並調用這個回調總是 成功,即,將「準備讀/寫」塊,而不是 cerring故障。爲什麼會發生?

雖然異步TCP連接正在進行(如connect()調用中的-1/EINPROGRESS所示),但應將套接字傳遞給select()作爲其準備寫入套接字集的一部分,因此select()將在套接字指示它準備好寫入時返回。

當TCP連接成功或失敗時,select()將返回套接字準備好寫入(*)。發生這種情況時,需要確定發生了兩種可能的結果(成功或失敗)中的哪一種。

以下是我在異步連接套接字select()準備好寫入時調用的函數。

// call this select() has indicated that (fd) is ready-for-write because 
// (fd)'s asynchronous-TCP connection has either succeeded or failed. 
// Returns true if the connection succeeded, false if the connection failed. 
// If this returns true, you can then continue using (fd) as a normal 
// connected/non-blocking TCP socket. If this returns false, you should 
// close(fd) because the connection failed. 
bool FinalizeAsyncConnect(int fd) 
{ 
#if defined(__FreeBSD__) || defined(BSD) 
    // Special case for FreeBSD7, for which send() doesn't do the trick 
    struct sockaddr_in junk; 
    socklen_t length = sizeof(junk); 
    memset(&junk, 0, sizeof(junk)); 
    return (getpeername(fd, (struct sockaddr *)&junk, &length) == 0); 
#else 
    // For most platforms, the code below is all we need 
    char junk; 
    return (send(fd, &junk, 0, 0L) == 0); 
#endif 
} 

(*)附註:事情是Windows下略有不同,因爲Windows喜歡做的事情自己的路:在Windows下,一個成功的異步連接()如上述表示,但如果你想在Windows下嘗試失敗的異步連接()嘗試時,您需要在「except」fd_set下注冊套接字,因爲它將用於傳遞失敗的異步連接()的「fd_set」除外。

+0

謝謝你,傑里米。但是,在我的OSX系統中,調用send與peer處於脫機狀態會導致SIGPIPE錯誤,程序退出,函數FinalizeAsyncConnect永遠不會返回。我怎樣才能解決這個問題? – Jes

+1

通過調用main()頂部的信號(SIGPIPE,SIG_IGN)來禁用SIGPIPE,如下所述:http://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly/108192#108192 –