处理'非阻塞'套接字连接

时间:2016-03-04 10:48:06

标签: c++ sockets network-programming

我正在尝试编写一个使用非阻塞套接字连接的客户端,我对应该检查的内容以及以何种顺序感到困惑。我查看了Non blocking socket - how to check if a connection was successful问题并尝试实施它而没有运气。这是Windows,而不是Linux。我更喜欢使用Posix方法,所以我可以稍后移植它。

问题在于,即使服务器不存在,我也会看到EWOULDBLOCK。一旦服务器出现,我在服务器上看到多个连接,因此我没有正确处理“被阻止的”#39;尚未完成的连接。

连接代码是(并且在循环中调用,如果它不能直接连接,直到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;
}

谢谢:)

4 个答案:

答案 0 :(得分:1)

connect from msdn(仔细阅读返回值)

所以第一次尝试连接循环时,会得到SOCKET_ERROR和errno = WSAEWOULDBLOCK; (这意味着一切都好!)

如果你继续循环(再次连接),你得到SOCKET_ERROR和errno = WSAEINPROGRESS,所以不要再循环!

所以在获得WSAEWOULDBLOCK之后,正如所解释的那样,你可以,

  

使用select通过检查套接字是否可写来确定连接请求的完成。

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);

答案 1 :(得分:1)

调用import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.scene.control.Label; import javafx.scene.layout.VBox; import javafx.event.EventHandler; import javafx.scene.input.MouseEvent; import javafx.scene.shape.Circle; public class circle extends Application{ Scene scene; VBox v; public void start(Stage stage){ double x = 0; double y = 0; v = new VBox(10); scene = new Scene(v, 300, 300); scene.setOnMousePressed(new EventHandler<MouseEvent>(){ public void handle(MouseEvent e) { x = e.getX(); y = e.getY(); } }); Circle circle = new Circle(); circle.setCenterX(x); circle.setCenterY(y); v.getChildren().add(circle); stage.setScene(scene); stage.show(); } } 后,您必须立即致电GetLastError 。这两个函数之间的所有代码都可以清除或更改错误代码。

您的代码将输出写入这两个调用之间的流。如果该写入没有产生错误,则connect不会从正在进行的GetLastError中获取错误。

connect行移至errorCode = m_socketAdaptor->GetLastError();后面。{/ p>

答案 2 :(得分:0)

我不确定这是否有帮助,但我发现在Windows平台上使用connect()函数时它将返回一个WSAEWOULDBLOCK(我认为它与EWOULDBLOCK相同)。你必须在以后确定连接是否真的成功。如果你想要,你可以检查出https://msdn.microsoft.com/en-us/library/windows/desktop/ms737625%28v=vs.85%29.aspx(我所指的部分是“返回值”)

答案 3 :(得分:0)

  

问题在于,即使服务器不存在,我也会看到EWOULDBLOCK。

由于套接字处于非阻塞模式,因此connect()会在实际连接到服务器之前立即返回。因此,预计会获得EINPROGRESS。

没有m_socketAdaptor的代码,我可以产生这个,但我很确定问题在于:

  

连接代码是(并且在循环中被调用,如果它不能直接连接,直到100次尝试或它连接):

如果你已经调用了connect()一次并获得了(WSA)EINPROGRESS,那么你不应该再次调用connect,但是你应该在下面的迭代中执行select()部分。你也不应该在检查一次后关闭()。

所以你应该做的是:

  • 第一次通过 - 或者如果连接完全失败:

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

  • 第2至第100次传递或直到选择返回成功:

select()

  • 如果之前的通话失败

close() go back to 1st step