端口复用

0人阅读 评论(0) 收藏 举报

实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     int sockfd_one;  
  12.     int err_log;  
  13.     sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one  
  14.     if(sockfd_one < 0)  
  15.     {  
  16.     perror("sockfd_one");  
  17.     exit(-1);  
  18.     }  
  19.   
  20.     // 设置本地网络信息  
  21.     struct sockaddr_in my_addr;  
  22.     bzero(&my_addr, sizeof(my_addr));  
  23.     my_addr.sin_family = AF_INET;  
  24.     my_addr.sin_port = htons(8000);     // 端口为8000  
  25.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  26.   
  27.     // 绑定,端口为8000  
  28.     err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  29.     if(err_log != 0)  
  30.     {  
  31.         perror("bind sockfd_one");  
  32.         close(sockfd_one);        
  33.         exit(-1);  
  34.     }  
  35.   
  36.     int sockfd_two;  
  37.     sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two  
  38.     if(sockfd_two < 0)  
  39.     {  
  40.         perror("sockfd_two");  
  41.         exit(-1);  
  42.     }  
  43.   
  44.     // 新套接字sockfd_two,继续绑定8000端口,绑定失败  
  45.     // 因为8000端口已被占用,默认情况下,端口没有释放,无法绑定  
  46.     err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  47.     if(err_log != 0)  
  48.     {  
  49.         perror("bind sockfd_two");  
  50.         close(sockfd_two);        
  51.         exit(-1);  
  52.     }  
  53.   
  54.     close(sockfd_one);  
  55.     close(sockfd_two);  
  56.   
  57.     return 0;  
  58. }  

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



那如何让sockfd_one, sockfd_two两个套接字都能成功绑定8000端口呢?这时候就需要要到端口复用了。端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。

设置socket的SO_REUSEADDR选项,即可实现端口复用:

  1. int opt = 1;  
  2. // sockfd为需要端口复用的套接字  
  3. 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。


需要注意的是,设置端口复用函数要在绑定之前调用,而且只要绑定到同一个端口的所有套接字都得设置复用

  1. // sockfd_one, sockfd_two都要设置端口复用  
  2. // 在sockfd_one绑定bind之前,设置其端口复用  
  3. int opt = 1;  
  4. setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR, (const void *)&opt, sizeof(opt) );  
  5. err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  6.   
  7. // 在sockfd_two绑定bind之前,设置其端口复用  
  8. opt = 1;  
  9. setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt) );  
  10. err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));  

端口复用完整代码如下:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     int sockfd_one;  
  12.     int err_log;  
  13.     sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one  
  14.     if(sockfd_one < 0)  
  15.     {  
  16.     perror("sockfd_one");  
  17.     exit(-1);  
  18.     }  
  19.   
  20.     // 设置本地网络信息  
  21.     struct sockaddr_in my_addr;  
  22.     bzero(&my_addr, sizeof(my_addr));  
  23.     my_addr.sin_family = AF_INET;  
  24.     my_addr.sin_port = htons(8000);     // 端口为8000  
  25.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  26.       
  27.     // 在sockfd_one绑定bind之前,设置其端口复用  
  28.     int opt = 1;  
  29.     setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR,   
  30.                     (const void *)&opt, sizeof(opt) );  
  31.   
  32.     // 绑定,端口为8000  
  33.     err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  34.     if(err_log != 0)  
  35.     {  
  36.         perror("bind sockfd_one");  
  37.         close(sockfd_one);        
  38.         exit(-1);  
  39.     }  
  40.   
  41.     int sockfd_two;  
  42.     sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two  
  43.     if(sockfd_two < 0)  
  44.     {  
  45.         perror("sockfd_two");  
  46.         exit(-1);  
  47.     }  
  48.   
  49.     // 在sockfd_two绑定bind之前,设置其端口复用  
  50.     opt = 1;  
  51.     setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,   
  52.                     (const void *)&opt, sizeof(opt) );  
  53.       
  54.     // 新套接字sockfd_two,继续绑定8000端口,成功  
  55.     err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  56.     if(err_log != 0)  
  57.     {  
  58.         perror("bind sockfd_two");  
  59.         close(sockfd_two);        
  60.         exit(-1);  
  61.     }  
  62.   
  63.     close(sockfd_one);  
  64.     close(sockfd_two);  
  65.   
  66.     return 0;  
  67. }  

端口复用允许在一个应用程序可以把 n 个套接字绑在一个端口上而不出错。同时,这 n 个套接字发送信息都正常,没有问题。但是,这些套接字并不是所有都能读取信息,只有最后一个套接字会正常接收数据。


下面,我们在之前的代码上,添加两个线程,分别负责接收sockfd_one,sockfd_two的信息:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <arpa/inet.h>  
  8. #include <pthread.h>  
  9.   
  10. // 线程1的回调函数  
  11. void *recv_one(void *arg)  
  12. {  
  13.     printf("===========recv_one==============\n");  
  14.     int sockfd = (int )arg;  
  15.     while(1){  
  16.         int recv_len;  
  17.         char recv_buf[512] = "";  
  18.         struct sockaddr_in client_addr;  
  19.         char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16  
  20.         socklen_t cliaddr_len = sizeof(client_addr);  
  21.           
  22.         recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);  
  23.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
  24.         printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));  
  25.         printf("sockfd_one =========== data(%d):%s\n",recv_len,recv_buf);  
  26.       
  27.     }  
  28.   
  29.     return NULL;  
  30. }  
  31.   
  32. // 线程2的回调函数  
  33. void *recv_two(void *arg)  
  34. {  
  35.     printf("+++++++++recv_two++++++++++++++\n");  
  36.     int sockfd = (int )arg;  
  37.     while(1){  
  38.         int recv_len;  
  39.         char recv_buf[512] = "";  
  40.         struct sockaddr_in client_addr;  
  41.         char cli_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16  
  42.         socklen_t cliaddr_len = sizeof(client_addr);  
  43.           
  44.         recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len);  
  45.         inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);  
  46.         printf("\nip:%s ,port:%d\n",cli_ip, ntohs(client_addr.sin_port));  
  47.         printf("sockfd_two @@@@@@@@@@@@@@@ data(%d):%s\n",recv_len,recv_buf);  
  48.       
  49.     }  
  50.   
  51.     return NULL;  
  52. }  
  53.   
  54. int main(int argc, char *argv[])  
  55. {  
  56.     int err_log;  
  57.       
  58.     /////////////////////////sockfd_one  
  59.     int sockfd_one;  
  60.     sockfd_one = socket(AF_INET, SOCK_DGRAM, 0); //创建UDP套接字one  
  61.     if(sockfd_one < 0)  
  62.     {  
  63.     perror("sockfd_one");  
  64.     exit(-1);  
  65.     }  
  66.   
  67.     // 设置本地网络信息  
  68.     struct sockaddr_in my_addr;  
  69.     bzero(&my_addr, sizeof(my_addr));  
  70.     my_addr.sin_family = AF_INET;  
  71.     my_addr.sin_port = htons(8000);     // 端口为8000  
  72.     my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  73.       
  74.     // 在sockfd_one绑定bind之前,设置其端口复用  
  75.     int opt = 1;  
  76.     setsockopt( sockfd_one, SOL_SOCKET,SO_REUSEADDR,   
  77.                     (const void *)&opt, sizeof(opt) );  
  78.   
  79.     // 绑定,端口为8000  
  80.     err_log = bind(sockfd_one, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  81.     if(err_log != 0)  
  82.     {  
  83.         perror("bind sockfd_one");  
  84.         close(sockfd_one);        
  85.         exit(-1);  
  86.     }  
  87.       
  88.     //接收信息线程1  
  89.     pthread_t tid_one;  
  90.     pthread_create(&tid_one, NULL, recv_one, (void *)sockfd_one);  
  91.       
  92.     /////////////////////////sockfd_two  
  93.     int sockfd_two;  
  94.     sockfd_two = socket(AF_INET, SOCK_DGRAM, 0);  //创建UDP套接字two  
  95.     if(sockfd_two < 0)  
  96.     {  
  97.         perror("sockfd_two");  
  98.         exit(-1);  
  99.     }  
  100.   
  101.     // 在sockfd_two绑定bind之前,设置其端口复用  
  102.     opt = 1;  
  103.     setsockopt( sockfd_two, SOL_SOCKET,SO_REUSEADDR,   
  104.                     (const void *)&opt, sizeof(opt) );  
  105.       
  106.     // 新套接字sockfd_two,继续绑定8000端口,成功  
  107.     err_log = bind(sockfd_two, (struct sockaddr*)&my_addr, sizeof(my_addr));  
  108.     if(err_log != 0)  
  109.     {  
  110.         perror("bind sockfd_two");  
  111.         close(sockfd_two);        
  112.         exit(-1);  
  113.     }  
  114.     //接收信息线程2  
  115.     pthread_t tid_two;  
  116.     pthread_create(&tid_two, NULL, recv_two, (void *)sockfd_two);  
  117.       
  118.       
  119.     while(1){   // 让程序阻塞在这,不结束  
  120.         NULL;  
  121.     }  
  122.   
  123.     close(sockfd_one);  
  124.     close(sockfd_two);  
  125.   
  126.     return 0;  
  127. }  

接着,通过网络调试助手给这个服务器发送数据,结果显示,只有最后一个套接字sockfd_two会正常接收数据:



我们上面的用法,实际上没有太大的意义。端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中——那只好等等再重试了,麻烦!

查看评论

端口复用突破防火墙(图)

端口复用突破防火墙(图) 时间:2009-08-03 10:50 来源:bitsCN.com 字体:[大 中 小] 评论:0  如何在溢出后得到安全的、隐蔽的Shell是大家一直都在讨论的问题,因为现...
  • liujiayu2
  • liujiayu2
  • 2014-10-25 10:55:12
  • 1333

WebServer端口复用后门

0x00 有朋友问到了我一个关于“无端口可用”的问题。说在如下图所示的内网环境中,firewall只允许Web Server的80端口建立网络连接,并且Web Server上的80端口已经被II...
  • weicool8886
  • weicool8886
  • 2016-03-11 17:16:22
  • 1139

(VC代码) 端口复用的思路与实现

端口复用在黑客后门程序中十分重要,当你侵入一台服务器时,这台服务器如果安装了防火墙,则使用一般的后门程序时,后门程序使用的是防火墙禁止的端口,则可以通过复用防火墙允许的端口来与后门程序进行数据交流,比...
  • minaiyi
  • minaiyi
  • 2014-05-08 18:54:28
  • 1640

SOCKET端口复用

在WINDOWS的SOCKET服务器应用的编程中,如下的语句或许比比都是:  s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); saddr.sin_family...
  • menghaibin2009
  • menghaibin2009
  • 2013-10-09 16:47:13
  • 1365

C++端口复用代码

主要用到的函数: int setsockopt( SOCKET s, int level, int optname, const char FAR *optval, int optlen ); s:标...
  • liujiayu2
  • liujiayu2
  • 2014-10-25 10:55:51
  • 1382

端口复用:隐藏 嗅探与攻击

前言 在WINDOWS的SOCKET服务器应用的编程中,如下的语句或许比比都是: s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); saddr.sin_family...
  • liujiayu2
  • liujiayu2
  • 2015-01-21 17:27:22
  • 889

socket端口复用

当我们使用socket通信时,有时需要关闭服务器,再重新开启 需要绑定的是同一个端口号 那么问题来了:当迅速关闭再重新创建socket之后bind会出错 根据网上资料,socket关闭后释放端口...
  • fzs333
  • fzs333
  • 2015-03-04 11:13:20
  • 938

多份HTTP服务80端口复用问题

我们知道:1台主机有65536个端口(0~65535),每个进程会占用1个端口,HTTP协议的默认端口是80。问题现有多份HTTP服务(设作HTTPD,IIS,Tomcat)要运行时,若都想用默认端口...
  • flashdelover
  • flashdelover
  • 2016-09-13 21:54:52
  • 3915

PHP下实现端口复用/劫持

经常看到有朋友问端口复用,我花了一个晚上写了一点代码本来想做成嗅叹转发的,无奈单线程的东西很局限,而且始终只能得到头行数据。我想要做成那样的,对于高手来说,不是困难的事。如果你不懂如何配置,请参照我以...
  • kj021320
  • kj021320
  • 2007-07-16 15:38:00
  • 1909
    个人资料
    等级:
    访问量: 1018
    积分: 38
    排名: 183万+
    文章分类