久久国产精品一区二区三区四区,久色婷婷小香蕉久久,国产日韩欧美在线播放不卡,另类av一区二区

焦點信息:美團二面:TCP 四次揮手,可以變成三次嗎?
來源:小林coding    時間:2022-08-27 15:56:07

大家好,我是小林,又到了愉快的周末,我來水一水。


(相關資料圖)

上周有位讀者面美團時,被問到:TCP 四次揮手中,能不能把第二次的 ACK 報文, 放到第三次 FIN 報文一起發送?

雖然我們在學習 TCP 揮手時,學到的是需要四次來完成 TCP 揮手,但是在一些情況下, TCP 四次揮手是可以變成 TCP 三次揮手的。

而且在用 wireshark 工具抓包的時候,我們也會??吹?TCP 揮手過程是三次,而不是四次,如下圖:

先來回答為什么 RFC 文檔里定義 TCP 揮手過程是要四次?

再來回答什么情況下,什么情況會出現三次揮手?

為什么 TCP 揮手需要四次?

TCP 四次揮手的過程如下:

具體過程:

客戶端主動調用關閉連接的函數,于是就會發送 FIN 報文,這個 FIN 報文代表客戶端不會再發送數據了,進入 FIN_WAIT_1 狀態;服務端收到了 FIN 報文,然后馬上回復一個 ACK 確認報文,此時服務端進入 CLOSE_WAIT 狀態。在收到 FIN 報文的時候,TCP 協議棧會為 FIN 包插入一個文件結束符 EOF 到接收緩沖區中,服務端應用程序可以通過 read 調用來感知這個 FIN 包,這個 EOF 會被放在已排隊等候的其他已接收的數據之后,所以必須要得繼續 read 接收緩沖區已接收的數據;接著,當服務端在 read 數據的時候,最后自然就會讀到 EOF,接著read() 就會返回 0,這時服務端應用程序如果有數據要發送的話,就發完數據后才調用關閉連接的函數,如果服務端應用程序沒有數據要發送的話,可以直接調用關閉連接的函數,這時服務端就會發一個 FIN 包,這個 FIN 報文代表服務端不會再發送數據了,之后處于 LAST_ACK 狀態;客戶端接收到服務端的 FIN 包,并發送 ACK 確認包給服務端,此時客戶端將進入 TIME_WAIT 狀態;服務端收到 ACK 確認包后,就進入了最后的 CLOSE 狀態;客戶端經過 2MSL 時間之后,也進入 CLOSE 狀態;

你可以看到,每個方向都需要一個 FIN 和一個 ACK,因此通常被稱為四次揮手。

為什么 TCP 揮手需要四次呢?

服務器收到客戶端的 FIN 報文時,內核會馬上回一個 ACK 應答報文,但是服務端應用程序可能還有數據要發送,所以并不能馬上發送 FIN 報文,而是將發送 FIN 報文的控制權交給服務端應用程序:

如果服務端應用程序有數據要發送的話,就發完數據后,才調用關閉連接的函數;

如果服務端應用程序沒有數據要發送的話,可以直接調用關閉連接的函數,

從上面過程可知,是否要發送第三次揮手的控制權不在內核,而是在被動關閉方(上圖的服務端)的應用程序,因為應用程序可能還有數據要發送,由應用程序決定什么時候調用關閉連接的函數,當調用了關閉連接的函數,內核就會發送 FIN 報文了,所以服務端的 ACK 和 FIN 一般都會分開發送。

 

FIN 報文一定得調用關閉連接的函數,才會發送嗎?

 

不一定。

如果進程退出了,不管是不是正常退出,還是異常退出(如進程崩潰),內核都會發送 FIN 報文,與對方完成四次揮手。

粗暴關閉 vs 優雅關閉

前面介紹 TCP 四次揮手的時候,并沒有詳細介紹關閉連接的函數,其實關閉的連接的函數有兩種函數:

close 函數,同時 socket 關閉發送方向和讀取方向,也就是 socket 不再有發送和接收數據的能力;shutdown 函數,可以指定 socket 只關閉發送方向而不關閉讀取方向,也就是 socket 不再有發送數據的能力,但是還是具有接收數據的能力;

如果客戶端是用 close 函數來關閉連接,那么在 TCP 四次揮手過程中,如果收到了服務端發送的數據,由于客戶端已經不再具有發送和接收數據的能力,所以客戶端的內核會回 RST 報文給服務端,然后內核會釋放連接,這時就不會經歷完成的 TCP 四次揮手,所以我們常說,調用 close 是粗暴的關閉。

當服務端收到 RST 后,內核就會釋放連接,當服務端應用程序再次發起讀操作或者寫操作時,就能感知到連接已經被釋放了:

如果是讀操作,則會返回 RST 的報錯,也就是我們常見的Connection reset by peer。如果是寫操作,那么程序會產生 SIGPIPE 信號,應用層代碼可以捕獲并處理信號,如果不處理,則默認情況下進程會終止,異常退出。

相對的,shutdown 函數因為可以指定只關閉發送方向而不關閉讀取方向,所以即使在 TCP 四次揮手過程中,如果收到了服務端發送的數據,客戶端也是可以正常讀取到該數據的,然后就會經歷完整的 TCP 四次揮手,所以我們常說,調用 shutdown 是優雅的關閉。

但是注意,shutdown 函數也可以指定「只關閉讀取方向,而不關閉發送方向」,但是這時候內核是不會發送 FIN 報文的,因為發送 FIN 報文是意味著我方將不再發送任何數據,而shutdown 如果指定「不關閉發送方向」,就意味著 socket 還有發送數據的能力,所以內核就不會發送 FIN。

什么情況會出現三次揮手?

當被動關閉方(上圖的服務端)在 TCP 揮手過程中,「沒有數據要發送」并且「開啟了 TCP 延遲確認機制」,那么第二和第三次揮手就會合并傳輸,這樣就出現了三次揮手。

然后因為 TCP 延遲確認機制是默認開啟的,所以導致我們抓包時,看見三次揮手的次數比四次揮手還多。

什么是 TCP 延遲確認機制?

當發送沒有攜帶數據的 ACK,它的網絡效率也是很低的,因為它也有 40 個字節的 IP 頭 和 TCP 頭,但卻沒有攜帶數據報文。

為了解決 ACK 傳輸效率低問題,所以就衍生出了TCP 延遲確認。

TCP 延遲確認的策略:

當有響應數據要發送時,ACK 會隨著響應數據一起立刻發送給對方當沒有響應數據要發送時,ACK 將會延遲一段時間,以等待是否有響應數據可以一起發送如果在延遲等待發送 ACK 期間,對方的第二個數據報文又到達了,這時就會立刻發送 ACK

延遲等待的時間是在 Linux 內核中定義的,如下圖:

關鍵就需要 HZ 這個數值大小,HZ 是跟系統的時鐘頻率有關,每個操作系統都不一樣,在我的 Linux 系統中 HZ 大小是 1000,如下圖:

知道了 HZ 的大小,那么就可以算出:

最大延遲確認時間是 200 ms (1000/5)最短延遲確認時間是 40 ms (1000/25)怎么關閉 TCP 延遲確認機制?

如果要關閉 TCP 延遲確認機制,可以在 Socket 設置里啟用 TCP_QUICKACK,啟用TCP_QUICKACK,就相當于關閉 TCP 延遲確認機制。

// 1 表示開啟 TCP_QUICKACK,即關閉 TCP 延遲確認機制int value = 1;setsockopt(socketfd, IPPROTO_TCP, TCP_QUICKACK, (char*)& value, sizeof(int));實驗驗證

實驗一

接下來,來給大家做個實驗,驗證這個結論:

 

當被動關閉方(上圖的服務端)在 TCP 揮手過程中,「沒有數據要發送」并且「開啟了 TCP 延遲確認機制」,那么第二和第三次揮手就會合并傳輸,這樣就出現了三次揮手。

 

服務端的代碼如下,做的事情很簡單,就讀取數據,然后當 read 返回 0 的時候,就馬上調用 close 關閉連接。因為 TCP 延遲確認機制是默認開啟的,所以不需要特殊設置。

#include #include #include #include #include #include #include #include #include #define MAXLINE 1024int main(int argc, char *argv[]){ // 1. 創建一個監聽 socket int listenfd = socket(AF_INET, SOCK_STREAM, 0); if(listenfd < 0) { fprintf(stderr, "socket error : %s\n", strerror(errno)); return -1; } // 2. 初始化服務器地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(8888); // 3. 綁定地址+端口 if(bind(listenfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) < 0) { fprintf(stderr,"bind error:%s\n", strerror(errno)); return -1; } printf("begin listen....\n"); // 4. 開始監聽 if(listen(listenfd, 128)) { fprintf(stderr, "listen error:%s\n\a", strerror(errno)); exit(1); } // 5. 獲取已連接的socket struct sockaddr_in client_addr; socklen_t client_addrlen = sizeof(client_addr); int clientfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addrlen); if(clientfd < 0) { fprintf(stderr, "accept error:%s\n\a", strerror(errno)); exit(1); } printf("accept success\n"); char message[MAXLINE] = {0}; while(1) { //6. 讀取客戶端發送的數據 int n = read(clientfd, message, MAXLINE); if(n < 0) { // 讀取錯誤 fprintf(stderr, "read error:%s\n\a", strerror(errno)); break; } else if(n == 0) { // 返回 0 ,代表讀到 FIN 報文 fprintf(stderr, "client closed \n"); close(clientfd); // 沒有數據要發送,立馬關閉連接 break; } message[n] = 0; printf("received %d bytes: %s\n", n, message); } close(listenfd); return 0;}

客戶端代碼如下,做的事情也很簡單,與服務端連接成功后,就發送數據給服務端,然后睡眠一秒后,就調用 close 關閉連接,所以客戶端是主動關閉方:

#include #include #include #include #include #include #include #include int main(int argc, char *argv[]){ // 1. 創建一個監聽 socket int connectfd = socket(AF_INET, SOCK_STREAM, 0); if(connectfd < 0) { fprintf(stderr, "socket error : %s\n", strerror(errno)); return -1; } // 2. 初始化服務器地址和端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(8888); // 3. 連接服務器 if(connect(connectfd, (struct sockaddr *)(&server_addr), sizeof(server_addr)) < 0) { fprintf(stderr,"connect error:%s\n", strerror(errno)); return -1; } printf("connect success\n"); char sendline[64] = "hello, i am xiaolin"; //4. 發送數據 int ret = send(connectfd, sendline, strlen(sendline), 0); if(ret != strlen(sendline)) { fprintf(stderr,"send data error:%s\n", strerror(errno)); return -1; } printf("already send %d bytes\n", ret); sleep(1); //5. 關閉連接 close(connectfd); return 0;}

編譯服務端和客戶端的代碼:

先啟用服務端:

然后用 tcpdump 工具開始抓包,命令如下:

tcpdump -i lo tcp and port 8888 -s0 -w /home/tcp_close.pcap

然后啟用客戶端,可以看到,與服務端連接成功后,發完數據就退出了。

此時,服務端的輸出:

接下來,我們來看看抓包的結果。

可以看到,TCP 揮手次數是 3 次。

所以,下面這個結論是沒問題的。

 

結論:當被動關閉方(上圖的服務端)在 TCP 揮手過程中,「沒有數據要發送」并且「開啟了 TCP 延遲確認機制(默認會開啟)」,那么第二和第三次揮手就會合并傳輸,這樣就出現了三次揮手。

 

實驗二

我們再做一次實驗,來看看關閉 TCP 延遲確認機制,會出現四次揮手嗎?

客戶端代碼保持不變,服務端代碼需要增加一點東西。

在上面服務端代碼中,增加了打開了 TCP_QUICKACK (快速應答)機制的代碼,如下:

編譯好服務端代碼后,就開始運行服務端和客戶端的代碼,同時用 tcpdump 進行抓包。

抓包的結果如下,可以看到是四次揮手。

所以,當被動關閉方(上圖的服務端)在 TCP 揮手過程中,「沒有數據要發送」,同時「關閉了 TCP 延遲確認機制」,那么就會是四次揮手。

 

設置 TCP_QUICKACK 的代碼,為什么要放在 read 返回 0 之后?

 

我也是多次實驗才發現,在 bind 之前設置 TCP_QUICKACK 是不生效的,只有在 read 返回 0 的時候,設置 TCP_QUICKACK 才會出現四次揮手。

網上查了下資料說,設置 TCP_QUICKACK 并不是永久的,所以每次讀取數據的時候,如果想要立刻回 ACK,那就得在每次讀取數據之后,重新設置 TCP_QUICKACK。

而我這里的實驗,目的是為了當收到客戶端的 FIN 報文(第一次揮手)后,立馬回 ACK 報文,所以就在 read 返回 0 的時候,設置 TCP_QUICKACK。

當然,實際應用中,沒人會在我這個位置設置 TCP_QUICKACK,因為操作系統都通過 TCP 延遲確認機制幫我們把四次揮手優化成了三次揮手了,這本來就是一件好事呀。

總結

當被動關閉方在 TCP 揮手過程中,如果「沒有數據要發送」,同時「沒有開啟 TCP_QUICKACK(默認情況就是沒有開啟,沒有開啟 TCP_QUICKACK,等于就是在使用 TCP 延遲確認機制)」,那么第二和第三次揮手就會合并傳輸,這樣就出現了三次揮手。

關鍵詞: 關閉連接 應用程序 發送數據 就出現了

X 關閉

X 關閉

久久国产精品一区二区三区四区,久色婷婷小香蕉久久,国产日韩欧美在线播放不卡,另类av一区二区
亚洲精品自在在线观看| 浪潮色综合久久天堂| 99视频在线精品国自产拍免费观看| 久久久久久久久久码影片| 欧美国产亚洲精品久久久8v| 久久久久久久999精品视频| 一本大道久久a久久精品综合| 欧美精品v国产精品v日韩精品| 妖精成人www高清在线观看| 国产精品卡一卡二卡三| 欧美特黄一级| 久久久久这里只有精品| 欧美激情久久久| 国产日韩欧美在线观看| 99视频在线精品国自产拍免费观看| 欧美刺激午夜性久久久久久久| 欧美日韩一区二区在线观看| 久久免费视频网| 亚洲欧美日韩国产一区二区三区| 欧美成人一区二区三区在线观看| 亚洲欧美日韩国产一区二区三区| 欧美中文在线字幕| 国产精品yjizz| 久久精品日韩一区二区三区| 亚洲一区二区免费| 国产精品久久久久久久久动漫| 亚洲一区二区三区在线| 国产精品综合不卡av| 久久精品日产第一区二区| 91久久综合| 国产精品日韩欧美一区二区| 激情久久婷婷| 夜夜躁日日躁狠狠久久88av| 亚洲欧洲一区二区三区久久| 国产情人综合久久777777| 91久久精品国产91久久| 欧美激情精品| 国产精品高清在线观看| 久久综合五月天婷婷伊人| 亚洲图片欧美一区| 欧美三级视频在线观看| 国产一区再线| 欧美一区二区在线观看| 欧美一区二区三区视频在线| 亚洲一区二区三区在线播放| 韩国在线一区| 亚洲大片精品永久免费| 激情成人av在线| 国产婷婷成人久久av免费高清| 国产日韩三区| 亚洲精品免费电影| 亚洲深夜福利视频| 99riav1国产精品视频| 一本久道综合久久精品| 亚洲午夜精品国产| 欧美精品成人一区二区在线观看| 日韩小视频在线观看专区| 精品粉嫩aⅴ一区二区三区四区| 国产亚洲午夜高清国产拍精品| 欧美午夜欧美| 国产精品va在线播放我和闺蜜| 欧美日韩国产另类不卡| 国产在线观看91精品一区| 一本久道久久综合狠狠爱| 亚洲精品久久久久久久久久久久久| 亚洲人体大胆视频| 国产精品综合av一区二区国产馆| 国产精品九色蝌蚪自拍| 一区二区久久久久| 欧美 日韩 国产在线| 亚洲欧美日韩一区二区三区在线观看| 欧美日韩国产综合一区二区| 国产精品久久网| 亚洲在线中文字幕| 在线精品国产成人综合| 国产日韩精品一区观看| 亚洲精品欧美极品| 免费亚洲视频| 久久精品欧洲| 国产精品入口66mio| 亚洲永久网站| 国产一区二区成人久久免费影院| 亚洲综合国产激情另类一区| 欧美亚洲综合在线| 欧美天天在线| 久久久久久久性| 欧美成人69| 国产女主播在线一区二区| 在线综合+亚洲+欧美中文字幕| 亚洲在线观看免费视频| 欧美三级第一页| 久久综合九色综合欧美狠狠| 欧美调教视频| 激情av一区| 欧美日韩国产成人在线91| 国产欧美精品久久| 亚洲三级电影全部在线观看高清| 在线视频欧美日韩| 欲香欲色天天天综合和网| 久久久一本精品99久久精品66| 欧美日韩八区| 狠狠色丁香婷综合久久| 欧美日韩亚洲一区二区三区四区| 正在播放亚洲一区| 免费观看不卡av| 亚洲自啪免费| 国产精品一区二区三区四区| 欧美无乱码久久久免费午夜一区| 久久精品视频亚洲| 国产欧美欧美| 久久av资源网| 亚洲伊人伊色伊影伊综合网| 久久久久五月天| 欧美日韩一区二区免费在线观看| 在线视频一区二区| 欧美午夜一区二区福利视频| 亚洲网站在线播放| 亚洲视频精品在线| 欧美天堂亚洲电影院在线播放| 欧美激情久久久久久| 欧美黄污视频| 欧美日韩直播| 国产日韩欧美在线播放不卡| 久久久久久久波多野高潮日日| 欧美在线一二三四区| 亚洲精品国产精品国产自| 亚洲图片欧美午夜| 亚洲女爱视频在线| 久久久噜噜噜久久人人看| 欧美高清在线视频观看不卡| 欧美久久九九| 久久精品电影| 欧美女同视频| 亚洲永久免费| 欧美激情精品久久久| 国产一区二区三区最好精华液| 亚洲系列中文字幕| 欧美极品在线播放| 另类春色校园亚洲| 亚洲日本电影| 一区二区三区国产在线| 久久伊人一区二区| 国产一区二区精品| 亚洲第一福利视频| 久久国产精品99久久久久久老狼| 欧美日韩一区二区在线| 久久本道综合色狠狠五月| 亚洲日韩欧美视频一区| 在线成人激情| 欧美在线free| 亚洲一区二区三区精品在线| 亚洲伊人伊色伊影伊综合网| 国产精品专区一| 亚洲一区制服诱惑| 欧美日韩综合久久| 在线观看视频欧美| 狠狠色狠狠色综合人人| 欧美国产综合视频| 欧美精品在线视频观看| 欧美一区二区三区四区视频| 在线观看欧美一区| 伊人婷婷欧美激情| 欧美粗暴jizz性欧美20| 欧美一级久久久| 毛片基地黄久久久久久天堂| 欧美精品高清视频| 国产精品v日韩精品v欧美精品网站| 久久国产精品99国产| 亚洲午夜电影在线观看| 欧美视频中文在线看| 国产精品欧美日韩| 99re8这里有精品热视频免费| 亚洲高清不卡| 欧美亚洲午夜视频在线观看| 久久久久国产一区二区三区四区| 亚洲国产女人aaa毛片在线| 久久国产精品99久久久久久老狼| 亚洲免费视频成人| 国产精品女人网站| 美女视频一区免费观看| 欧美成人免费在线视频| 亚洲精品在线免费| 亚洲一级片在线观看| 亚洲欧美日韩视频一区| 蜜桃精品久久久久久久免费影院| 亚洲私人影吧| 亚洲欧美国产精品专区久久| 国产欧美一区二区三区视频| 亚洲一区二区视频在线| 国产精品日韩一区| 国产精品丝袜xxxxxxx| 91久久久久| 国产主播精品在线| 亚洲电影免费观看高清完整版在线| 1000部国产精品成人观看| 在线日韩日本国产亚洲| 性欧美大战久久久久久久免费观看| 一区二区三区精品视频在线观看| 国产伦精品一区二区三区免费| 国产视频欧美|