Home > Blockchain >  Why socket fails in bind after having a listenfd with same port?
Why socket fails in bind after having a listenfd with same port?

Time:06-12

Simple code first:

#include  "NetFunc.h"
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
using namespace std;
void test()
{
    // SOCKET listenfd = OpenListenFD("127.0.0.1", "19287");
    
    SOCKET connfd = OpenClientFD("127.0.0.1", "12389", "127.0.0.1", "19287");
    send(connfd, "test", 5, 0);
    close(connfd);
    using namespace std::chrono_literals;
    this_thread::sleep_for(100ms);
    connfd = OpenClientFD("127.0.0.1", "12389", "127.0.0.1", "19287");
    send(connfd, "test2", 6, 0);
    close(connfd);
    // close(listenfd);
    cout << "over" << endl;
    return;
}

int main()
{
    SOCKET listenfd = OpenListenFD("127.0.0.1", "12389");
    sockaddr_storage clientaddr;
    socklen_t clientlen = sizeof clientaddr;
    // SOCKET connfd = 0;

    const int maxNameLen = 0x800;
    char* buffer = new char[0x800   1];
    char* hostname = new char[maxNameLen   1], * port = new char[maxNameLen   1];
    std::thread newThread{&test};
    newThread.detach();

    SOCKET connfd;
    while (true)
    {
        if ((connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen)) < 0)
        {
            std::cerr << "Not accept correctly at InteractWithClients" << std::endl;
        }
        else{
            getnameinfo((sockaddr*)&clientaddr, clientlen, hostname, maxNameLen, port, maxNameLen, NI_NUMERICHOST);
            std::string completeAddr = string(hostname)   ":"   string(port);
            std::cout << "Addr : " << completeAddr << std::endl;
            recv(connfd, buffer, 0x800, 0);
            std::cout << buffer << std::endl;
        }
        close(connfd);
    }
    
    delete[] buffer; delete[] hostname; delete[]port;
    return 0;
}

Here, the NetFunc.h has necessary headers and #define SOCKET int. It corresponds with NetFunc.cpp, which has two functions for server&client as below:

(Note that I'm trying to generate a client with a specific port, for I just need two address-port to connect rather than to distinguish a server from a client. Besides, I already use SO_REUSEADDR.)

#include "NetFunc.h"
#include <iostream>
#include <string.h>

SOCKET OpenListenFD(const char* addr, const char* port)
{
    struct addrinfo hints, * listp, * p;
    SOCKET listenfd;
    int optval = 1;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;             /* Accept connections */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */

    hints.ai_flags |= AI_NUMERICSERV;            /* ... using port number */
    hints.ai_family = AF_INET;
    getaddrinfo(addr, port, &hints, &listp);

    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            continue;  /* Socket failed, try the next */

        /* Eliminates "Address already in use" error from bind */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,    //line:netp:csapp:setsockopt
            (setsockopt_ptr)&optval, sizeof(int)); 

        /* Bind the descriptor to the address */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
        {
            break; /* Success */
        }
        closesocket(listenfd); /* Bind failed, try the next */
    }

    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* No address worked */
        return -1;

    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        closesocket(listenfd);
        return -1;
    }
    return listenfd;

}

SOCKET OpenClientFD(const char* serverAddr, const char* port, const char* localAddr, const char* localPort)
{
    struct sockaddr_in my_addr, my_addr1;
    SOCKET client = socket(AF_INET, SOCK_STREAM, 0);
    if (client < 0)
        std::cerr<<"Error in client creating\n";
    else
        std::cout << "Client Created\n";
    int optval = 1;
    setsockopt(client, SOL_SOCKET, SO_REUSEADDR,    //line:netp:csapp:setsockopt
        (setsockopt_ptr)&optval, sizeof(int));

    my_addr.sin_family = AF_INET;
    my_addr.sin_addr.s_addr = INADDR_ANY;
    my_addr.sin_port = htons(atoi(port));

    // This ip address will change according to the machine
    inet_pton(AF_INET, serverAddr, &my_addr.sin_addr.s_addr);

    // Explicitly assigning port number 12010 by
    // binding client with that port
    my_addr1.sin_family = AF_INET;
    my_addr1.sin_addr.s_addr = INADDR_ANY;
    my_addr1.sin_port = htons(atoi(localPort));

    // This ip address will change according to the machine
    inet_pton(AF_INET, localAddr, &my_addr1.sin_addr.s_addr);

    if (bind(client, (struct sockaddr*)&my_addr1, sizeof(struct sockaddr_in)) == 0)
        std::cout << "Binded Correctly\n";
    else
    {
        std::cerr << "Unable to bind\n";
        return -1;
    }
    socklen_t addr_size = sizeof my_addr;
    int con = 0;
    do { con = connect(client, (struct sockaddr*)&my_addr, sizeof my_addr); } while (con != 0);
    if (con == 0)
        std::cout << "Client Connected\n";
    else
        std::cerr << "Error in Connection\n";
    return client;
}

My OS is Ubuntu20.04 and such code will work with normal output. However, when I remove comments in test() for listenfd, it will output Unable to bind infinitely.

I'm really a newbie in socket and network. I have no idea why that will happen. Also, is there a way for an address-port to be both server and client at the same time? (Maybe to be exact, can an address-port both connect actively & accept others' connection passively?) Thank you!

CodePudding user response:

    my_addr1.sin_family = AF_INET;
    my_addr1.sin_addr.s_addr = INADDR_ANY;
    my_addr1.sin_port = htons(atoi(localPort));

    // This ip address will change according to the machine
    inet_pton(AF_INET, localAddr, &my_addr1.sin_addr.s_addr);

    if (bind(client, (struct sockaddr*)&my_addr1, sizeof(struct sockaddr_in)) == 0)
        std::cout << "Binded Correctly\n";

This code binds a listening socket to port 19287, localPort is 19287.

  // SOCKET listenfd = OpenListenFD("127.0.0.1", "19287");

And so does the commented-out code. Un-commenting it out results in two sockets attempting to listen on the same port. Surprise, surprise: this doesn't work.

The fact that the first socket that binds this port uses SO_REUSEADDR is irrelevant, and makes no difference. With or without SO_REUSEADDR, no two sockets can listen on the same port.

SO_REUSEADDR does not allow you to have multiple sockets listening on the same port, at the same time. All that SO_REUSEADDR does is allow a listening socket to be bound on a port that's temporarily "take out of commission", in specific edge cases, until a prescribed timeout expires. It allows a port to be bound as long as nothing else is listening on it, even if it's sitting in a "timeout room".

But it still won't allow you to have two different sockets listening on the same port, at the same time.

  • Related