2016-03-04 191 views
1

我想寫一個客戶端連接使用非阻塞套接字,我很困惑,我應該檢查和以什麼順序。我看着Non blocking socket - how to check if a connection was successful的問題,並試圖實現它沒有運氣。這是Windows,而不是Linux。我更喜歡使用Posix方法,所以我可以稍後再移植它。處理'非阻塞'套接字連接

問題是,即使服務器不存在,我也會看到EWOULDBLOCK。一旦服務器出現,我在服務器上看到多個連接,因此我沒有正確處理尚未完成的「已阻止」連接。

的連接碼是(並且被稱爲一個循環中,如果它不能連接直線距離,直到100次嘗試或它連接):

bool IPV4Socket::Connect(std::string hostname 
         , unsigned short remotePort 
         , TimeoutValue *timeout) 
{ 
    AddrInfo getResults; 
    AddrInfo getaddrinfoHints; 
    int connReturn = 0; 
    SockAddr_In *addrData; 
    std::string service = std::to_string(remotePort); 
    int errorCode = 0; 

    getaddrinfoHints.ai_family = AddressFamily_inet; 
    getaddrinfoHints.ai_socktype = SockType_stream; 

    if (m_socketAdaptor->getaddrinfo(hostname 
            , service 
            , &getaddrinfoHints 
            , &getResults) != 0) 
    { 
     return false; 
    } 

    addrData = (SockAddr_In *)&(*getResults.ai_addr.begin()); 

    connReturn = m_socketAdaptor->connect(m_socket 
             , (const Sockaddr *)addrData 
             , (int)getResults.ai_addrlen); 

    static int staticLastErr = 0; 
    static int staticConnStat = -2048; 

    if (connReturn == 0) 
    { 
     m_isConnected = true; 
     return true; 
    } 

    if (connReturn != staticConnStat) 
    { 
     std::cout << "[DEBUG] IPV4Socket::Connect() ::Connect() returned : " << connReturn << std::endl; 
     staticConnStat = connReturn; 
    } 

    // Check if the error is fatal - e.g. not blocking related! 
    if (connReturn == SocketError) 
    { 
     errorCode = m_socketAdaptor->GetLastError(); 

     // Check for fatal connection error 
#ifdef LIBSSL_OS_WIN32 
     if (errorCode != SockErr_EWOULDBLOCK) 
#else 
     if (errorCode != SockErr_EINPROGRESS) 
#endif 
     { 
      Close(); 
      return false; 
     } 
    } 

    SocketSet writeFDS; 
    SocketSet exceptFDS; 
    int selectReturn = 0; 

    // Clear all the socket FDS structures 
    SocketSet_ZERO(&writeFDS); 
    SocketSet_ZERO(&exceptFDS); 

    // Put the socket into the FDS structures 
    SocketSet_SET(m_socket, &writeFDS); 
    SocketSet_SET(m_socket, &exceptFDS); 

    selectReturn = m_socketAdaptor->select(m_socket + 1 
              , NULL 
              , &writeFDS 
              , &exceptFDS 
              , timeout); 

    // select() failed or timed out, connection wasn't successful! 
    if ((selectReturn == SocketError) || (selectReturn == 0)) 
    { 
     if (selectReturn != 0) std::cout << "[DEBUG] m_socketAdaptor->select() returned : " << selectReturn << std::endl; 
     Close(); 
     return false; 
    } 

    // Check for error (exception) first 
    if (m_socketAdaptor->SocketSet_ISSET(m_socket, &exceptFDS)) 
    { 
     std::cout << "[DEBUG] ::Connect() found excetion on 'm_socketAdaptor->SocketSet_ISSET'" << std::endl; 
     Close(); 
     return false; 
    } 

    if (m_socketAdaptor->SocketSet_ISSET(m_socket, &writeFDS)) 
    { 
     std::cout << "[DEBUG] ::Connect() m_socketAdaptor->SocketSet_ISSET(m_socket, &writeFDS)  [FOUND]" << std::endl; 
     m_isConnected = true; 
     return true; 
    } 

    Close(); 
    return false; 
} 

關閉功能:

int IPV4Socket::Close() 
{ 
    int errNo = -1; 

    if (m_socket >= 0) 
    { 
     errNo = m_socketAdaptor->shutdown(m_socket, ShutdownFlag_Both); 
     if (errNo < 0) 
     { 
      int lastError = m_socketAdaptor->GetLastError(); 
      if (lastError != SockErr_ENOTCONN && lastError != SockErr_EINVAL) return lastError; 
     } 

     errNo = m_socketAdaptor->closesocket(m_socket); 
     if (errNo < 0) return m_socketAdaptor->GetLastError(); 
    } 

    return 0; 
} 

更新連接使用註釋:

bool IPV4Socket::Connect(std::string hostname 
         , unsigned short remotePort 
         , TimeoutValue *timeout) 
{ 
    bool connectReturn = false; 

    if (m_incompleteConnect == false) 
    { 
     connectReturn = PerformConnect(hostname, remotePort); 
    } 

    // If connect failed (returned false) then abort! 
    if (connectReturn == false) return false; 

    // If Connect() returned success, but didn't connect, it is because of a 
    // blocking IO not completing in time and needs to be retried, otherwise a 
    // connection was successful and just return success. 
    if (connectReturn && m_isConnected) return true; 

    m_incompleteConnect = true; 

    fd_set writeFDS; 
    fd_set exceptFDS; 

    // Clear all the socket FDS structures 
    FD_ZERO(&writeFDS); 
    FD_ZERO(&exceptFDS); 

    // Put the socket into the FDS structures 
    FD_SET(m_socket, &writeFDS); 
    FD_SET(m_socket, &exceptFDS); 

    int selectReturn = ::select(m_socket + 1 
           , NULL 
           , &writeFDS 
           , &exceptFDS 
           , (const timeval *)timeout); 

    // Check if ::select() has timed out, if so, connection wasn't successful! 
    if (selectReturn == 0) 
    { 
     m_incompleteConnect = false; 
     return false; 
    } 

    if (FD_ISSET(m_socket, &writeFDS)) 
    { 
     m_isConnected = true; 
     m_incompleteConnect = false; 
    } 

    // Check for error (exception) 
    if (FD_ISSET(m_socket, &exceptFDS)) 
    { 
     m_incompleteConnect = false; 
     return false; 
    } 

    return m_isConnected; 
} 


bool IPV4Socket::PerformConnect(std::string hostname, int port) 
{ 
    addrinfo *results; 
    addrinfo hints; 
    int connReturn = 0; 
    std::string service = std::to_string(port); 
    int errorCode = 0; 
    bool returnValue = false; 

    memset(&hints, 0, sizeof hints); 
    hints.ai_family = AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 

    if (::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &results) != 0) 
    { 
     return false; 
    } 

    // Attempt the connection... 
    connReturn = ::connect(m_socket, results->ai_addr, results->ai_addrlen); 

    // If connect returned error (SOCKET_ERROR), check that it's not fatal - 
    // e.g. EWOULDBLOCK, if it is then connect can check until complete! 
    if (connReturn == SOCKET_ERROR) 
    { 
     int errorCode = WSAGetLastError(); 
     returnValue = (errorCode == WSAEWOULDBLOCK) ? true : false; 
    } 
    else 
    { 
     m_isConnected = true; 
     returnValue = true; 
    } 

    return returnValue; 
} 

謝謝:)

+0

'不運氣'不是問題描述。 – EJP

+0

@EJP添加額外的'描述'的問題。 –

+1

您會首先看到EWOILDBLOCK,無論目標是否存在。在connect()返回後發生的所有事情是SYN已經排隊等待傳遞。它是以下'select()'和錯誤檢查,告訴你連接是否成功,失敗,超時等。 – EJP

回答

0

我不知道這是否有幫助,但我發現,在Windows平臺上,當你使用connect()函數時,它將返回一個WSAEWOULDBLOCK(我認爲它與EWOULDBLOCK相同)。而且你必須確定在以後的時間,如果連接竟是成功或not.If你想你可以看看這個https://msdn.microsoft.com/en-us/library/windows/desktop/ms737625%28v=vs.85%29.aspx(我指的部分是,「返回值」)

+0

謝謝你,不幸的是,如果你看問題中的代碼,我使用select()檢查寫入能力,這並沒有幫助。 –

+0

在你已發佈的代碼行錯誤發生了嗎?我的意思是EWOULDBLOCK錯誤 –

+0

我不是100%發生失敗的地方 –

0

的問題是在Windows中,即使服務器不存在,我也會看到EWOULDBLOCK。

由於套接字處於非阻塞模式,所以connect()在實際連接到服務器之前立即返回。因此預計會得到EINPROGRESS。

而不必爲m_socketAdaptor的代碼,我can'reproduce這一點,但我敢肯定問題就出在這:

連接的代碼是(和被稱爲一個循環中,如果可以」 t直接連接,直到100嘗試或它連接):

如果你已經調用connect()一次並得到(WSA)EINPROGRESS,你不應該再次調用connect,但你應該只是做select( )部分在以下迭代。你也不應該在檢查一次後關閉()。

所以,你應該做的是這樣的:

  • 第1次 - 或者,如果連接完全失敗:

getaddrinfo(); connect(); select();

  • 第二-100通過或直到select返回成功:

select()

  • 如果先前調用失敗

close() go back to 1st step

+0

在Windows上,無論您是否連接,看起來您總是會獲得EINPROGRESS? –

+0

正確。不僅在Windows上,而且在Linux上。這是預期的非阻塞套接字行爲。 MS-Windows正確地模擬了非阻塞套接字的傳統Berkeley套接字API行爲。這就是它應該如何工作。 –

+0

@SamVarshavchik我已經修改了我的代碼(請參見問題),但我仍然看到多個連接...好像不能正確處理連接仍然 –

0

connect from msdn(請仔細閱讀返回值)

所以第一次嘗試在你的循環連接,你就會得到SOCKET_ERROR和errno = WSAEWOULDBLOCK; (這意味着一切都OK !!)

如果你保持循環(再次連接)你得到SOCKET_ERROR和errno = WSAEINPROGRESS,所以不要再循環!

因此讓WSAEWOULDBLOCK,作爲解釋,就可以了,之後

使用選擇通過檢查插座是否可寫來確定連接請求的完成。

fd_set wr_set; 
struct timeval timeout={10,1}; 

int err=WSAConnect(s,(struct sockaddr*)&addr,len,0,0,0,0); 
printf("connect: %d %d\n",err,WSAGetLastError()); 


FD_ZERO(&wr_set); 
FD_SET(s, &wr_set); 

printf("%d\n",wr_set.fd_count); 
select(s,0,&wr_set,0,&timeout); <- blocks (10s) only if can't get socket writable 
printf("%d\n",wr_set.fd_count); 
0

你必須調用GetLastError立即調用connect後。這兩個函數之間的所有代碼都可以清除或更改錯誤代碼。

您的代碼將輸出寫入這兩個調用之間的流中。如果該寫入沒有產生錯誤,GetLastError將不會從正在進行的connect中收到錯誤。

errorCode = m_socketAdaptor->GetLastError();行移動到connect之後。