关于Socket中端口复用(udp)
最近在处理单位的外场遇到了一个实际问题。在实际与第三方系统进行通信的过程中,我绑定了30009端口作为UDP服务端,但是偶尔会发现第三方系统向30009端口发送数据的时候,通过网络抓包可以抓到,但是我自己在 receive数据的时候却收不到数据。而这个现象还是偶尔一段时间会出现,通过网络抓包,看我本段的30009端口在使用向对端发送语音数据,这就是说明我的端口被占用了,但是我启动后就绑定了这个..
最近在处理单位的外场遇到了一个实际问题。
在实际与第三方系统进行通信的过程中,我绑定了30009端口作为UDP服务端,但是偶尔会发现第三方系统向30009端口发送数据的时候,通过网络抓包可以抓到,但是我自己在 receive数据的时候却收不到数据。而这个现象还是偶尔一段时间会出现,通过网络抓包,看我本段的30009端口在使用向对端发送语音数据,这就是说明我的端口被占用了,但是我启动后就绑定了这个端口,
别人怎么能把这个给抢占走呢?后来发现程序里设置了这个端口的复用
int flag = 1;
int result = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&flag, sizeof(flag));
也就是说允许,其他进程再绑定这个端口。
而我是先绑定的,我们的媒体是后绑定的,那这个时候第三方系统向这个端口发送数据的话,就会有三种可能
1.先绑定的收到
2.后绑定的收到
3.都收到
然后我们在绑定ip的时候常见的有两种形式,
1.一种是 AddrServer.sin_addr.S_un.S_addr = INADDR_ANY;也就是0.0.0.0
2.另外一种是真实的本地ip addr.sin_addr.S_un.S_addr = inet_addr("10.110.12.68");
那么这两种绑定对于上面的结论是否有影响呢
于是我写了测试程序,来验证上面的结论,在windows下验证的结论如下:先写结论,然后再上代码
结论是:
1.如果两个进程都绑定的是本地的真实ip,如 ip addr.sin_addr.S_un.S_addr = inet_addr("10.110.12.68");
那谁先绑定谁就可以收到数据。后面虽然可以绑定,但是收不到发送过来的数据。
2.如果两个进程一个绑定的是INADDR_ANY,而一个绑定的是真实ip,那么最终收到数据的是真实ip,我们外场遇到的问题就是这样
的情况,在windows下操作系统下,我自己的进程先绑定了 30009端口,但是是用的INADDR_ANY绑定,而媒体进程是后绑定的端口,他是用真实的ip,所以他虽然是后绑定的,但是可以收到数据,导致我的进程收不到数据了。
但是这个结论感觉在windows下都绑定了真实的ip的情况如1,有点不合理,既然允许冲入,那么冲入进去的应该可以收到数据啊,而反而是第一个绑定的能收到数据。
所以我又在linux下启动udp的服务器端程序,同样验证上面的第一个结论,结果发现linux下和windows下不同。
结论是:
3.如果两个进程都绑定的是本地的真实ip,如 ip addr.sin_addr.S_un.S_addr = inet_addr("10.110.12.68");
在linux操作系统中,那谁后绑定谁就可以收到数据。
那么端口复用到底应该用到什么场合呢,看下下面的连接就可以了,
Linux网络编程之绑定端口注意事项及端口复用-51CTO.COM
还有这篇文章:关于SO_REUSEADDR选项的作用 - linehrr-freedom - 博客园
这里要注意的是对于udp是可以有绑相同的ip地址的情况的,但是对于tcp是不行的,我们看下具体的事项如下:对于第一个就是我看尹圣雨的书中提到的。也就是tcp如果你设置了这个选项,那么对应的ip也不能相同,如果一个是0.0.0.0另外一个是真实的IP,也会报错。具体见下面的2和3,而那个上面说的例子中主要是在第4中udp的多播的情况下使用。
SO_REUSEADDR可以用在以下四种情况下。 (摘自《Unix网络编程》卷一,即UNPv1)
1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。(这个是tcp专用的)
2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。
3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。(这里我在linux下试验了,下,要求一个的ip不能是0.0.0.0)
4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
tcp不行的例子如下,当设置两个同样的ip的时候,tcp连接,在使用reuseaddr也会报错。
包括一个用真实的ip,一个用0.0.0.0(127.0.0.1不算)也会报错。代码如下
//
// Created by hczhang on 2022/2/15.
//
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include <errno.h>
#define BUF_SIZE 30
void read_childproc(int sig);
int main(int argc,char *argv[]){
int serv_sock,clnt_sock,serv_sock1;
struct sockaddr_in serv_adr,clnt_adr,serv_adr1;
pid_t pid;
struct sigaction act;
socklen_t adr_sz;
int str_len,state;
char buf[BUF_SIZE];
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
state = sigaction(SIGCHLD,&act,0);
serv_sock = socket(AF_INET,SOCK_STREAM,0);
serv_sock1 = socket(AF_INET,SOCK_STREAM,0);
int option=1;
socklen_t optlen = sizeof(option);
printf("server bind start\n");
setsockopt(serv_sock,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);
setsockopt(serv_sock1,SOL_SOCKET,SO_REUSEADDR,(void*)&option,optlen);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr= htonl(atoi("0.0.0.0"));
serv_adr.sin_port= htons(atoi("12345"));
memset(&serv_adr1,0,sizeof(serv_adr1));
serv_adr1.sin_family=AF_INET;
serv_adr1.sin_addr.s_addr= htonl(atoi("10.2.6.185"));
serv_adr1.sin_port= htons(atoi("12345"));
if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1){
printf("1 bind error:%s\n", strerror(errno));
exit(1);
}
if(bind(serv_sock1,(struct sockaddr*)&serv_adr1,sizeof(serv_adr1))==-1){
printf("2 bind error:%s\n", strerror(errno));
exit(1);
}
if(listen(serv_sock,5)==-1){
printf("1 listen error:%s\n", strerror(errno));
exit(1);
}
if(listen(serv_sock1,5)==-1){
printf("2 listen error:%s\n", strerror(errno));
exit(1);
}
while(1){
adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_adr,&adr_sz);
if(clnt_sock==-1){
printf("accept error:%s\n",strerror(errno));
continue;
}
else
printf("new client connected...\n");
pid = fork();
if(pid ==-1){
close(clnt_sock);
continue;
}
// 子进程运行区域
if(pid == 0){
printf("child process enter!!!\n");
close(serv_sock);//把复制过来的句柄关闭
while((str_len= read(clnt_sock,buf,BUF_SIZE)!=0)){
write(clnt_sock,buf,str_len);
}
close(clnt_sock);
printf("client disconnected...\n");
return 0;
}else{
//父进程执行区域
close(clnt_sock);
}
}
close(serv_sock);
return 0;
}
void read_childproc(int sig){
pid_t pid;
int status;
pid = waitpid(-1,&status,WNOHANG);
printf("removed proc id:%d\n",pid);
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)