我正在为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()方法返回到外部代码(它们是使用第二个构造函数创建的)。现在这些是我们的麻烦制造者。