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.hqwc.cn/news/259875.html

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

相关文章

Navicat 技术指引 | 适用于 GaussDB 分布式的日志查询与配置设置

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

[笔记]ARMv7/ARMv8 交叉编译器下载

开发 Cortex-A7、Cortex-A72 或其他 ARM 架构 profile 芯片时&#xff0c;经常需要下载对应架构的交叉编译器&#xff0c;所以写这篇笔记&#xff0c;用于记录一下交叉编译器下载流程&#xff0c;免得搞忘。 编译环境&#xff1a;ubuntu 虚拟机 下载地址 我们可以从 ARM 官网…

深入浅出:HTTPS单向与双向认证及证书解析20231208

介绍: 网络安全的核心之一是了解和实施HTTPS认证。本文将探讨HTTPS单向认证和双向认证的区别&#xff0c;以及SSL证书和CA证书在这些过程中的作用&#xff0c;并通过Nginx配置实例具体说明。 第一部分&#xff1a;HTTPS单向认证 定义及工作原理&#xff1a;HTTPS单向认证是一…

C语言WFC实现绘制贝塞尔曲线的函数

前言&#xff1a; 贝塞尔曲线于 1962 年&#xff0c;由法国工程师皮埃尔贝济埃&#xff08;Pierre Bzier&#xff09;所广泛发表&#xff0c;他运用贝塞尔曲线来为汽车的主体进行设计,贝塞尔曲线最初由保尔德卡斯特里奥于1959年运用德卡斯特里奥算法开发&#xff0c;以稳定数值…

RTL编码(3)——语句优化与布线优化

目录 一、语句优化 1.1 if与case 1.1.1 条件语句避免出现锁存器 1.1.2 if与case语句综合效果的差异 1.2 阻塞赋值与非阻塞赋值 1.3 算法改进 1.3.1 算法改进提升速度 1.3.2 算法改进缩小面积 二、布线优化 2.1 热点 2.2 瓶颈 2.3 广播 三、总结 一、语句优化 1…

STM32的BKP与RTC简介

芯片的供电引脚 引脚表橙色的是芯片的供电引脚&#xff0c;其中VSS/VDD是芯片内部数字部分的供电&#xff0c;VSSA/VDDA是芯片内部模拟部分的供电&#xff0c;这4组以VDD开头的供电都是系统的主电源&#xff0c;正常使用时&#xff0c;全部都要接3.3V的电源上&#xff0c;VBAT是…

这是最后的战役了

不变因子 初等因子 行列式因子 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^{…

springboot3远程调用

RPC 两个服务器之间的调用 远程请求 内部服务之间的调用 可以通过 cloud 注册中心 openfeign等 外部服务的调用 http请求 外部协议 api:远程接口 sdk&#xff1a;本地调用 调用阿里云的天气请求

T5论文个人记录

参考&转载自&#xff1a; 介绍Google推出的大一统模型—T5_谷歌大模型_深度之眼的博客-CSDN博客 T5 和 mT5-CSDN博客 T5&#xff1a;Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer&#xff08;万字长文略解T5&#xff09;_t5论文…

关于前端原生技术-Jsonp的理解与简述

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/134777717 出自【进步*于辰的博客】 在学习了Jsoup这个知识点之后&#xff0c;发觉js的这一特点…

易错C语言选择题

1.void main() {int a2,b5; printf("a%%d,b%%d\n"); } 结果为&#xff1a;&#xff08;&#xff09; A. a%2&#xff0c;b%5 B. a2&#xff0c;b5 C. a%%d&#xff0c;b%%d D. a%d&#xff0c;b%d 答案&#xff1a;D 2.若有说明&#x…

react Hooks实现原理

Fiber 上篇文章fiber简单理解记录了react fiber架构&#xff0c;Hooks是基于fiber链表来实现的。阅读以下内容时建议先了解react fiber。 jsx -> render function -> vdom -> fiber树 -> dom vdom 转 fiber 的过程称为 recocile。diff算法就是在recocile这个过程…