connect返回EINVAL - 无效参数

时间:2016-04-01 02:48:14

标签: c++ c linux sockets

我编写了两个封装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,但这显然不正确,因为它正在这样做。

0 个答案:

没有答案