C/C++端口复用SO_REUSEADDR(setsockopt参数),test ok

news2024/11/25 23:00:31

端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦!

实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
int main(int argc, char *argv[])
{
    int sockfd_one;
    int err_log;
    sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    if(sockfd_one < 0)
    {
    perror("sockfd_one");
    exit(-1);
    }
 
    // 设置本地网络信息
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(8000);        // 端口为8000
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    // 绑定,端口为8000
    err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_one");
        close(sockfd_one);        
        exit(-1);
    }
 
    int sockfd_two;
    sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    if(sockfd_two < 0)
    {
        perror("sockfd_two");
        exit(-1);
    }
 
    // 新套接字sockfd_two,继续绑定8000端口,绑定失败
    // 因为8000端口已被占用,默认情况下,端口没有释放,无法绑定
    err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_two");
        close(sockfd_two);        
        exit(-1);
    }
 
    close(sockfd_one);
    close(sockfd_two);
 
    return 0;
}
/*
ser@DESKTOP-MF4BDTB:/mnt/e/test_code$ g++ SO_REUSEADDR.cpp -o SO_REUSEADDR.O
user@DESKTOP-MF4BDTB:/mnt/e/test_code$ ./SO_REUSEADDR.O
bind sockfd_two: Address already in use
user@DESKTOP-MF4BDTB:/mnt/e/test_code$
*/


程序编译运行后结果如下:


那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。

设置socket的SO_REUSEADDR选项,即可实现端口复用:

int opt = 1;
// sockfd为需要端口复用的套接字
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));


SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)

1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。

2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。

3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。

4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。

需要注意的是,设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用:

// sockfd_one, sockfd_two都要设置端口复用
// 在sockfd_one绑定bind之前,设置其端口复用
int opt = 1;
setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );
err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
 
// 在sockfd_two绑定bind之前,设置其端口复用
opt = 1;
setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt) );
err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));


端口复用完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
int main(int argc, char *argv[])
{
    int sockfd_one;
    int err_log;
    sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    if(sockfd_one < 0)
    {
    perror("sockfd_one");
    exit(-1);
    }
 
    // 设置本地网络信息
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(8000);        // 端口为8000
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    // 在sockfd_one绑定bind之前,设置其端口复用
    int opt = 1;
    setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, 
                    (const void *)&opt, sizeof(opt) );
 
    // 绑定,端口为8000
    err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_one");
        close(sockfd_one);        
        exit(-1);
    }
 
    int sockfd_two;
    sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    if(sockfd_two < 0)
    {
        perror("sockfd_two");
        exit(-1);
    }
 
    // 在sockfd_two绑定bind之前,设置其端口复用
    opt = 1;
    setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, 
                    (const void *)&opt, sizeof(opt) );
    
    // 新套接字sockfd_two,继续绑定8000端口,成功
    err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_two");
        close(sockfd_two);        
        exit(-1);
    }
 
    close(sockfd_one);
    close(sockfd_two);
 
    return 0;
}


端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。同时,这 n 个套接字发送信息都正常,没有问题。但是,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。


下面,我们在之前的代码上,添加两个线程,分别负责接收sockfd_one,sockfd_two的信息:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
 
// 线程1的回调函数
void *recv_one(void *arg)
{
    printf("===========recv_one==============\n");
    int sockfd = (int )arg;
    while(1){
        int recv_len;
        char recv_buf[512] = "";
        struct sockaddr_in client_addr;
        char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
        socklen_t cliaddr_len = sizeof(client_addr);
        
        recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
        inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
        printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
        printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);
    
    }
 
    return NULL;
}
 
// 线程2的回调函数
void *recv_two(void *arg)
{
    printf("+++++++++recv_two++++++++++++++\n");
    int sockfd = (int )arg;
    while(1){
        int recv_len;
        char recv_buf[512] = "";
        struct sockaddr_in client_addr;
        char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16
        socklen_t cliaddr_len = sizeof(client_addr);
        
        recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);
        inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
        printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));
        printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",recv_len,recv_buf);
    
    }
 
    return NULL;
}
 
int main(int argc, char *argv[])
{
    int err_log;
    
    /sockfd_one
    int sockfd_one;
    sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one
    if(sockfd_one < 0)
    {
    perror("sockfd_one");
    exit(-1);
    }
 
    // 设置本地网络信息
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(8000);        // 端口为8000
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    // 在sockfd_one绑定bind之前,设置其端口复用
    int opt = 1;
    setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, 
                    (const void *)&opt, sizeof(opt) );
 
    // 绑定,端口为8000
    err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_one");
        close(sockfd_one);        
        exit(-1);
    }
    
    //接收信息线程1
    pthread_t tid_one;
    pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);
    
    /sockfd_two
    int sockfd_two;
    sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two
    if(sockfd_two < 0)
    {
        perror("sockfd_two");
        exit(-1);
    }
 
    // 在sockfd_two绑定bind之前,设置其端口复用
    opt = 1;
    setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR, 
                    (const void *)&opt, sizeof(opt) );
    
    // 新套接字sockfd_two,继续绑定8000端口,成功
    err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(err_log != 0)
    {
        perror("bind sockfd_two");
        close(sockfd_two);        
        exit(-1);
    }
    //接收信息线程2
    pthread_t tid_two;
    pthread_create(&tid_two, NULL, recv_two, (void *)sockfd_two);
    
    
    while(1){    // 让程序阻塞在这,不结束
        NULL;
    }
 
    close(sockfd_one);
    close(sockfd_two);
 
    return 0;
}


接着,通过网络调试助手给这个服务器发送数据,结果显示,只有最后一个套接字sockfd_two会正常接收数据:

我们上面的用法,实际上没有太大的意义。端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1296670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

StoneDB-8.0-V2.2.0 企业版正式发布!性能优化,稳定性提升,持续公测中!

​ 11月&#xff0c;StoneDB 新版本如期而至&#xff0c;这一个月来我们的研发同学加班加点&#xff0c;持续迭代&#xff1a;在 2.2.0 版本中&#xff0c;我们针对用户提出的需求和做出了重量级更新&#xff0c;修复了一些已知和用户反馈的 Bug&#xff0c;同时对部分代码进行…

docker学习(四、修改容器创建新的镜像推送到云上)

镜像是只读的&#xff0c;容器是可编辑的。Docker镜像是分层的&#xff0c;支持通过扩展镜像&#xff0c;创建新的镜像。 学到这里感觉docker跟git很想~~ 通过docker commit将修改的容器做成新的镜像 # 将容器做成新的镜像 docker commit -m"提交备注" -a"作…

结构化布线系统

满足下列需求&#xff1a; 1.标准化&#xff1a;国际、国家标准。 2.实用性&#xff1a;针对实际应用的需要和特点来建设系统。 3.先进性&#xff1a;采用国际最新技术。5-10年内技术不落后。 4.开放性&#xff1a;整个系统的开放性。 5.结构化、层次化&#xff1a;易于管理和维…

Nginx+Promtail+Loki+Grafana 升级ELK强大工具

最近客户有个新需求,就是想查看网站的访问情况,由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的页面,咱也做不到 成熟的日志解决方案,那就是ELK,还有现在比较火的Loki,(当然还有很多其…

WPF使用WebBrowser报脚本错误问题处理

前言 WPF使用WebBrowser报脚本错误问题处理,我们都知道WPF自带的WebBrowser都用的IE内核,但是在特殊的条件下我们还需要用到它,比如展示纯html简单的页面。再展示主流页面的时候比如用到Jquery高级库或者VUE等当前主流站点时经常就会报JS脚本错误,在Winform里面我们一句代…

进程、线程、线程池状态

线程几种状态和状态转换 进程主要写明三种基本状态&#xff1a; 线程池的几种状态&#xff1a;

这是最后的战役了

不变因子 初等因子 行列式因子 smith标准型 酉矩阵 H-阵等等 A H A A^H A AHA 就是 H-阵 正定H阵的性质 若 A A A 为正定的H-阵. 存在可逆矩阵 Q Q Q&#xff0c; 使得 A Q H Q AQ^H Q AQHQ.存在 P P P, 使得 P H A P I P^HAPI PHAPI.A的特征值大于0. Q − 1 A Q Q^{…

生成fip.bin在Milkv-duo上跑rtthread的相关尝试,及其问题分析

前言 &#xff08;1&#xff09;PLCT实验室实习生长期招聘&#xff1a;招聘信息链接 &#xff08;2&#xff09;本来是想在Milkv-duo上跑rtthread的&#xff0c;做了很多努力&#xff0c;一直没有结果。虽然不知道最终能不能成功做出来&#xff0c;还是把自己的相关努力分享出来…

RocketMQ安装和使用

RocketMQ快速入门 下载RocketMQ 下载地址 环境要求 Linux64位系统 JDK1.8(64位) 安装RocketMQ 解压 unzip rocketmq-all-4.4.0-bin-release.zip启动RocketMQ 启动NameServer # 1.启动NameServer nohup sh bin/mqnamesrv & # 2.查看启动日志 tail -f ~/logs/rocke…

用C语言实现队列的顺序结构

用C语言实现队列的初始化、队列的判空操作、入队操作、出队运算、取队头元素运算、顺序打印队列。 #include<stdio.h> #define QueueSize 100 typedef char ElemType; typedef struct//队列结构体 {ElemType data[QueueSize];//保存队中元素int front, rear;//队头和队尾…

hook其他调试技巧

输出堆栈信息 通过 android.util.Log 输出当前线程的堆栈跟踪信息。 function showStacks() {Java.perform(function () {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new() )); }) } 可以在需要的…

判断css文字发生了截断,增加悬浮提示

示例&#xff1a; 固定显示宽度&#xff0c;溢出显示...&#xff0c;利用了css的属性&#xff0c;想要实现成下面这样&#xff1a; 针对溢出的文字&#xff0c;hover显示全部。 提示很好加&#xff0c;使用tooltip组件就行了&#xff0c;难点是如何判断是否发生了文字溢出。…

【从零认识ECS云服务器 | 快速上线个人网站】三、对外发布网站

3.1 配置域名 用户是如何访问网站的呢&#xff1f; 用户在浏览器(IE、Chrome、FireFox等)上输入域名&#xff0c;如&#xff1a;http://www.aliyun.com &#xff1b; 浏览器自动调用DNS&#xff08;域名服务&#xff09;将域名解析为IP地址&#xff0c;如&#xff1a;123.123…

canvas绘制运动的圆

代码实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdev…

【数据结构】顺序表的定义和运算

目录 1.初始化 2.插入 3.删除 4.查找 5.修改 6.长度 7.遍历 8.完整代码 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &…

Redis主从架构中从节点的master_link_status:down

项目场景&#xff1a; 在搭建Redis的主从架构时&#xff0c;查看Redis的从节点状态时发现其连接的主节点的状态为down&#xff0c;并且查看主节点的状态时发现连接的从节点数量为0。 问题描述 原因分析&#xff1a; 可能在主节点中配置了密码&#xff0c;即requirepass。 解决…

diffusers pipeline拆解:理解pipelines、models和schedulers

diffusers pipeline拆解&#xff1a;理解pipelines、models和schedulers 翻译自&#xff1a;https://huggingface.co/docs/diffusers/using-diffusers/write_own_pipeline v0.24.0 diffusers 设计初衷就是作为一个简单且易用的工具包&#xff0c;来帮助你在自己的使用场景中构建…

图像的均方差和信噪比计算

图像的均方差和信噪比计算 一、均方差1、公式2、代码 二、信噪比1、公式2、代码 图像的均方差和信噪比公式及代码&#xff0c;代码基于opencv和C实现。 一、均方差 均方误差&#xff0c;英文简称&#xff1a;MSE&#xff0c;英文全称&#xff1a;“Mean Square Error”。 衡量…

Java数据结构06——树

1.why: 数组&链表&树 2. 大纲 2.1前中后序 public class HeroNode {private int no;private String name;private HeroNode left;//默认为nullprivate HeroNode right;//默认为nullpublic HeroNode(int no, String name) {this.no no;this.name name;}public int …

AWTK 串口屏开发(1) - Hello World

1. 功能 这个例子很简单&#xff0c;制作一个调节温度的界面。在这里例子中&#xff0c;模型&#xff08;也就是数据&#xff09;里只有一个温度变量&#xff1a; 变量名数据类型功能说明温度整数温度。范围 (0-100) 摄氏度 2. 创建项目 从模板创建项目&#xff0c;将 hmi/…