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套接字oneif(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);        // 端口为8000my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 绑定,端口为8000err_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套接字twoif(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套接字oneif(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);        // 端口为8000my_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) );// 绑定,端口为8000err_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套接字twoif(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=16socklen_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=16socklen_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_oneint sockfd_one;sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字oneif(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);        // 端口为8000my_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) );// 绑定,端口为8000err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));if(err_log != 0){perror("bind sockfd_one");close(sockfd_one);        exit(-1);}//接收信息线程1pthread_t tid_one;pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);/sockfd_twoint sockfd_two;sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字twoif(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);}//接收信息线程2pthread_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.rhkb.cn/news/213159.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1-Maven基础

文章目录 Maven基础Maven相关概念构建依赖 Maven用途Maven的工作机制 Maven使用-1-Maven软件的解压与配置步骤1&#xff1a;下载步骤2&#xff1a;解压Maven核心程序步骤3&#xff1a;指定本地仓库步骤4&#xff1a;配置阿里云提供的镜像仓库步骤5&#xff1a;配置 Maven工程的…

【小白专用】MySQL入门(详细总结)

3. 创建数据库 使用 create database 数据库名; 创建数据库。 create database MyDB_one; create database DBAliTest; 创建数据库成功后&#xff0c;数据库的数量变成了6个&#xff0c;多了刚才创建的 dbalitest 。 4. 创建数据库时设置字符编码 使用 create database 数据…

sfp8472学习CDR

1,cdr名称解释 因为光信号传输至一定距离的时候,通常是长距离传输,其波形会出现一定程度的失真,接收端接收到的信号是一个个长短不一的脉冲信号,这个时候在接收端,我们就无法得到我们需要的数据。所以,这个时候就需要有信号的再生,信号的再生功能为再放大、再整形和再…

【Python】 Python web开发库大全

库排序是按照使用人数和文档的活跃度为参考进行的&#xff0c;建议大家使用排名靠前的框架&#xff0c;因为它们的文档更齐全&#xff0c;技术积累要更多&#xff0c;社区更繁盛&#xff0c;能得到更好的支持&#xff0c;这样在遇到自己无法解决的问题&#xff0c;可以更快更高…

京东运营数据分析:10月京东奶粉行业销售数据分析

近年来&#xff0c;随着出生人口红利逐渐消逝&#xff0c;婴幼儿奶粉竞争进入红海时代&#xff0c;产品逐渐过剩。在这种情况下&#xff0c;我国奶粉市场进入调整阶段&#xff0c;企业开始将目光投向奶粉的品类细分领域&#xff0c;如有机奶粉、羊奶粉、特殊配方奶粉、成人奶粉…

elementUI中的 “this.$confirm“ 基本用法,“this.$confirm“ 调换 “确认“、“取消“ 按钮的位置

文章目录 前言具体操作总结 前言 elementUI中的 "this.$confirm" 基本用法&#xff0c;"this.$confirm" 调换 "确认"、"取消" 按钮的位置 具体操作 基本用法 <script> this.$confirm(这是数据&#xff08;res.data&#xff0…

kafka学习笔记--安装部署、简单操作

本文内容来自尚硅谷B站公开教学视频&#xff0c;仅做个人总结、学习、复习使用&#xff0c;任何对此文章的引用&#xff0c;应当说明源出处为尚硅谷&#xff0c;不得用于商业用途。 如有侵权、联系速删 视频教程链接&#xff1a;【尚硅谷】Kafka3.x教程&#xff08;从入门到调优…

现代皮质沙发模型材质编辑

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

vite配置nework访问ip

如果没有进行配置&#xff0c;运行项目之后&#xff0c;看到的访问地址是本地访问地址&#xff0c;其他人同个局域网的人访问不了。 如下&#xff1a; 如果想要其他人也可以访问&#xff0c;需要设置内网 ip 访问地址&#xff0c;设置方法如下&#xff1a; 一、配置 “ vite…

【UE5 c++】c++创建AI对象SpawnAIFromClass

根据蓝图节点&#xff0c;可以发现此方法在AIBlueprintHelperLibrary中。 在.h文件中声明被创建的Class和需要使用的AITree void SpawnMyAI();UPROPERTY(EditAnywhere,BlueprintReadWrite,Category"Class")TSubclassOf<APawn> myclass;UPROPERTY(EditAnywhere…

Qt内存管理、UI编辑器、客制化组件、弹出对话框、常用部件类

头文件的小技巧 #include <QtWidgets> // 在自动生成的 .h 里面加上此句 适用条件&#xff1a; QT 的内存管理 当父窗体被关闭时&#xff0c;子部件的内存会自动释放。 对象树是一种管理对象生命周期的机制。当一个对象被添加到另一个对象的子对象列表中时&#xff0…

Python网络爬虫的基础理解-对应的自我理解误区

##通过一个中国大学大学排名爬虫的示例进行基础性理解 以软科中国最好大学排名为分析对象&#xff0c;基于requests库和bs4库编写爬虫程序&#xff0c;对2015年至2019年间的中国大学排名数据进行爬取&#xff1a;&#xff08;1&#xff09;按照排名先后顺序输出不同年份的前10…

上网监控软件——安全与隐私的平衡

网络已经成为人们生活和工作中不可或缺的一部分。然而&#xff0c;随着网络使用的普及&#xff0c;网络安全问题也日益突出。上网监控软件作为网络安全领域的一个重要组成部分&#xff0c;在保护企业和家庭网络安全方面发挥着重要作用。 本文将探讨上网监控软件的背景、功能、优…

2023滨海湾人工智能论坛举办,范向伟代表和鲸科技共同发起成立工业智能算法联盟

人工智能是新一轮科技革命和产业变革的重要驱动力量&#xff0c;算力算法产业也正迎来爆发式增长。12月7日&#xff0c;以“要素融合知识互联”为主题的2023滨海湾人工智能论坛在东莞滨海湾新区举行&#xff0c;本次活动由东莞市人民政府主办&#xff0c;东莞滨海湾新区管委会、…

go语言 grpc 拦截器

文章目录 拦截器服务端拦截器一元拦截器流拦截器 客户端拦截器一元拦截器流拦截 多个拦截器 代码仓库 拦截器 gRPC拦截器&#xff08;interceptor&#xff09;是一种函数&#xff0c;它可以在gRPC调用之前和之后执行一些逻辑&#xff0c;例如认证、授权、日志记录、监控和统计…

物联网+AI智慧工地云平台源码(SaaS模式)

智慧工地云平台充分运用数字化技术&#xff0c;聚焦施工现场岗位一线&#xff0c;依托物联网、互联网、AI等技术&#xff0c;围绕施工现场管理的人、机、料、法、环五大维度&#xff0c;以及施工过程管理的进度、质量、安全三大体系为基础应用&#xff0c;实现全面高效的工程管…

【hugging face】bitsandbytes中8 bit量化的理解

8 位量化使数十亿参数规模的模型能够适应更小的硬件&#xff0c;而不会降低性能。 8 位量化的工作原理如下&#xff1a; 1.从输入隐藏状态中按列提取较大值&#xff08;离群值&#xff09;。 2.对 FP16 中的离群值和 int8 中的非离群值执行矩阵乘法。 3.改变非异常值结果以将值…

代理IP怎么使用?Mac苹果系统设置http代理IP教程

代理IP是一种通过将请求转发到另一个服务器&#xff0c;以隐藏自己的真实IP地址的服务器。使用代理IP可以保护您的隐私和安全&#xff0c;防止被跟踪或被攻击。在本文中&#xff0c;我们将介绍如何在Mac苹果系统上设置http代理IP教程。 一、了解代理IP 代理IP地址是一种可以用来…

网络编程值UDP

1. 知识点 1.1 TCP和UDP优缺点 1.2 UDP通信流程 1.2.1 服务端 1. 创建udp套接字 2. 初始化服务端网络地址结构 3. 绑定服务端网络地址 4.创建结构体用来存储客户端网络地址结构 5. 接收客户数据 1.2.2 客户端 1. 创建udp套接字 2. 初始化服务器网络地址结构 3. 客户端先发送数…

STM32 map文件详解

文章目录 1. 前言2. 生成 .map 文件3 .map 文件的组成3.1 Section Cross References - 各个源文件之间函数的调用关系3.2 Removing Unused input sections from the image - 移除未使用的模块3.3 Image Symbol Table - 映射符号表&#xff1a;描述各&#xff08;程序段 / 数据&…