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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# 语法笔记

1.ref、out&#xff1a;参数传递的两种方式 ref&#xff1a;引用传递 using System; namespace CalculatorApplication {class NumberManipulator{public void swap(ref int x, ref int y){int temp;temp x; /* 保存 x 的值 */x y; /* 把 y 赋值给 x */y temp; /* 把 t…

巧用静默,原来真的可以告警零误报!

前言 在当今的信息技术环境中&#xff0c;系统的稳定性和安全性至关重要。然而&#xff0c;在进行计划内的升级维护时&#xff0c;监控系统往往会产生大量的误报警告&#xff0c;给运维人员带来不必要的困扰。为了解决这一问题&#xff0c;组织可以通过合理设置静默规则&#…

如何选择 Facebook 代理?

Facebook上从事业务推广、广告或资料推广以及群组的用户需要高质量且可靠的代理。使用代理&#xff0c;用户可以在账号被封锁的情况下访问自己的资料&#xff0c;同时与多人进行即时通信&#xff0c;并能够&#xff1a; 自动发送邀请参加各种活动&#xff1b; 通过特殊的机器人…

中国开源大模型登顶HuggingFace排行榜

12月8日消息&#xff0c;全球最大的开源大模型社区HuggingFace日前公布了最新的开源大模型排行榜&#xff0c;阿里云通义千问力压Llama2等国内外开源大模型登顶榜首。 HuggingFace的开源大模型排行榜&#xff08;Open LLM Leaderboard&#xff09;是目前大模型领域最具权威性的…

SpringAMQP 快速入门

SpringAMQP 快速入门 1. 创建项目2. 快速入门2.2.1 消息发送2.2.2 消息接收 3. 交换机3.1 Fanout Exchange&#xff08;扇出交换机&#xff09;3.1.1 创建队列与交换机3.1.2 消息接收3.1.3 消息发送 3.2 Direct Exchange&#xff08;直连交换机&#xff09;3.2.1 创建交换机与队…

❤ Mac IDEA使用并运行项目

❤ IDEA导入项目并运行 Mac IDEA使用 (1) 仓库导入 通过获取giett仓库包的url&#xff0c;在idea中导入项目 在gitee里获取项目的ur打开idea&#xff0c;点击 File->new->Project from Version Control (2) 创建数据库ry并导入数据脚本 &#xff08;3&#xff09;修改配…

JOSEF 单稳态中间继电器 UEG/A-4H-L DC110V 导轨安装

系列型号 UEG/A-2H2D中间继电器UEG/A-4H4D中间继电器UEG/A-2D中间继电器 UEG/A-2H中间继电器UEG/A-4H中间继电器UEG/A-4D中间继电器 UEG/A-6H中间继电器UEG/A-6D中间继电器UEG/A-8H中间继电器 UEG/A-10D中间继电器UEG/A-10H中间继电器UEG/A-2DPDT中间继电器 UEG/A-4DPDT中…

用网安技术去合法挖漏洞,一个月能拿多少钱?想不到吧!

学会网安技术后去挖漏洞一个月能搞多少外快&#xff1f; 现在很多白帽子都是白天上班晚上挖洞&#xff0c;甚至有的人连班都不想上&#xff0c;纯靠挖漏洞来收入&#xff0c;比如说补天上面的这些人&#xff0c;每个月收入较高的都是他们&#xff0c;八成都是在家全职挖洞了。…

echarts绘制一个环形图2

其他echarts&#xff1a; echarts绘制一个环形图 echarts绘制一个柱状图&#xff0c;柱状折线图 echarts绘制一个饼图 效果&#xff1a; 组件代码&#xff1a; <template><div class"wrapper"><div ref"doughnutChart2" id"dough…

脉冲压缩及MATLAB仿真

文章目录 前言一、脉冲压缩二、MATLAB 仿真1、LFM 脉冲压缩匹配滤波实现测距①、MATLAB 源码②、仿真结果1) LFM 时域波形2) LFM 频域波形3) 两个未分辨目标的合成回波信号4) 脉冲压缩检测距离 2、去协处理仿真①、MATLAB 源码②、仿真结果1) 未压缩回波信号&#xff0c;3个目标…

C语言——2048完整版

2048是一个简单又有趣的小游戏&#xff0c;相信大家都接触并了解过&#xff0c;那如何通过代码来实现他呢&#xff1f;下面就让我们来一起看看。 目录 1、头文件 2、主函数 3、 StarGame 4、GetNum 5、Show 6、Picture 7、GetButton 8、MergeLeft 9、MergeUp 10、MergeR…

python圣诞树代码编程

以下是一个简单的Python圣诞树代码&#xff1a; def draw_tree(height): for i in range(height): print( * (height - i - 1) * * (2 * i 1)) print( * (height - 1) |)draw_tree(10) 这个函数会绘制一个等腰三角形&#xff0c;其中每一行的星号数量从1开…

改造后的LED电子大屏幕:环保与节能的新选择

随着人们生活水平和文明程度的提高&#xff0c;对于环保的意识也日益增强。这在支付宝几亿用户参与蚂蚁森林环保项目中体现得淋漓尽致&#xff0c;为环保事业贡献一份力量。为迎合这一时代发展的要求&#xff0c;LED显示屏厂家早已推出了亮度可调的LED节能屏。如今&#xff0c;…

linux高级篇基础理论七(Tomcat)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

html、css类名命名思路整理

开发页面时&#xff0c;老是遇到起名问题&#xff0c;越想越头疼&#xff0c;严重影响开发进度&#xff0c;都是在想名字&#xff0c;现在做一下梳理&#xff0c;统一一下思想&#xff0c;希望以后能减少这块的痛苦。 命名规则 [功能名称]__[组成部分名称]--[样式名称] 思路…

Spring Security 6.x 系列(10)—— SecurityConfigurer 配置器及其分支实现源码分析(二)

一、前言 在本系列文章&#xff1a; Spring Security 6.x 系列&#xff08;4&#xff09;—— 基于过滤器链的源码分析&#xff08;一&#xff09; 中着重分析了Spring Security在Spring Boot自动配置、 DefaultSecurityFilterChain和FilterChainProxy 的构造过程。 Spring …

神经内科临床常用的焦虑、抑郁评估量表,医生必备!

根据神经内科医生的量表使用情况&#xff0c;常笑医学整理了神经内科临床上常用的焦虑、抑郁评估量表&#xff0c;为大家分享临床常见的焦虑、抑郁、睡眠等量表评估内容&#xff0c;均支持量表下载和在线使用&#xff0c;建议收藏&#xff01; 1.汉密顿抑郁量表&#xff08;Ham…

从零开发短视频电商 AWS OpenSearch Service开发环境申请以及Java客户端介绍

文章目录 创建域1.创建域2.输入配置部署选项数据节点网络精细访问控制访问策略 获取域端点数据如何插入到OpenSearch ServiceJava连接OpenSearch Servicespring-data-opensearchelasticsearch-rest-high-level-clientopensearch-rest-clientopensearch-java 因为是开发测试使用…

喜讯!云起无垠上榜《成长型初创企业推荐10强》

近期&#xff0c;由中国计算机学会抗恶劣环境计算机专业委员会、信息产业信息安全测评中心和安全牛联合发起的第十一版《中国网络安全企业100强》榜单正式发布。在这份备受关注的榜单中&#xff0c;云起无垠凭借其创新的技术能力&#xff0c;荣登《成长型初创企业推荐10强》榜单…

echarts绘制一个环形图

其他echarts&#xff1a; echarts绘制一个柱状图&#xff0c;柱状折线图 echarts绘制一个饼图 echarts绘制一个环形图2 效果图&#xff1a; 代码&#xff1a; <template><div class"wrapper"><!-- 环形图 --><div ref"doughnutChart…