我正在为我的代码库编写TCP通信库。我已经用一个acceptor类(在服务器上运行)和连接器类(在客户端上运行)和一个流类(处理发送和接收数据)实现了这个库。
在大多数情况下,一切正常。我遇到的一个问题是,如果我断开客户端和服务器之间的以太网线路,并尝试连接,我会收到目标无法访问的错误。在进一步尝试连接时,无论是否连接了以太网电缆,我都会收到操作正在进行的错误。
请注意,如果调用connect,即使重新连接,一切都可以正常连接。
另外,请注意我在连接期间的代码中将套接字选项设置为SO_REUSEADDR,我使用的是SO_LINGER,并且在连接失败后我将关闭所有套接字。
TCPConnector类:
/** @file TCPConnector.cpp
* @brief cpp file to TCPConnector class, which encapsulates the socket mechanisms to actively
* connect to a server.
* @author Austin Small.
*/
#include "TCPConnector.h"
#include <iostream>
#include <errno.h>
/** @brief This method establishes a connection with the server (robot).
*
* @param server Server IP address.
* @param port Server port number.
* @param timeoutSec Number of seconds before timout of connect method.
*/
TCPStream* TCPConnector::connect(const char* serverIP, int port, int timeoutSec)
{
std::cout << "connect was called" << std::endl;
struct sockaddr_in address;
// Store all zeros for address struct.
memset(&address, 0, sizeof(address));
// Configure address struct.
address.sin_family = AF_INET;
address.sin_port = htons(port); // Convert from host to TCP network byte order.
inet_pton(PF_INET, serverIP, &(address.sin_addr)); // Convert IP address to network byte order.
// Create a socket. The socket signature is as follows: socket(int domain, int type, int protocol)
int sd = socket(AF_INET, SOCK_STREAM, 0);
int optval = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) == -1)
{
std::cout << "failed to set socket option" << std::endl;
}
// Set socket to terminate all communications when close is called.
struct linger so_linger;
so_linger.l_onoff = true;
so_linger.l_linger = 0;
if (setsockopt(sd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger) == -1)
{
std::cout << "failed to set socket option" << std::endl;
}
// Set socket to be non-blocking.
int arg;
arg = fcntl(sd, F_GETFL, NULL);
arg |= O_NONBLOCK;
fcntl(sd, F_SETFL, arg);
// Connect with time limit.
fd_set set;
FD_ZERO(&set); // Clear the set.
FD_SET(sd, &set); // Add our file descriptor to the set.
struct timeval timeout;
timeout.tv_sec = timeoutSec;
timeout.tv_usec = 0;
// If the connect call returns 0, then the connection was established. Otherwise,
// check if the three-way handshake is underway.
if (::connect(sd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
// If the handshake is underway.
if (errno == EINPROGRESS)
{
std::cout << "handshake in progress" << std::endl;
// Designate timeout period.
int ret = select(sd + 1, NULL, &set, NULL, &timeout);
std::cout << "return value from select : " << ret << std::endl;
// Check if timeout or an error occurred.
if (ret <= 0)
{
std::cout << "return less than 0" << std::endl;
std::cout << "closing socket descriptor" << std::endl;
if (close(sd) < 0)
{
char * newerrorMessage = strerror( errno); // get string message from errn
std::string newmsg (newerrorMessage);
std::cout << newmsg << std::endl;
std::cout << "failed to close socket descriptor" << std::cout;
}
return NULL;
}
else
{
// Check if select returned 1 due to an error.
int valopt;
socklen_t len = sizeof(int);
getsockopt(sd, SOL_SOCKET, SO_ERROR, (void*)(&valopt), &len);
if (valopt)
{
char * errorMessage = strerror( errno); // get string message from errn
std::string msg (errorMessage);
std::cout << msg << std::endl;
std::cout << "closing socket descriptor" << std::endl;
if (close(sd) < 0)
{
char * newerrorMessage = strerror( errno); // get string message from errn
std::string newmsg (newerrorMessage);
std::cout << newmsg << std::endl;
std::cout << "failed to close socket descriptor" << std::cout;
}
return NULL;
}
}
}
else
{
std::cout << "error but not EINPROGRESS" << std::endl;
char * errorMessage = strerror( errno); // get string message from errn
std::string msg (errorMessage);
std::cout << msg << std::endl;
if (close(sd) < 0)
{
char * newerrorMessage = strerror( errno); // get string message from errn
std::string newmsg (newerrorMessage);
std::cout << newmsg << std::endl;
std::cout << "failed to close socket descriptor" << std::cout;
}
return NULL;
}
}
// Return socket to blocking mode.
arg = fcntl(sd, F_GETFL, NULL);
arg &= (~O_NONBLOCK);
fcntl(sd, F_SETFL, arg);
// Create stream object.
return new TCPStream(sd, &address);
}
TCPStream类:
/** @file TCPStream.cpp
* @brief cpp file to TCPStream class, which provides methods to send and receive
* data over a TCP/IP connection.
* @author Austin Small.
*/
#include "TCPStream.h"
#include <iostream>
/** @brief TCPStream class constructor.
*
* @param argsd Socket descriptor.
* @param address sockaddr_in struct.
*/
TCPStream::TCPStream(int argsd, struct sockaddr_in* address) :
sd(argsd)
{
char ip[50];
// Convert a numeric address into a text string.
// struct sockaddr_in
// {
// short sin_family;
// unsigned short sin_port;
// struct in_addr sin_addr;
// char sin_zero[8];
// };
//
inet_ntop(PF_INET, (struct in_addr*)&(address->sin_addr.s_addr), ip, sizeof(ip));
peerIP = ip;
// Convert from network byte order to host byte order.
peerPort = ntohs(address->sin_port);
}
/** @brief TCPComputerComm class destructor.
*
*/
TCPStream::~TCPStream()
{
std::cout << "closing fd" << std::endl;
if (close(sd) < 0)
{
std::cout << "file descriptor not closed successfully" << std::endl;
}
}
/** @brief Wrapper function to send data.
*
* @param buffer Pointer to first character of string.
* @param len Size of input string.
* @param timeoutSec Timeout period for write command.
*
* @return Number of bytes written, -1 if a non-timeout error occured, and -2 if a timeout occurred.
*/
ssize_t TCPStream::send(const char* buffer, size_t len, int timeoutSec)
{
// Attempt to send data with a timeout on write.
fd_set set;
FD_ZERO(&set); // Clear the set.
FD_SET(sd, &set); // Add our file descriptor to the set.
struct timeval timeout;
timeout.tv_sec = timeoutSec;
timeout.tv_usec = 0;
int ret;
ret = select(sd + 1, NULL, &set, NULL, &timeout);
// First check if an error or timeout occurred. Otherwise, call accept method.
if (ret == -1)
{
return -1;
}
else if (ret == 0)
{
return -2;
}
else
{
return write(sd, buffer, len);
}
}
/** @brief Wrapper function to receive data.
*
* @param buffer Pointer to first character of buffer to store received string.
* @param len Max number of bytes to read from file descriptor.
* @param timeoutSec Timeout period for read command.
*
* @return Number of bytes read or -1 if a non-timeout error occurred and -2 if a timeout occurred.
*/
ssize_t TCPStream::receive(char* buffer, size_t len, int timeoutSec)
{
// Attempt to send data with a timeout on write.
fd_set set;
FD_ZERO(&set); // Clear the set.
FD_SET(sd, &set); // Add our file descriptor to the set.
struct timeval timeout;
timeout.tv_sec = timeoutSec;
timeout.tv_usec = 0;
int ret;
ret = select(sd + 1, &set, NULL, NULL, &timeout);
// First check if an error or timeout occurred. Otherwise, call read method.
if (ret == -1)
{
return -1;
}
else if (ret == 0)
{
return -2;
}
else
{
//std::cout << "attempting to read" << std::endl;
return read(sd, buffer, len);
}
}
/** @brief Get peerIP address.
*
* @return peerIP address.
*/
string TCPStream::getPeerIP(void)
{
return peerIP;
}
/** @brief Get peer port.
*
* @return Peer port.
*/
int TCPStream::getPeerPort(void)
{
return peerPort;
}