关于Socket中端口复用(udp)

最近在处理单位的外场遇到了一个实际问题。

在实际与第三方系统进行通信的过程中,我绑定了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);
}

  • 9
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值