0

I am writing a TCP communication library for my codebase. I have implemented this library with an acceptor class (to be run on server), and connector class (to be run on client) and a stream class (handle sending and receiving data).

For the most part, everything works. The one problem I have is that if I disconnect the ethernet wire between client and server, and attempt to connect, I get the error that the destination was unreachable. Upon further attempts to connect, whether the ethernet cable is connected or not, I get an error that operation is in progress.

Note that if connect is called, even when reconnecting, everything works if a connection is possible.

Also, please note in my code during connect I am setting the socket option to SO_REUSEADDR, I am using SO_LINGER, and that after a failed connection I am closing all sockets.

TCPConnector class:

/** @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 Class:

/** @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;
}
Ken White
  • 120,522
  • 13
  • 212
  • 426
Austin
  • 79
  • 5
  • 1
    FYI, you don't need `SO_REUSEADDR` in the client, only on the server. – Barmar Jul 01 '16 at 23:13
  • 1
    `EINPROGRESS` isn't an error. It just means that the handshake hasn't completed. See http://stackoverflow.com/questions/8277970/what-are-possible-reason-for-socket-error-einprogress-in-solaris – Barmar Jul 01 '16 at 23:17
  • 1
    Servers don't reconnect to clients. Clients connect and reconnect to servers. Your title doesn't make sense. You do not need and should not use SO_LINGER. Your code lacks vital error checking, and doesn't report the actual error where it isn't lacking. You don't need to select before writing unless you have had a prior EINPROGRESS from a prior write. – user207421 Jul 02 '16 at 00:09
  • Any idea why the operation isn't proceeding / completing? – Austin Jul 02 '16 at 05:52

0 Answers0