一个奇怪的socket 端口冲突问题

时间:2022-12-15 08:07:43
碰到一个自己解释不通的问题,大家看看怎么解释:
客户端代码:
int main(int argc, char* argv[]){
        int ilRet = 0;

        while(1){
                struct sockaddr_in slAddr;
                bzero(&slAddr, sizeof(slAddr));
                slAddr.sin_family = AF_INET;
                slAddr.sin_port = htons(44445);
                slAddr.sin_addr.s_addr = inet_addr("172.17.236.122");                
                int sockfd = socket(AF_INET, SOCK_STREAM, 0);
                if (-1 == sockfd) {
                        printf("socket()fail, errno[%d], errmsg[%s]\n", errno, strerror(errno));
                        return -1;
                }
                printf("socket ok!\n");

                /*const int on = 1;
                if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1 ) {
                        printf("setsockopt sockfd[%d] fail, errno[%d], errmsg[%s]\n", 
                                        sockfd, errno,strerror(errno));
                        return -1;
                }
                printf("setsockopt ok!\n");*/

                printf("connect!\n");
                unsigned int len = sizeof(slAddr);
                if (connect(sockfd, (struct sockaddr*)&slAddr, len) == 0){
                        printf("connect ok!\n");
                        sleep(10000000);
                        char buf[100];
                        if(recv(sockfd,buf, 100, 0) <= 0){
                                printf("recv error!\n");
                                close(sockfd);
                                continue;
                        }

                        break;
                }
                else{
                        printf("connect fail, errno[%d], errmsg[%s]\n", errno, strerror(errno));
                }
                sleep(1);
        }


}


启动客户端连接某主机,连接成功:
netstat -an |grep 44445
tcp        0      0 172.18.64.64:51365      172.17.236.122:44445    ESTABLISHED 

取出其中的随机端口51365
服务器代码:
int main(int argc, char* argv[]){
        int ilRet = 0;
        int fd = 0;

        struct sockaddr_in slAddr;
        bzero(&slAddr, sizeof(slAddr));
        slAddr.sin_family = AF_INET;
        slAddr.sin_port = htons(51365);
        //slAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        slAddr.sin_addr.s_addr = inet_addr("172.18.64.64");
        /* inet_pton(AF_INET, paIP, &slAddr.sin_addr); */

        struct sockaddr_in pAddr;
        bzero(&pAddr, sizeof(pAddr));

        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (-1 == sockfd) {
                printf("socket()fail, errno[%d], errmsg[%s]\n", errno, strerror(errno));
                return -1;
        }
        printf("socket ok!\n");

        int on = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0 ) {
                printf("setsockopt sockfd[%d] fail, errno[%d], errmsg[%s]\n",
                                sockfd, errno,strerror(errno));
                return -1;
        }
        printf("setsockopt ok!\n");

        if (bind(sockfd, (struct sockaddr *)&slAddr, sizeof(slAddr)) != 0) {
                printf("bind()sockfd[%d]fail, errno[%d], errmsg[%s]\n",
                                sockfd, errno,strerror(errno));
                close(sockfd);
                return -1;
        }
        printf("bind ok!\n");


        if (listen(sockfd, 5) != 0) {
                close(sockfd);
                printf("listen fail, errno[%d], errmsg[%s]\n", errno,strerror(errno));
                return -1;
        }
        printf("listen ok!\n");
/*后面省略1000字*/


服务器端监听端口填写成客户端的随机端口,在客户端启动的机器上(同一台机器)启动服务端
报错:
socket ok!
setsockopt ok!
bind()sockfd[3]fail, errno[98], errmsg[Address already in use]


再次修改客户端的代码,将客户端代码中
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)的注释打开,重新启动客户端和服务端程序,服务端正常监听:
socket ok!
setsockopt ok!
bind ok!
listen ok!

netstat -an |grep 51365
tcp        0      0 172.18.64.64:51365      0.0.0.0:*               LISTEN      
tcp        0      0 172.18.64.64:51365      172.17.236.122:44445    ESTABLISHED 


系统版本信息:
cat /etc/issue  

Welcome to SUSE Linux Enterprise Server 11 SP2  (x86_64) - Kernel \r (\l).

lsb_release -a
LSB Version:    core-2.0-noarch:core-3.2-noarch:core-4.0-noarch:core-2.0-x86_64:core-3.2-x86_64:core-4.0-x86_64:desktop-4.0-amd64:desktop-4.0-noarch:graphics-2.0-amd64:graphics-2.0-noarch:graphics-3.2-amd64:graphics-3.2-noarch:graphics-4.0-amd64:graphics-4.0-noarch
Distributor ID: SUSE LINUX
Description:    SUSE Linux Enterprise Server 11 (x86_64)
Release:        11
Codename:       n/a


不解的问题:
1、随机端口被占用,比如说45000,那么在45000上的监听就起不来么?那这么说所有的监听端口是不是都要在随机端口的范围下面才合适?有书上这么说么,比如说UNP或者TCP/IP详解,没见到。。
2、SO_REUSEADDR我所理解就是解决服务端TIME_WAIT的问题(大部分应用都是这么用的),今天的这个例子怎么解释,用UNP里面SO_REUSEADDR应用说明的四条中的那一条解释合适?

球大侠现身。。。

7 个解决方案

#1


SO_REUSERPORT是可以多进程bind同一个Port的,但只有linux3.x内核才刚刚支持这个选项,之前此选项是不起作用的。

SO_REUSERADDR是复用TIME_WAIT的,没问题。

#2


引用 1 楼 qq120848369 的回复:
SO_REUSERPORT是可以多进程bind同一个Port的,但只有linux3.x内核才刚刚支持这个选项,之前此选项是不起作用的。

SO_REUSERADDR是复用TIME_WAIT的,没问题。


多提一句吧,windows的socket早就支持SO_REUSERPORT这个功能了,很多木马会扫除比如QQ正在使用的通讯端口,然后也bind到这个端口上,从而避免杀软发现自己。

你只要知道TCP是4元组,那么肯定知道在本地bind 无数次同一个Port是毫无问题的,只是linux内核一直把SO_REUSERPORT这个选项别扭着没实现。

#3


多谢版主出马,SO_REUSEPORT目前并不支持;
问题依旧:
1、随机端口被占用,比如说51365,那么在51365上的监听就起不来么?(这个是我碰到的问题的根源)
那这么说所有的监听端口是不是都要在随机端口的范围下面才合适?
netstat -an |grep 44445
tcp        0      0 172.18.64.64:51365      172.17.236.122:44445    ESTABLISHED 

44445是我主机的一个监听,实验主机用的客户端程序连接到44445,产生随机端口51365
然后悲剧发生了:实验主机上的服务端程序绑定51365失败了。。。
socket ok!
setsockopt ok!
bind()sockfd[3]fail, errno[98], errmsg[Address already in use]
主要是这个怎么解释???

2、我在实验主机的客户端connect之前SO_REUSEADDR,问题就解决了,相当于clt和svr都设置了SO_REUSEADDR才可以。。。
当然,作为服务端程序,第一次碰到跟别人的随机端口冲突,也不能指望别人调用之前SO_REUSEADDR,更不能寄希望系统不会分配到跟我们监听端口一样的随机端口;
这个该怎么解释??

这个问题最终是可以解决的,就是listen在随机端口范围以下的端口就ok了;
但是1和2是为什么????

#4


在SO_REUSEPORT尚未普及的情况下, 内核中的每个port只应属于一个process,但是有两个特例:

1,bind后fork的。
2,listen后accept的。

楼主问题1:既然随机端口被占用着,你bind肯定是失败的,除非有REUSERPORT选项。
楼主问题2:你是说系统会给你分配到一个已经bind的端口?不太可能。

#5


版主说要用REUSERPORT选项来实现,但是我的这个例子中用SO_REUSEADDR已经可以解决这个冲突问题了,但是苦于在UNP上SO_REUSEADDR的四条用法中找不到对应的解释:
SO_REUSEADDR可以用在以下四种情况下: 
    (摘自《Unix网络编程》卷一,即UNPv1) 
    1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启
动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。 
    2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但
每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可
以测试这种情况。 
    3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个soc
ket绑定的ip地址不同。这和2很相似,区别请看UNPv1。 
    4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
多播,不用于TCP。

我所纠结的问题在这儿:
1、既然存在这种跟随机端口冲突的问题,那我的监听端口该设定为多少才能保证不会跟随机端口冲突,就是svr监听端口的取值范围问题
2、从上述实验中只有clt和svr都设置了SO_REUSEADDR,绑定在该端口上的bind操作才会成功,只要一个没有设置就errno98,如何解释,必须要clt也设置SO_REUSEADDR;
3、实际上我在AIX上也做过实验,AIX只需要svr设置了SO_REUSEADDR就可以bind在一个已经分配了的随机端口上,还是想知道如何解释这个实现机制。

#6


再说一下实验过程吧:
SUSE Linux :
1、客户端socket  connect 172.17.233.122:44445
2、连接成功 tcp  0  0 172.18.64.64:54050  172.17.236.122:44445    ESTABLISHED 
3、服务端socket  setsockopt(SO_REUSEADDR) bind(172.18.64.64:54050)  listen accept
4、启动服务端,bind失败,errno98


1、客户端socket   setsockopt(SO_REUSEADDR) connect 172.17.233.122:44445
2、连接成功 tcp  0  0 172.18.64.64:54052  172.17.236.122:44445    ESTABLISHED 
3、服务端socket  setsockopt(SO_REUSEADDR) bind(172.18.64.64:54052)  listen accept
4、启动服务端,bind成功

IBM AIX:
上述两种情况都bind成功,只要svr设置了setsockopt(SO_REUSEADDR);
上述两种情况都bind失败,只要svr没有设置setsockopt(SO_REUSEADDR);

#7


自己顶一个。。。

#1


SO_REUSERPORT是可以多进程bind同一个Port的,但只有linux3.x内核才刚刚支持这个选项,之前此选项是不起作用的。

SO_REUSERADDR是复用TIME_WAIT的,没问题。

#2


引用 1 楼 qq120848369 的回复:
SO_REUSERPORT是可以多进程bind同一个Port的,但只有linux3.x内核才刚刚支持这个选项,之前此选项是不起作用的。

SO_REUSERADDR是复用TIME_WAIT的,没问题。


多提一句吧,windows的socket早就支持SO_REUSERPORT这个功能了,很多木马会扫除比如QQ正在使用的通讯端口,然后也bind到这个端口上,从而避免杀软发现自己。

你只要知道TCP是4元组,那么肯定知道在本地bind 无数次同一个Port是毫无问题的,只是linux内核一直把SO_REUSERPORT这个选项别扭着没实现。

#3


多谢版主出马,SO_REUSEPORT目前并不支持;
问题依旧:
1、随机端口被占用,比如说51365,那么在51365上的监听就起不来么?(这个是我碰到的问题的根源)
那这么说所有的监听端口是不是都要在随机端口的范围下面才合适?
netstat -an |grep 44445
tcp        0      0 172.18.64.64:51365      172.17.236.122:44445    ESTABLISHED 

44445是我主机的一个监听,实验主机用的客户端程序连接到44445,产生随机端口51365
然后悲剧发生了:实验主机上的服务端程序绑定51365失败了。。。
socket ok!
setsockopt ok!
bind()sockfd[3]fail, errno[98], errmsg[Address already in use]
主要是这个怎么解释???

2、我在实验主机的客户端connect之前SO_REUSEADDR,问题就解决了,相当于clt和svr都设置了SO_REUSEADDR才可以。。。
当然,作为服务端程序,第一次碰到跟别人的随机端口冲突,也不能指望别人调用之前SO_REUSEADDR,更不能寄希望系统不会分配到跟我们监听端口一样的随机端口;
这个该怎么解释??

这个问题最终是可以解决的,就是listen在随机端口范围以下的端口就ok了;
但是1和2是为什么????

#4


在SO_REUSEPORT尚未普及的情况下, 内核中的每个port只应属于一个process,但是有两个特例:

1,bind后fork的。
2,listen后accept的。

楼主问题1:既然随机端口被占用着,你bind肯定是失败的,除非有REUSERPORT选项。
楼主问题2:你是说系统会给你分配到一个已经bind的端口?不太可能。

#5


版主说要用REUSERPORT选项来实现,但是我的这个例子中用SO_REUSEADDR已经可以解决这个冲突问题了,但是苦于在UNP上SO_REUSEADDR的四条用法中找不到对应的解释:
SO_REUSEADDR可以用在以下四种情况下: 
    (摘自《Unix网络编程》卷一,即UNPv1) 
    1、当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启
动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。 
    2、SO_REUSEADDR允许同一port上启动同一服务器的多个实例(多个进程)。但
每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可
以测试这种情况。 
    3、SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个soc
ket绑定的ip地址不同。这和2很相似,区别请看UNPv1。 
    4、SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的
多播,不用于TCP。

我所纠结的问题在这儿:
1、既然存在这种跟随机端口冲突的问题,那我的监听端口该设定为多少才能保证不会跟随机端口冲突,就是svr监听端口的取值范围问题
2、从上述实验中只有clt和svr都设置了SO_REUSEADDR,绑定在该端口上的bind操作才会成功,只要一个没有设置就errno98,如何解释,必须要clt也设置SO_REUSEADDR;
3、实际上我在AIX上也做过实验,AIX只需要svr设置了SO_REUSEADDR就可以bind在一个已经分配了的随机端口上,还是想知道如何解释这个实现机制。

#6


再说一下实验过程吧:
SUSE Linux :
1、客户端socket  connect 172.17.233.122:44445
2、连接成功 tcp  0  0 172.18.64.64:54050  172.17.236.122:44445    ESTABLISHED 
3、服务端socket  setsockopt(SO_REUSEADDR) bind(172.18.64.64:54050)  listen accept
4、启动服务端,bind失败,errno98


1、客户端socket   setsockopt(SO_REUSEADDR) connect 172.17.233.122:44445
2、连接成功 tcp  0  0 172.18.64.64:54052  172.17.236.122:44445    ESTABLISHED 
3、服务端socket  setsockopt(SO_REUSEADDR) bind(172.18.64.64:54052)  listen accept
4、启动服务端,bind成功

IBM AIX:
上述两种情况都bind成功,只要svr设置了setsockopt(SO_REUSEADDR);
上述两种情况都bind失败,只要svr没有设置setsockopt(SO_REUSEADDR);

#7


自己顶一个。。。