Chinaunix首页 | 论坛 | 博客
  • 博客访问: 65159
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 160
  • 用 户 组: 普通用户
  • 注册时间: 2014-11-04 17:12
个人简介

It

文章分类

全部博文(15)

文章存档

2015年(13)

2014年(2)

我的朋友

分类: LINUX

2015-06-15 11:13:30

一、问题描述

今天一个同事遇到一个问题,问题如下:
  他的程序的运行操作系统是linux,作为TCP Client 端,需要连接本机上的另一程序(TCP Server端, 端口为39000)。TCP Server端程序不是并不是总是启动着,TCP Client端程序在连接不到Server端时,等待10分钟,再重新连接Server端,不断重试。昨天他遇到了这样的问题,Server端未启动,但他的程序Client端可以连接成功,造成Server端程序因为端口被占用,无法启动!

他描述完问题,我第一反应就是发生了自连接。关于自连接为什么是合法的,可以查看相关资料。

二、产生自连接程序测试程序


点击(此处)折叠或打开

  1. #include <unistd.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <sys/types.h> /* See NOTES */
  5. #include <sys/socket.h>
  6. #include <errno.h>
  7. #include <arpa/inet.h>

  8. #define TRY_CNT 60000

  9. void exec_cmd(char *cmd) {
  10.         printf("\n**********start exec cmd: %s **********\n\n", cmd);
  11.         system(cmd);
  12.         printf("\n\n**********finish exec cmd: %s **********\n", cmd);
  13. }

  14. int main(int argc, char *argv[]) {
  15.         int cnt ;

  16.         int sock;
  17.         int ret;
  18.         struct sockaddr_in seraddr, cliaddr;
  19.         socklen_t addrlen;
  20.         char cmd[1024];

  21.         int port;

  22.         if(argc < 2) {
  23.                 printf("need port arg. usage:\nselfconntest port\n");
  24.                 return 1;
  25.         }
  26.         snprintf(cmd, sizeof(cmd), "netstat -npt | grep %s", argv[1]);

  27.         port = atoi(argv[1]);
  28.         if(port < 1000 || port > 65535) {
  29.                 printf("port should be 1000 - 65535\n");
  30.                 return 1;
  31.         }

  32.         sock = socket(AF_INET, SOCK_STREAM, 0);
  33.         if( sock < 0 ) {
  34.                 printf("socket() error. errno = %d, errstr = %s\n",
  35.                         errno, strerror(errno));
  36.                 return -1;
  37.         }

  38.         // init seraddr
  39.         memset(&seraddr, 0, sizeof(seraddr));
  40.         seraddr.sin_family = AF_INET;
  41.         seraddr.sin_port = ntohs(port);
  42.         if( inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr) < 0 ) {
  43.                 printf("inet_pton() error");
  44.                 return -2;
  45.         }

  46.         printf("remote addr:\t ip = %X, port = %u\n",
  47.                 ntohl(seraddr.sin_addr.s_addr), ntohs(seraddr.sin_port));

  48.         for(cnt = 0; cnt < TRY_CNT; cnt++) {
  49.                 ret = connect(sock, (const struct sockaddr *)&seraddr, sizeof(seraddr));
  50.                 if( ret == 0 ) {
  51.                         printf("try %d times, connect succ.\n", cnt + 1);

  52.                         // get local addr info
  53.                         addrlen = sizeof(cliaddr);
  54.                         ret = getsockname(sock, (struct sockaddr *)&cliaddr, &addrlen);
  55.                         if( ret < 0 ) {
  56.                                 printf("getsockname () error. errno = %d, errstr = %s\n",
  57.                                         errno, strerror(errno));
  58.                         } else {
  59.                                 printf("local addrr:\t ip = %X, port = %u\n",
  60.                                         ntohl(cliaddr.sin_addr.s_addr), ntohs(cliaddr.sin_port));
  61.                                 exec_cmd(cmd);
  62.                         }
  63.                 // pause();
  64.                         sleep(1);
  65.                         break;
  66.                 }
  67.                 // only print error message of first time and last time
  68.                 if( cnt == 0 || cnt == TRY_CNT - 1) {
  69.                         printf("connect () error. errno = %d, errstr = %s\n",
  70.                                 errno, strerror(errno));
  71.                 }

  72.         }

  73.         printf("cnt = %d\n", cnt+1);

  74.         close(sock);
  75.         sleep(2);
  76.         exec_cmd(cmd);
  77.         return 0;
  78. }
执行测试程序如下:
./selfconntest 39000
remote addr:     ip = 7F000001, port = 39000
connect () error. errno = 111, errstr = Connection refused
try 1997 times, connect succ.
local addrr:     ip = 7F000001, port = 39000

**********start exec cmd: netstat -npt | grep 39000 **********

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp   0   0 127.0.0.1:39000      127.0.0.1:39000      ESTABLISHED 8512/./selfconntest


**********finish exec cmd: netstat -npt | grep 39000 **********
cnt = 1997

**********start exec cmd: netstat -npt | grep 39000 **********

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:39000             127.0.0.1:39000             TIME_WAIT   -                   


**********finish exec cmd: netstat -npt | grep 39000 **********


三、如何避免自连接?


1. Client端连接成功后,然后判断是否是自连接。

该方法在一定程度上可行,每次connect成功后,调用getsockname(),获得本端的ip及port,判断是否等于server端端口,如果是,关闭该连接,继续循环重试。
但该方法仍然存在一个问题,就是判断出是自连接,并关闭,但因为是主动关闭,必然造成该TCP连接处于TIME_WAIT状态,如果Server端此时启动,仍然因为端口被占用导致不能启动成功。这个时间窗口,会因为你设置socket的选项,可以缩小窗口,降低发生Server端不能启动的概率,但仍然无法避免。所以Server端也应该进行相应的修改,设置SO_REUSEADDR套接口选项,并当发现端口被占用时,过一段时间重试。
这样一个问题,导致Client与Server都需要进行修改,而且Server端的开发人员还不是一个组的时候,确实有点大动干戈。

2. Client使用固定端口,该端口与Server端不同

Client端在连接前bind本地固定端口,然后再connect Server端。
这种方式能够解决问题。但仍然需要修改Client程序,如果有多个Client端,要申请多个固定的端口号。

3. 查看系统配置文件,未雨绸缪。

在linux系统,系统随机分配未绑定的客户端的端口号,是有一定规律,并可以配置的,随机本地随机端口的分配的区间是ip_local_port_range。
查看 /etc/sysctl.conf ,看一下是否有对 net.ipv4.ip_local_port_range 以及 net.ipv4.ip_local_reserved_ports的设置,如果没有,再查看/proc/sys/net/ipv4/ip_local_port_range 以及 /proc/sys/net/ipv4/ip_local_reserved_ports中的值。
我机器上的值如下:
cat /proc/sys/net/ipv4/ip_local_port_range/ip_local_port_range
32768    61000

总的来说就是,系统分配Client端的随机端口规律是:ip_local_port_range 区间的值 去掉 ip_local_reserved_ports 后剩下的端口。

未雨绸缪就是Server端程序的端口号最好不要选择ip_local_port_range区间内的端口,这样Client如果使用随机端口是在ip_local_port_range区间内,这样也就不会发生本机上的自连接。
如果Server端的端口号已经固定,并在 ip_local_port_range区间内,那么可以设置 ip_local_reserved_ports 为该Server端的端口号,那么Client就不会使用ip_local_reserved_ports中的值作为随机端口。




阅读(3175) | 评论(0) | 转发(0) |
0

上一篇:8. nginx重要结构

下一篇:nginx配置文件解析

给主人留下些什么吧!~~