C ++ Socket select()在accept()之前多次看到相同的连接

时间:2016-02-22 16:58:40

标签: c++ sockets

我有一个listen函数,用于检查新连接是否在服务器套接字上等待,如果我先启动客户端然后启动服务器,我看到了新的连接'多次,如果我启动服务器然后客户端,我会看到它一次。

我是否缺少一些关于Posix标准的知识或者缺乏知识(最有可能)?

轮询新连接的功能是:

bool IPV4Socket::HasNewConnection( TimeoutValue *timeout )
{
    SocketSet read_fd_set;
    bool hasConnection = false;

    SocketSet_ZERO( &read_fd_set );
    SocketSet_SET( m_socket, &read_fd_set );

    if ( m_socketAdaptor->select( m_socket + 1
                                , &read_fd_set
                                , NULL
                                , NULL
                                , timeout ) < 0 )
    {
        throw( std::string( "::select() failed, unable to continue." ) );
    }

    for ( unsigned int i = 0; i <= m_socket; i++ )
    {
        if ( m_socketAdaptor->SocketSet_ISSET( i, &read_fd_set ) &&
             ( i == m_socket ) )
        {
            hasConnection = true;
        }
    }

    return hasConnection;
}

使用以下方式调用:

while( 1 )
{
    if ( socket->HasNewConnection( &newConnTimeout ) )
    {
        std::cout << "[INFO] A new connection is waiting...." << std::endl;

        acceptedSocket = socket->Accept( &newConnTimeout );

        if ( acceptedSocket )
        {
            std::cout << "[INFO] New connection received...." << std::endl;
        }
    }
}

连接功能是:

while( connStatus == false )
{
    socket = socketlayer->CreateSocket( socketlayer::SockType_stream );

    socket->SetNonBlocking( true );
    socket->SetSocketOption( socketlayer::SocketOption_KeepAlive, true );
    socket->SetSocketOption( socketlayer::SocketOption_ReuseAddr, true );

    connStatus = socket->Connect( "127.0.0.1", 18000, &tv );

    if ( connStatus == false ) socket->Close();
}

因为这是一段开源代码,可以在Sourceforge

中找到

通过以下方式完成连接:

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

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

    if ( connReturn == SocketError)
    {
        int m_lastErrorCode = m_socketAdaptor->GetLastError();

        //  Connection error : FATAL 
        if ( ( m_lastErrorCode != SockErr_EWOULDBLOCK) &&
             ( m_lastErrorCode != SockErr_EALREADY ) && 
             ( m_lastErrorCode != SockErr_EINPROGRESS ) )
        {
            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
                                            , NULL
                                            , timeout );

    //  Check for Socket Error or timeout
    if ( ( selectReturn == SocketError ) || ( selectReturn == 0 ) )
    {
        return false;
    }

    return true;
}

2 个答案:

答案 0 :(得分:1)

套接字设置为非阻塞,对connect的调用通常会返回错误,因为无法确定连接状态:

  

使用非阻塞套接字,无法立即完成连接尝试。在这种情况下,connect将返回SOCKET_ERROR,WSAGetLastError将返回WSAEWOULDBLOCK。 (...)

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

鉴于您的客户端代码和上述文档,它永远不能成功连接。但是,由于您使用环回设备进行连接,因此在服务器已经运行的情况下,由于时间安排,连接会立即被接受(并且确实发生)。

因此,如上所述,在select调用后在客户端上使用connect,或者只使用阻塞套接字。

答案 1 :(得分:1)

HasNewConnection()超时时,

select()忽略,并且它的循环完全是冗余的,因为它一次只在一个套接字上等待。代码可以简化为以下内容:

bool IPV4Socket::HasNewConnection( TimeoutValue *timeout )
{
    SocketSet read_fd_set;

    SocketSet_ZERO( &read_fd_set );
    SocketSet_SET( m_socket, &read_fd_set );

    int ret = m_socketAdaptor->select( m_socket + 1
                                , &read_fd_set
                                , NULL
                                , NULL
                                , timeout );
    if (ret < 0)
    {
        throw( std::string( "::select() failed, unable to continue." ) );
    }

    return (ret > 0);
}

话虽如此,我只是查看Accept()的代码(请不要让人们查看外部网站的代码,请始终将其放在您的实际问题中,它属于哪里)并且我看到一个大错误。您对SocketSet的两个不同参数使用相同的select()变量,因此当select()退出时,该变量的内容将不确定。如果您想同时检查read/writeexcept参数,则需要使用单独的变量(就像在Connect()中一样):

iSocket *IPV4Socket::Accept( TimeoutValue *timeout )
{
    SocketSet readFDS;
    SocketSet exceptFDS;
    Socket newSocketHandle = 0;

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

    //  Add listening socket to the FDS structures
    SocketSet_SET( m_socket, &readFDS );
    SocketSet_SET( m_socket, &exceptFDS );

    if ( m_socketAdaptor->select( m_socket + 1, &readFDS, NULL, &exceptFDS, timeout ) > 0 )
    {
        if ( !m_socketAdaptor->SocketSet_ISSET( m_socket, &exceptFDS ) )
        {
            newSocketHandle = m_socketAdaptor->accept( m_socket, NULL, NULL );

            if ( newSocketHandle != Invalid_Socket )
            {
                return new IPV4Socket( newSocketHandle, m_socketType, m_socketAdaptor );
            }
        }
    }

    return NULL;
}

但是,对于accept()来说,这是相当多余的,您根本不需要使用except参数(这对connect()有意义,但不是accept() }):

iSocket *IPV4Socket::Accept( TimeoutValue *timeout )
{
    SocketSet readFDS;
    Socket newSocketHandle = 0;

    //  Clear the socket FDS structure
    SocketSet_ZERO( &readFDS );

    //  Add listening socket to the FDS structure
    SocketSet_SET( m_socket, &readFDS );

    if ( m_socketAdaptor->select( m_socket + 1, &readFDS, NULL, NULL, timeout ) > 0 )
    {
        newSocketHandle = m_socketAdaptor->accept( m_socket, NULL, NULL );

        if ( newSocketHandle != Invalid_Socket )
        {
            return new IPV4Socket( newSocketHandle, m_socketType, m_socketAdaptor );
        }
    }

    return NULL;
}