1. 程式人生 > >讀Muduo原始碼筆記(TCP自連線)---4

讀Muduo原始碼筆記(TCP自連線)---4

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中的值作為隨機埠。