端口复用之 SO_REUSEADDR

前言:端口复用是网络编程里的经典问题,同时这里面的知识点又非常繁琐,本文通过代码简单介绍一下 SO_REUSEADDR,但不会涉及到 SO_REUSEPORT。

长期以来,我们都有一个认知,就是不能监听同一个端口。比如以下代码。

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

我们就会看到 Address already in use 的错误。但是真的不能绑定到同一个端口吗?不一定。

#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"));
}

上面的代码启动了两个服务器,两个服务器都绑定了同一个端口,编译执行是可以正常跑的,因为我指定了不同的 IP。由此可见,平时我们认为多个服务器不能同时监听同一个端口是因为我们只指定了端口,而没有指定 IP。

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

执行以上代码,通过 lsof -i:8080 可以看到绑定的地址 *:8080。也就是说,如果我们没有指定 IP,那么系统就会默认监听全部 IP。当第二次监听同一个端口时就会报错。接着看第二种情况。

#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"));
}

上面的代码执行会报错 Address already in use。为什么改成 INADDR_ANY 就不行了呢?因为 INADDR_ANY 代表的是全部 IP,这样默认情况下就无法绑定到其他 IP 了。从逻辑上来说就是当操作系统收到这个127.0.0.1:6666 的数据包时,不知道该给谁处理,因为绑定的两个地址都命中了。但是我们可以告诉操作系统把这个数据包给谁。

#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_REUSEADDR 的逻辑,编译执行成功。由此可见,SO_REUSEADDR 就是告诉操作系统当一个数据包命中多个socket时应该给谁处理,操作系统明确了这个逻辑后,自然也就允许以这种方式监听端口了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SO_REUSEADDR是Linux中socket选项之一,用于设置套接字的地址复用。当我们在同一主机上运行多个具有相同IP地址和端口号的套接字时,可以使用SO_REUSEADDR选项来避免地址冲突。通过设置SO_REUSEADDR选项,我们可以允许在上一个套接字关闭后,立即重用该地址绑定到新的套接字上。 在引用的代码示例中,可以看到通过调用setsockopt函数并指定SO_REUSEADDR选项,将其设置为1(或非零值)可以启用地址复用功能。这样,在套接字绑定时,即使之前的套接字还在TIME_WAIT状态,也能够绑定到相同的地址上。 SO_REUSEADDR选项对于服务器程序特别有用。当服务器程序在关闭连接后重新启动时,可以立即绑定到之前使用的端口上,而无需等待TIME_WAIT状态结束。这样可以更快地重新启动服务器程序,提高了服务器的可用性。 总结起来,SO_REUSEADDR选项在Linux的socket编程中用于实现地址复用,可以解决端口冲突的问题,特别适用于服务器程序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Socket中SO_REUSEADDR详解](https://blog.csdn.net/chanlp129/article/details/126690729)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [socket选项: SO_REUSEADDR, SO_RCVBUF, SO_SNDBUF | 学步园](https://blog.csdn.net/weixin_29129833/article/details/115997485)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值