在《绑定( bind )端口需要注意的问题》提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 )。

实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 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;
}

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

那如何让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已经在使用中——那只好等等再重试了,麻烦!

源代码下载请点此处。

Linux网络编程——端口复用(多个套接字绑定同一个端口)相关推荐

  1. tcp/ip网络编程--accept()函数返回的套接字

    tcp/ip网络编程–accept()函数返回的套接字 套接字:1)套接字是对网络中不同主机的应用进程之间进行双向通信的端点的抽象:一个套接字就是网络进程通信的一端.[1] 2)套接字是用来与另一个进 ...

  2. week6 day1 网络编程之扫盲知识与套接字介绍

    week6 day1 网络编程之套接字介绍 一. 客户端/服务器架构 二. OSI七层 2.0 引子 2.1 OSI七层协议 三. tcp/ip五层模型讲解 3.1 物理层(高低电位传输) 3.2 数 ...

  3. Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象

    一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字server.bind() #把地址绑定到套接字,网络地址加端口server.liste ...

  4. 【网络编程】---C++实现原始套接字捕获数据包

    C++实现原始套接字捕获数据包 引言 原始套接字与TCP套接字和UDP套接字的区别 原始套接字编程使用的场合 原始套接字的通信过程 (1)基于原始套接字的数据发送过程 (2)基于原始套接字的数据接收过 ...

  5. java实现套接字网络编程_Java网络编程(一)Socket套接字

    一.基础知识 1.TCP:传输控制协议. 2.UDP:用户数据报协议. 二.IP地址封装 1.InetAddress类的常用方法 getLocalHost() 返回本地主机的InetAddress对象 ...

  6. Java网络编程(一)Socket套接字

    一.基础知识 1.TCP:传输控制协议. 2.UDP:用户数据报协议. 二.IP地址封装 1.InetAddress类的常用方法 getLocalHost() 返回本地主机的InetAddress对象 ...

  7. python网络编程--创建简单的UPD套接字实现两个进程间互相通信

    Socket 什么是socket Socket通常又称"套接字",应用程序通常会通过"套接字"实现向网络发出请求或者相应网络请求,从而实现不同计算机之间或同一计 ...

  8. TCP/IP网络编程 学习笔记_3 --给套接字分配IP地址和端口号

    IP地址和端口号 1,IP地址:为使计算机连接到网络并收发数据,必须为其分配IP地址.IP地址分为两类:IPv4(4字节地址族)和IPv6(16字节地址族).它们主要区别就是在表示IP地址所用的字节数 ...

  9. Linux socket编程(一) 对套接字操作的封装

    转载:http://www.cnblogs.com/-Lei/archive/2012/09/04/2670942.html 以前写的,现在回顾一下: 下面是对socket操作的封装,因为在Linux ...

  10. socket网络编程及通过socket接口实现一个自我通信的简易UDP服务器

    网络编程socket基础 认识socket套接字 先用一张图引出socket的内容 根据上面的图示我们可以知道socket其实是位于应用层与传输层之间的一层软件抽象层.它是一组接口,在后面的部分我们会 ...

最新文章

  1. 区块链是互联网未来十年中举足轻重的技术
  2. WEB测试常见问题汇总
  3. EtherType :以太网类型字段及值
  4. Js中数组去重的几种方法
  5. C语言学习笔记--函数与指针
  6. Spring MVC搭建REST风格网站
  7. 项目Beta冲刺(团队)第七天
  8. 【Spring】Spring DataSourceTransactionManager
  9. datetime(0) NULL DEFAULT NULL报错
  10. 三菱M80操作介绍_共享屋:三菱M70 PLC 转M80操作步骤
  11. 自定义路径创建Cocos2d-x项目
  12. 每日算法系列【LeetCode 289】生命游戏
  13. 关于AfxMessageBox没有重载函数
  14. imu预积分_VINS 中的 IMU 预积分推导和代码解读
  15. sobel边缘检测java_Sobel边缘检测
  16. golang mysql null_Golang将变量声明为NULL
  17. Android扫描wifi二维码自动连接wifi
  18. 如何由网址查找IP地址
  19. 驾照新规4月起实施:有驾驶经历者可直接申请考试
  20. MATLAB 实现将彩色图像的R、G、B三个分量以彩色的形式显示

热门文章

  1. ACM竞赛-练习题-每周训练-2022-11-15-A
  2. jave使用打印插件(Lodop)可以打印以及保存excel
  3. Win11 安装WSA(安卓子系统)指南
  4. Sublime基本设置
  5. Excel2007 WPS 冻结窗格
  6. 一款好看的HTML介绍页、引导页源码
  7. mysql时间相减得到天数保留两位_MySQL计算两个日期相差的天数、月数、年数
  8. JAVA 进行图片中文字识别(准确度高)!!!
  9. 卡尔曼滤波C语言实现
  10. rbegin()和rend()