Search code examples
c++linuxtcpnetwork-programming

Create socket from hostname and port but ::select() indicates no data


I am trying to create a socket using a hostname and port. However, ::select() implies the socket has no data.

Can someone please help?

Self-contained example below (and Godbolt link).

https://godbolt.org/z/r7G3aczdK

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/tcp.h>

#include <string>
#include <iostream>

bool isSocketReady(int sockfd)
{
    fd_set rfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_SET(0, &rfds);

    tv.tv_sec = 2;
    tv.tv_usec = 0;

    const int retval = ::select(sockfd, &rfds, NULL, NULL, &tv);

    if (retval == -1)
    {
        printf("select() error");
        return false;
    }
    else if (retval)
    {
        printf("Data is available now.\n");
        return true;
    }
    else
    {
        printf("No data within timeout.\n");
        return false;
    }
}

int connectByHostName(const std::string& host, int port)
{
    int sockfd = -1;

    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    const std::string port_str = std::to_string(port);

    struct addrinfo* res{nullptr};
    const int r =  getaddrinfo(host.c_str(), port_str.c_str(), &hints, &res);
    
    if(r < 0)
    {
        return -1;
    }

    for (struct addrinfo* address = res; address != nullptr; address = address->ai_next)
    {
        sockfd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
        
        if (sockfd < 0)
        {
            continue;
        }

        int flag = 1;
        setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag));

        if(::connect(sockfd, address->ai_addr, address->ai_addrlen) < 0)
        {
            continue;
        }
        else if(isSocketReady(sockfd))
        {
            break;
        }            
    }

    freeaddrinfo(res);
    return sockfd;
}

int main()
{
    int sock = connectByHostName("example.com", 80);
    std::cout << sock << std::endl;
}

Solution

  • You are calling select() incorrectly.

    You need to change this:

    FD_SET(0, &rfds);
    

    to this:

    FD_SET(sockfd, &rfds);
    

    And change this:

    ::select(sockfd, ...);
    

    to this:

    ::select(sockfd+1, ...); // or 0 on Windows
    

    That being said, there is no point is using isSocketReady() after connect() in the code you have shown. Both HTTP and WebSocket protocols require you to send a request before you can then read any data, but you are not doing that, so the socket will never report as readable.

    If you are trying to use isSocketReady() to check if connect() is successful, then this is wrong too, for 2 reasons:

    • you would have to test the socket for writability instead of readability.

    • you are not putting the socket into non-blocking mode (TCP_NODELAY is not meant for that purpose), so connect() will block the calling thread until the connection is fully established or fails. So you can't use select() to check for that condition. To put the socket into non-blocking mode, you have to do one of the following (depending on which platform your code is running on):

      • create the socket with the SOCK_NONBLOCK flag on the socket() call.
      • use fctrl(F_SETFL) to enable the O_NONBLOCK flag on the socket.
      • use ioctl() (posix) or ioctlsocket() (Windows) to enable the FIONBIO option on the socket.