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