我编写了两个封装TCP套接字的C ++类。我的超类是TCPSocket类,它提供了套接字如何操作的基本语义:bind,send,recv等。我的第二个类是一个名为TCPClient的子类,它提供了连接功能。
我遇到的问题是connect()函数返回EINVAL。我有一台开发机器,我对这些类进行了单元测试,它们与我编写的虚拟服务器完美配合。但是,当我在目标生产系统上运行它时,它会因EINVAL而失败。
这里有相当多的代码所以请耐心等待。第一个是基类TCPSocket.hpp。我省略了send和recv函数以及getter / setter函数。
#ifndef __TCP_SOCKET_HPP__
#define __TCP_SOCKET_HPP__
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <string>
#include <vector>
#include "SocketException.hpp"
using namespace std;
class TCPSocket
{
public:
TCPSocket();
TCPSocket(int sockfd);
~TCPSocket();
TCPSocket(const TCPSocket& other);
TCPSocket& operator = (const TCPSocket& other);
virtual void closeSocket();
bool isValid() const;
bool isNonBlocking() const;
bool setNonBlocking(bool value);
...
int getSockFd() const;
protected:
int m_sockfd;
int m_saved_errno;
string m_error_string;
string m_local_hostname;
string m_local_service;
string m_remote_hostname;
string m_remote_service;
bool m_allow_non_blocking;
bool forceSetNonBlocking(bool value);
void obtainNewSocket();
bool bindAddress();
private:
size_t recv_buffer_size;
};
#endif
以下是源文件TCPSocket.cpp。我再一次省略了不必要的功能。
#include "TCPSocket.hpp"
TCPSocket::TCPSocket() : m_sockfd(-1), m_allow_non_blocking(true), recv_buffer_size(1024)
{
obtainNewSocket();
}
TCPSocket::TCPSocket(int sockfd) : m_allow_non_blocking(true), recv_buffer_size(1024)
{
int sock_type;
socklen_t type_length = sizeof(sock_type);
if(getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &sock_type, &type_length) == -1)
{
throw SocketException("Error retrieving socket information", errno);
}
if(sock_type != SOCK_STREAM)
{
throw SocketException("Socket is not of type SOCK_STREAM", EBADF);
}
m_sockfd = dup(sockfd);
if(m_sockfd == -1)
{
throw SocketException("Failed to duplicate socket file descriptor", errno);
}
}
TCPSocket::~TCPSocket()
{
closeSocket();
}
TCPSocket::TCPSocket(const TCPSocket& other)
: m_local_hostname(other.m_local_hostname),
m_local_service(other.m_local_service),
m_remote_hostname(other.m_remote_hostname),
m_remote_service(other.m_remote_service),
m_allow_non_blocking(true),
recv_buffer_size(other.recv_buffer_size)
{
m_sockfd = dup(other.m_sockfd);
if(m_sockfd == -1)
{
throw SocketException("Failed to duplicate socket file descriptor", errno);
}
}
TCPSocket& TCPSocket::operator = (const TCPSocket& other)
{
int temp_sockfd = dup(other.m_sockfd);
if(temp_sockfd == -1)
{
throw SocketException("Failed to duplicate socket file descriptor", errno);
}
closeSocket();
m_sockfd = temp_sockfd;
recv_buffer_size = other.recv_buffer_size;
m_local_hostname = other.m_local_hostname;
m_local_service = other.m_local_service;
m_remote_hostname = other.m_remote_hostname;
m_remote_service = other.m_remote_service;
m_allow_non_blocking = other.m_allow_non_blocking;
return *this;
}
bool TCPSocket::bindAddress()
{
addrinfo hints = { 0 };
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo* result;
int getaddr_result;
const char *hostname;
const char *service;
if(m_local_hostname.empty())
{
hostname = NULL;
}
else
{
hostname = m_local_hostname.c_str();
}
if(m_local_service.empty())
{
service = NULL;
}
else
{
service = m_local_service.c_str();
}
/* If the hostname and service is NULL, then you cannot bind anything to it. */
if(hostname == NULL && service == NULL)
{
return true;
}
getaddr_result = getaddrinfo(hostname, service, &hints, &result);
if(getaddr_result != 0)
{
m_error_string = "Failed to obtain address information: ";
m_error_string.append(gai_strerror(getaddr_result));
return false;
}
if(bind(m_sockfd, result->ai_addr, result->ai_addrlen) != 0)
{
m_error_string = "Failed to bind address to socket: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
freeaddrinfo(result);
return false;
}
freeaddrinfo(result);
return true;
}
bool TCPSocket::setNonBlocking(bool value)
{
if(!m_allow_non_blocking)
{
m_error_string = "Not allowed to set O_NONBLOCK at this time.";
return false;
}
int options = fcntl(m_sockfd, F_GETFL);
if(options == -1)
{
m_error_string = "Failed to retrieve socket options: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
return false;
}
if(value)
{
options = (options | O_NONBLOCK);
}
else
{
options = (options & ~O_NONBLOCK);
}
if(fcntl(m_sockfd, F_SETFL, options) == -1)
{
m_error_string = "Failed to set O_NONBLOCK flag for socket: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
return false;
}
return true;
}
bool TCPSocket::isNonBlocking() const
{
int options = fcntl(m_sockfd, F_GETFL);
if(options == -1)
{
throw SocketException("Failed to retrieve socket options", errno);
}
return (options & O_NONBLOCK);
}
void TCPSocket::closeSocket()
{
if(m_sockfd != -1)
{
close(m_sockfd);
m_sockfd = -1;
}
}
void TCPSocket::obtainNewSocket()
{
if(m_sockfd > 0)
{
close(m_sockfd);
}
m_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_sockfd == -1)
{
throw SocketException("Failed to obtain new file descriptor for socket", errno);
}
}
bool TCPSocket::isValid() const
{
return m_sockfd != -1;
}
bool TCPSocket::forceSetNonBlocking(bool value)
{
int options = fcntl(m_sockfd, F_GETFL);
if(options == -1)
{
m_error_string = "Failed to retrieve socket options: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
return false;
}
if(value)
{
options = (options | O_NONBLOCK);
}
else
{
options = (options & ~O_NONBLOCK);
}
if(fcntl(m_sockfd, F_SETFL, options) == -1)
{
m_error_string = "Failed to set O_NONBLOCK flag for socket: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
return false;
}
return true;
}
int TCPSocket::getSockFd() const
{
return m_sockfd;
}
我创建了一个名为TCPClient的子类来实现连接功能。请注意,连接功能使用非阻塞连接&#34;引擎盖下#34;以便用户可以提供超时,以便在操作系统认为连接尝试失败之前不会阻止它。
#ifndef __TCP_CLIENT_HPP__
#define __TCP_CLIENT_HPP__
#include "TCPSocket.hpp"
class TCPClient : public TCPSocket
{
public:
TCPClient(const string& hostname = "", const string& service = "0");
~TCPClient();
TCPClient(const TCPClient& other);
TCPClient& operator = (const TCPClient& other);
bool connectToServer(const string& hostname, const string& service, time_t timeout = 0);
void closeSocket();
time_t getLastConnectTime() const;
private:
time_t m_timeout;
time_t m_last_connect_time;
bool connectFunction();
};
#endif
下一个文件是源文件TCPSocket.cpp。
#include "TCPClient.hpp"
TCPClient::TCPClient(const string& hostname, const string& service)
m_timeout(0),
m_last_connect_time(0)
{
m_local_hostname = hostname;
m_local_service = service;
m_allow_non_blocking = false;
}
TCPClient::~TCPClient() { }
TCPClient::TCPClient(const TCPClient& other)
: TCPSocket(other),
m_connected(other.m_connected),
m_timeout(other.m_timeout),
m_last_connect_time(0)
{
if(other.connecting())
{
throw SocketException("Cannot copy TCPClient object that is connecting.");
}
}
TCPClient& TCPClient::operator = (const TCPClient& other)
{
if(connecting())
{
throw SocketException("Cannot assign in the middle of a connection attempt.");
}
if(other.connecting())
{
throw SocketException("Cannot assign to TCPClient object that is connecting.");
}
TCPSocket::operator = (other);
m_connected = false;
m_timeout = 0;
m_last_connect_time = 0;
return *this;
}
bool TCPClient::connectToServer(const string& hostname, const string& service, time_t timeout)
{
if(connecting())
{
m_error_string = "Failed to connect to server: ";
m_error_string += strerror(EINPROGRESS);
m_saved_errno = errno;
return false;
}
if(connected())
{
return true;
}
m_timeout = timeout;
m_remote_hostname = hostname;
m_remote_service = service;
m_connected = connectFunction();
if(m_connected)
{
m_allow_non_blocking = true;
}
m_last_connect_time = time(NULL);
return m_connected;
}
bool TCPClient::connectFunction()
{
int connect_status;
int getaddr_result;
int max_fd;
int select_result;
int so_error = 0;
socklen_t so_error_len = sizeof(so_error);
addrinfo hints = { 0 };
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
addrinfo* result;
fd_set write_fds;
FD_ZERO(&write_fds);
timeval tv;
tv.tv_sec = m_timeout;
tv.tv_usec = 0;
if(!isValid())
{
obtainNewSocket();
}
/* Bind the local endpoint to the hostname and service provided. */
if(!bindAddress())
{
resetConnection();
return false;
}
if(!forceSetNonBlocking(true))
{
resetConnection();
return false;
}
getaddr_result = getaddrinfo(m_remote_hostname.c_str(), m_remote_service.c_str(), &hints, &result);
if(getaddr_result != 0)
{
m_error_string = "Failed to obtain address information: ";
m_error_string += gai_strerror(getaddr_result);
resetConnection();
return false;
}
connect_status = connect(m_sockfd, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
if(connect_status == 0)
{
if(!forceSetNonBlocking(false))
{
resetConnection();
return false;
}
return true;
}
if(errno == EINPROGRESS)
{
FD_SET(m_sockfd, &write_fds);
max_fd = m_sockfd + 1;
}
else
{
m_error_string = "Failed to connect to server: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
resetConnection();
return false;
}
if(tv.tv_sec == 0)
{
select_result = select(max_fd, NULL, &write_fds, NULL, NULL);
}
else
{
select_result = select(max_fd, NULL, &write_fds, NULL, &tv);
}
switch(select_result)
{
case -1:
m_error_string = "Failed to perform select on socket: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
resetConnection();
return false;
break;
case 0:
m_error_string = "Failed to connect to server: ";
m_error_string += strerror(ETIMEDOUT);
m_saved_errno = ETIMEDOUT;
resetConnection();
return false;
break;
default:
if(getsockopt(m_sockfd, SOL_SOCKET, SO_ERROR, &so_error, &so_error_len) != 0)
{
m_error_string = "Failed to obtain socket information during connect: ";
m_error_string += strerror(errno);
m_saved_errno = errno;
resetConnection();
return false;
}
if(so_error != 0)
{
m_error_string = "Failed to connect to server: ";
m_error_string += strerror(so_error);
m_saved_errno = so_error;
resetConnection();
return false;
}
break;
}
if(!forceSetNonBlocking(false))
{
resetConnection();
return false;
}
return true;
}
void TCPClient::closeSocket()
{
if(connecting())
{
return;
}
m_connected = false;
m_allow_non_blocking = false;
TCPSocket::closeSocket();
}
void TCPClient::resetConnection()
{
m_connected = false;
m_allow_non_blocking = false;
TCPSocket::closeSocket();
}
time_t TCPClient::getLastConnectTime() const
{
return m_last_connect_time;
}
我如何使用这些对象是创建TCPClient对象的向量,然后像这样调用connectToServer():
int num_of_clients = atoi(argv[1]);
vector<TCPClient> clients;
for(int index = 0 ; index < num_of_clients ; ++index)
{
TCPClient client;
clients.push_back(client);
}
for(vector<TCPClient>::iterator client = clients.begin() ;
client != clients.end() ;
++client)
{
if(!client->connected() && time(NULL) - client->getLastConnectTime() > 10)
{
if(!client->connectToServer("hostname", "9999", 10))
{
cout << "CONNECT ERROR - " << client->getErrorString() << endl;
}
}
}
以上代码输出&#34; CONNECT ERROR - 无法连接到服务器:无效的参数&#34;
我学到的东西是矢量STL容器喜欢使用复制构造函数来移动东西。我很难学到这一点,因为我的TCPSocket类的复制构造函数只是分配文件描述符而不是像现在这样调用dup()。之前,当我在复制构造函数中分配文件描述符时,我遇到了各种错误。
这是在RHEL 6,以防万一有人在想。手册页没有说connect可以返回EINVAL,但这显然不正确,因为它正在这样做。