来自同一主机的多个连接会导致套接字断开连接问题

时间:2016-08-08 15:34:02

标签: c++ linux sockets tcp-ip

我正在为Linux编写C ++应用程序。它使用套接字进行TCP通信。我从远程主机读取数据如下(对于该主题的需求稍微简化):

Packet Connection::receiveRawData()
{
    int length = recv(socketDescriptor, receiveBuffer, MAXBUF, 0);
    if(length < 0)
    {
        std::cout << getConnectionDetails() << " - nothing to read.\n"; 
        return Packet(); //Empty packet object (with no data)
    }
    else if(length == 0)
    {
        std::cout << getConnectionDetails() << " - disconnected suddenly!\n";
        //Do something about this closed connection
    }

    return Packet(receiveBuffer, length);
}

getConnectionDetails()显示地址和端口号(如下所示:xx.xx.xx.xx:yy)所以我知道该消息是关于特定连接的(因为即使它们来自同一个主机,它们也会仍有不同的端口号)。 Couts只是为了瞄准目的。

问题是,当我从同一主机关闭其中一个连接时(即在与应用程序建立三个不同的连接之后,但它们都来自同一台机器),它不能很好地工作。只有当我以相反的顺序(从最新到最旧)关闭它时,它才能按预期工作 - 它显示“xx.xx.xx.xx:yy - 突然断开!”在应用程序的控制台中。但是,当我尝试从最旧到最新时,我不断得到“xx.xx.xx.xx:yy - 没有什么可读的。”对于每个连接,即使这些连接应该关闭!

发生了什么事?当远程主机关闭其连接时,recv()应该返回0,那么当我从最旧的连接器(从远程主机端)关闭连接时,为什么不会发生这种情况呢?从最旧的连接器(既不是最旧的也不是最新的)? / p>

当我关闭来自不同机器的连接时不会发生这种情况(因此没有机器与应用程序建立多个连接) - 这些操作的顺序无关紧要,它只是正常工作。

编辑:更多代码,以便您可以看到我如何设置连接套接字:

//Create "empty" socket object associated with the given port number
Connection::Connection(PortNumberFormat portNum)
: address(), port(portNum), socketDescriptor(socket(AF_INET, SOCK_STREAM, 0)), isConnected(false), isListening(false), isShuttingDown(false)
{
    if(socketDescriptor < 0)
        throw std::string("Fatal error: cannot create socket! The error was: " + std::string( strerror(errno) ));
}

//Create socket object using specified socket descriptor and mark it as connected to the server
//available on specified address and port
Connection::Connection(SocketDescriptor sock, char *addr, PortNumberFormat portNum)
: address(addr), port(portNum), socketDescriptor(sock), isConnected(true), isListening(false), isShuttingDown(false)
{
    setTimeouts();
}

Connection::~Connection()
{
    if(socketDescriptor < 0) return;
    if(!isShuttingDown) signalShutdown();
    close(socketDescriptor);
}

//Turn socket into listening mode by binding it to the specified port
void Connection::startListen()
{
    if(isConnected || isListening || socketDescriptor < 0)
        throw std::string("Socket can not be set as listening!");

    struct sockaddr_in bindData;
    int dummy = 1;
    memset(&bindData, 0, sizeof(bindData));
    bindData.sin_family = AF_INET;
    bindData.sin_addr.s_addr = INADDR_ANY;
    bindData.sin_port = htons(port);
    setsockopt(socketDescriptor, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(dummy)); //Release socket ASAP on closing
    if(bind(socketDescriptor, (struct sockaddr*)&bindData, sizeof(bindData)) < 0)
        throw std::string("Fatal error: cannot bind socket for listening! The error was: " + std::string( strerror(errno) ));
    if(listen(socketDescriptor, MAXCONN) < 0)
        throw std::string("Fatal error: cannot listen on bound socket! The error was: " + std::string( strerror(errno) ));
    isListening = true;
}

//Get new Connection object that represents external client connection
//This method will block until client connects or the socket shutdown occurs
Connection Connection::getClientConnection()
{
    if(!isListening || socketDescriptor < 0)
        throw std::string("Can't get connection from non-listening socket!");

    struct sockaddr_in clientData;
    socklen_t len = sizeof(clientData);
    SocketDescriptor clientSocket = accept(socketDescriptor, (struct sockaddr*)&clientData, &len);
    if(isShuttingDown) //An shutdown occured
    {
        if(clientSocket < 0)
            throw std::string("Information: listening connection is shutting down peacefully.");
        //Else...
        close(clientSocket); //Just to be extra sure
        throw std::string("Warning: listening connection is shutting down. Client connection has been discarded.");
    }
    if(clientSocket < 0)
        throw std::string("Fatal error: cannot open client connection socket! The error was: " + std::string( strerror(errno) ));
    return Connection(clientSocket, inet_ntoa(clientData.sin_addr), ntohs(clientData.sin_port));
}

//Invalidates socket and prepares it to be closed
void Connection::signalShutdown()
{
    if(socketDescriptor < 0) return;
    isShuttingDown = true;
    shutdown(socketDescriptor, SHUT_RDWR);
}

inline void Connection::setTimeouts()
{
    struct timeval timeout;      
    timeout.tv_sec = 2;
    timeout.tv_usec = 0;

    if(setsockopt(socketDescriptor, SOL_SOCKET, SO_RCVTIMEO, (void*)&timeout, sizeof(timeout)) < 0
        || setsockopt(socketDescriptor, SOL_SOCKET, SO_SNDTIMEO, (void*)&timeout, sizeof(timeout)) < 0)
    {
        //Do something about it
    }
}

std::string Connection::getConnectionDetails()
{
    return std::string(address + ":" + std::to_string(port));
}

使用第一个构造函数(仅限端口号)创建侦听连接,然后在其上调用startListen()。来自主机的连接使用getClientConnection()方法返回到外部代码(它们是使用第二个构造函数创建的)。现在这些是我们的麻烦制造者。

0 个答案:

没有答案