读Muduo源码笔记---4(TCP自连接)

1、问题原因

  svr挂掉了,端口释放了,cli去connect这个目的端口的时候正好选择了这个端口作为源端口,此时端口没人用,使用是合法的。于是自连接形成了。 就是出现源ip和源端口通目的ip和目的端口完全相同的情况,也就是在服务端没有启动,客户端也可以连接成功,但会造成服务端无法启动。

2、tcp连接分析

   要建立一个tcp连接,首先svr要在b端口上listen,cli再使用a端口connect,端口选择一般是用户不显示bind,由内核代为选择一个空闲端口号,那么,即使svr和cli在一台机器上,因为svr已将b端口占用,cli不管用户bind还是内核选择,都不可能选到b。所以这样看来,同一个端口自连接就是一个伪命题。

同时打开

    在同时打开的过程中,我们站在一方的角度想,它①先发了一个SYN,然后②收到了对端的SYN(而不是SYN+ACK),这时③回复SYN+ACK,当④再次收到SYN+ACK时就认为建连成功。对于自连接过程:为了建立连接,向destport发送一个SYN(完成了步骤①),因为目的ip是自己,因此会被loopback网络接口处理回送给本机TCP/IP协议栈,又因为port是自己,于是这个SYN给了正在等SYN的自己(完成了步骤②),这时需要执行步骤③发送SYN+ACK,同样原理,这个报文段会送给了自己(完成了步骤④),于是连接建立了。

3、自连接测试程序

#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<errno.h>
#include<arpa/inet.h>

#define TRY_CNT 50000

void exec_cmd(char*cmd)
{
  printf("start exec cmd: %s\n\n",cmd);
  system(cmd);
  printf("finish exec cmd:%s\n",cmd);
}

int main(int argc,char**argv)
{
     int cnt;
     int sock;
     int ret;
     struct sockaddr_in seraddr,cliaddr;
     socklen_t addrlen;
     char cmd[1024];
  
     int port;
     if(argc<2)
     {
       printf("need port arg.usage:\nselfconnection port\n");
       return 1;
     }
     snprintf(cmd,sizeof(cmd),"netstat -npt GREP %s",argv[1]);
  
     port=atoi(argv[1]);
     if(port<1000 || port>65535)
     {
          printf("port should be 1000 -65535\n");
          return 1;
     }
      sock=socket(AF_INET,SOCK_STREAM,0);
  
      if(sock<0)
      {
         printf("socket() error.error=%d,errstr=%s\n",errno,strerror(errno));
         return -1;
      }
  
       memset(&seraddr,0,sizeof(seraddr));
       seraddr.sin_family=AF_INET;
       seraddr.sin_port=ntohs(port);
       if(inet_pton(AF_INET,"127.0.0.1",&seraddr.sin_addr.s_addr)<0)
       {
          printf("inet_pton()error");
          return -2;
       }
        printf("remote addr:\t ip = %X, port = %u\n",ntohl(seraddr.sin_addr.s_addr), ntohs(seraddr.sin_port));

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

               // get local addr info
               addrlen = sizeof(cliaddr);
               ret = getsockname(sock, (struct sockaddr *)&cliaddr, &addrlen);
               if( ret < 0 ) 
               {
                   printf("getsockname () error. errno = %d, errstr = %s\n",errno, strerror(errno));
               } 
               else 
               {
                   printf("local addrr:\t ip = %X, port = %u\n",ntohl(cliaddr.sin_addr.s_addr), ntohs(cliaddr.sin_port));
                    exec_cmd(cmd);
               }
                // pause();
               sleep(1);
               break;
            }
                // only print error message of first time and last time
            if( cnt == 0 || cnt == TRY_CNT - 1) 
            {
                        printf("connect () error. errno = %d, errstr = %s\n",errno, strerror(errno));
            }

          }

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

          close(sock);
          sleep(2);
          exec_cmd(cmd);
          return 0;
      
}

4、自连接的避免

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

       每次connect成功后,调用getsockname(),获得本端的ip及port,判断是否等于server端端口,如果是,关闭该连接,继续循环 重试。存在的问题:因为是主动关闭,必然造成该TCP连接处于TIME_WAIT状态,如果Server端此时启动,仍然因为端口被占用导致不能启动成功。解决办法:可以通过设置socket选项,降低等待时间,服务端设置SO_REUSEADDR选项,并当发   现 端口被占用时,过一段时间重试。

  • Client使用固定端口,该端口与Server端不同。

  • 查看系统配置文件。

     在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中的值作为随机端口。

猜你喜欢

转载自blog.csdn.net/qq_28840229/article/details/83268598