Port multiplexing SO_REUSEADDR

Posted by davanderbilt on Thu, 03 Feb 2022 22:44:02 +0100

Port reuse is a classic problem in network programming, and the knowledge points in it are very cumbersome. This paper briefly introduces so through the code_ Reuseaddr, but so will not be involved_ REUSEPORT.

For a long time, we all know that we can't listen to the same port. For example, the following code.

server1.listen(8080);
server2.listen(8080);

We will see the error of Address already in use. But can't you really bind to the same port? not always.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

void start_server(__uint32_t host) {
    int listenfd, connfd;
    struct sockaddr_in servaddr;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        goto ERROR;
    }
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = host;
    servaddr.sin_port = htons(6666);

    if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        goto ERROR;
    }

    if(listen(listenfd, 10) == -1) {
        goto ERROR;
    }
    return;
    ERROR:
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
}
int main()
{
   start_server(inet_addr("127.0.0.1"));
   start_server(inet_addr("192.168.8.246"));
}

The above code starts two servers. Both servers are bound to the same port. The compilation execution can run normally because I specify different IP addresses. It can be seen that we usually think that multiple servers cannot listen to the same port at the same time because we only specify the port, not the IP.

const net = require('net');
const server = net.createServer();
server.listen(8080);

Execute the above code, and you can see the bound address *: 8080 through lsof -i:8080. In other words, if we do not specify an IP address, the system will listen to all IP addresses by default. An error will be reported when listening to the same port for the second time. Then look at the second case.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

void start_server(__uint32_t host) {
    int listenfd, connfd;
    struct sockaddr_in servaddr;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        goto ERROR;
    }
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = host;
    servaddr.sin_port = htons(6666);

    if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        goto ERROR;
    }

    if(listen(listenfd, 10) == -1) {
        goto ERROR;
    }
    return;
    ERROR:
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
}
int main()
{
   start_server(htonl(INADDR_ANY));
   start_server(inet_addr("127.0.0.1"));
}

The above code execution will report an error Address already in use. Why change to INADDR_ANY can't? Because INADDR_ANY represents all IP addresses, so you cannot bind to other IP addresses by default. Logically speaking, when the operating system receives the 127.0.0.1:6666 packet, it doesn't know who to deal with, because the bound two addresses hit. But we can tell the operating system who to give this packet to.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

void start_server(__uint32_t host) {
    int listenfd, connfd;
    struct sockaddr_in servaddr;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        goto ERROR;
    }
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
        goto ERROR;
    }
        
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = host;
    servaddr.sin_port = htons(6666);

    if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        goto ERROR;
    }

    if(listen(listenfd, 10) == -1) {
        goto ERROR;
    }
    return;
    ERROR:
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
}
int main()
{
   start_server(htonl(INADDR_ANY));
   start_server(inet_addr("127.0.0.1"));
}

So is added to the above code_ The logic of reuseaddr is compiled and executed successfully. Thus, SO_REUSEADDR tells the operating system who should handle a packet when it hits multiple socket s. After the operating system defines this logic, it will naturally allow it to listen to ports in this way.

Topics: C Linux network server