面试官,求你了别再问我TCP三次握手和四次挥手了(含面试题)
少點代碼,多點頭發
三次握手建立鏈接,四次揮手斷開鏈接。這個問題算非常經典的問題,也是面試官非常喜歡問的問題。
不夸張的說,龍叔在校招面試的時候每一家公司都問到過關于三次握手和四次揮手相關的問題,相信大家也都差不多被面試官各種懟。
這個問題的重要性,已經意識到。不說廢話了,接下來就是聽龍叔給你安排的明明白白。
先畫個圖,看下TCP的建立連接 和 斷開連接的整體過程。
tcp三次握手四次揮手看完這個圖相信聰明的你在整體對三次握手和四次揮手有了一些基本把控。但是,里面的細節肯定是會有些生疏或者模糊的,接下來就一個一個問題的揭露本質。
在解釋之前先看點基礎知識做做鋪墊。
TCP狀態轉移解釋
| CLOSED | 阻塞或關閉狀態,表示主機當前沒有正在傳輸或者建立的鏈接 |
| LISTEN | 監聽狀態,表示服務器做好準備,等待建立傳輸鏈接 |
| SYN RECV | 收到第一次的傳輸請求,還未進行確認 |
| SYN SENT | 發送完第一個SYN報文,等待收到確認 |
| ESTABLISHED | 鏈接正常建立之后進入數據傳輸階段 |
| FIN WAIT1 | 主動發送第一個FIN報文之后進入該狀態 |
| FIN WAIT2 | 已經收到第一個FIN的確認信號,等待對方發送關閉請求 |
| TIMED WAIT | 完成雙向鏈接關閉,等待分組消失 |
| CLOSING | 雙方同時關閉請求,等待對方確認時 |
| CLOSE WAIT | 收到對方的關閉請求并進行確認進入該狀態 |
| LAST ACK | 等待最后一次確認關閉的報文 |
再看下TCP的報文格式
TCP報文格式首部有20字節的固定長度,含義如下:
各占2字節,就是存儲源端口號和目的端口的
占4字節,表示的范圍就是整形的范圍[0~2^32]。序號使用在給數據部分每個字節進行編號的,編號方式是mod 2^32 。
占4字節,范圍也是無符號整數的范圍。使用在對端傳輸給我的數據最后一個字節序號,例如A傳輸給B 101—500,此時B返回的確認號一定是小于等于501的。當B段正確接收數據之后才會返回確認號,換句話說確認號之前的數據已經全部接收。
占4bit,數據偏移很多人很容易想到是不是表示數據的長度,那就錯了。偏移嘛,指的是TCP起始位置到數據部分的起始位置的偏移,也就是TCP首部的長度。
占6bit,保留字段顧名思義,就是為今后使用,默認置為0。
占用1bit,URG=1,表示緊急指針有效,此時tcp數據優先傳輸。相當于生活中的緊急通道,特殊情況時使用。
在網絡中也會有特殊情況,例如,發送一個很長的程序在遠程服務器上運行,此時發現程序有bug,需要中斷運行,因此我們從鍵盤輸入Ctrl c,假如不使用緊急數據,需要在緩沖區里排隊,都知道是bug了,還要排隊,這怕是要出鍋啊。
此時使用緊急數據傳輸,不需要排隊,直接中斷程序是不是更符合我們的預期。
需要注意一點是,即使窗口為0時,也可以發送緊急數據。
如何使用緊急URG控制位,在socket編程中send函數flag參數
send(int socket, const void *buffer, size_t length, int flags);
flags參數傳MSG_OOB宏時,表示此時有緊急數據。MSG_OOB是個宏,
占1bit,當ACK=1時生效。TCP有條硬性規定,當建立鏈接成功后所有傳輸的數據報文都必須把ACK置為1。
占1bit,發送方把PSH置為1時 會立即發送該數據包,接收方收到PSH=1的報文會立即處理交付給應用層處理。是不是感覺和URG很像,其實還是有些區別的。
- 兩者相同點:
URG與PSH兩者都使用于緊急處理的情況,用來快速傳輸緊急數據。
- 兩者不同點
URG置為1時,對于發送發,“帶外數據”與正常情況下應該發送的消息數據一起,封裝成數據報發送,省去了在隊列中等待的時間。 在接收方,解析報文后,獲取數據之后還是要放在緩存區中,等待滿了之后在向上往應用層交付。
PSH置為1時,對于發送方,表明這些數據不需要等向下發送的緩存區滿,立刻封裝成報文,發送,省去了等待發送緩存區到達滿的狀態的時間。 在接收方,也不需要等接受緩存區滿,直接向上交付給應用層。
占1bit,當RST=1時,TCP會主動釋放鏈接,兩種情況會用上。
TCP出現嚴重差錯時,會主動釋放連接,重建鏈接,傳輸數據。
遇到非法報文或者拒絕連接時會把RST置為1.
占1bit,同步控制位,用來在傳輸連接建立時同步傳輸連接序號。
SYN=1時,表示這是一個連接請求或連接確認報文。
SYN=1,ACK=0,表明這是一個連接請求數據段,如果對方同意建立連接,則對方會返回一個SYN=1、ACK=1的確認。
占1bit,用于釋放一個傳輸連接。
FIN=1時,表示數據已全部傳輸完成,發送端沒有數據要傳輸了,要求釋放當前連接,但是接收端仍然可以繼續接收還沒有接收完的數據。
FIN=0,正常傳輸數據。
占16bit,2byte,用于表示發送方可以接受的最大數據大小。
該窗口是動態變化的,用作流量控制時使用。
占16bit,2byte,用于對TCP頭部,偽頭部,數據三個部分進行校驗。
占16bit,2byte,用于記錄緊急數據的末尾在數據段中的位置。
當URG=1時,該指針才生效。
可選項最長可達40byte,是可選的,可以沒有。當可選項不存在時,TCP頭部長度為20byte。
可選項可以包括窗口縮放選項(Window ScaleOption,WSopt)、MSS(最大數據段大小)選項、SACK(選擇性確認)選項、時間戳(Timestamp)選項等。
TCP數據部分,由應用層應用程序提交的數據。
TCP頭部是基礎知識,必須了解才能更好的理解TCP數據如何封裝和傳輸,以及在建立鏈接和斷開鏈接時都在操作那些地方。
三次握手建立連接
三次握手如何建立連接?
三次握手建立鏈接從圖中可以清楚的看到,三次握手的過程,我在在把過程清楚的解釋一遍,順便說下每個過程容易被問到的知識點。
采用C/S模式解釋,假設C端發起傳輸請求。
在發送建立鏈接請求之前,C端是保持CLOSED狀態,S端最開始也是處于CLOSED狀態,當執行listen函數套接字進入被動監聽狀態。
所謂被動監聽,是指當沒有客戶端請求時,套接字處于“睡眠”狀態,只有當接收到客戶端請求時,套接字才會被“喚醒”來響應請求。
第一次:C端發送SYN=1的請求報文,此時C端進入SYN SENT狀態,等待服務器確認。
此時如果報文丟失發送不到對端會如何?
C端發送報文之后會啟動一個定時器,在超時之后未收到S端的確認,會再次發送SYN請求,每次嘗試的時間會是第一次的二倍,如果總的總嘗試時間為75秒,此次建立鏈接失敗。
第二次:S端收到C端發送的SYN報文(建立鏈接請求)后,S端必須返回確認號并且同時發送一條SYN報文,此時進入SYN RCVD狀態。
為啥要連帶發送SYN報文?
TCP是全雙工通信,協議規定當收到建立鏈接請求后必須返回序列號,同時建立本端到對端的通信鏈接。這也叫做捎帶應答機制。
如果第二次報文丟失怎么辦?
在發送完ACK+SYN報文后會啟動一個定時器,超時沒有收到ACK確認,會再次發送,會進行多次重試。超時時間依舊每次翻倍,重試次數可設置。
修改 /proc/sys/net/ipv4/tcp_synack_retries 的值
第三次:C端收到S端發的ACK+SYN報文,需要返回一個應答ACK的報文,此時該連接會進入半連接狀態的隊列,當S端收到ACK后,一條完整的全雙工TCP鏈接建立完成,雙方進入ESTABLISHED狀態。
這里有個常用攻擊手段,攻擊者偽造一個SYN請求發送給服務端,服務端響應之后,會收不到C端的ACK確認,服務端會不斷的重試,默認會重試五次。
此時服務端會維持這個鏈接的所有資源,如果有大量這樣的請求,服務端的資源會被耗完。
這就是DOS攻擊。
如果第三次報文丟失怎么辦?
S端在發出ACK+SYN報文后會啟動一個定時器,在超時觸發還沒收到ACK就確認是丟失了,會重試一次發送。
這里面的每個狀態都必須搞明白,面試官也超級愛問上面的狀態轉移。
龍叔還遇到過一個面試官問我用過socket編程么?問我用過哪些socket函數?
C端socket編程代碼
//C端int?main(int?argc,?char?**argv)
{
????//定義IPV4的TCP連接的套接字描述符
????int?sock_cli?=?socket(AF_INET,SOCK_STREAM,?0);
????//定義sockaddr_in
????struct?sockaddr_in?servaddr;
????memset(&servaddr,?0,?sizeof(servaddr));
????servaddr.sin_family?=?AF_INET;
????servaddr.sin_addr.s_addr?=?inet_addr(argv[1]);
????servaddr.sin_port?=?htons(PORT);??
?
????//連接服務器,成功返回0,錯誤返回-1
????int?ret?=?connect(sock_cli,?(struct?sockaddr?*)&servaddr,?sizeof(servaddr));
?
????//客戶端將控制臺輸入的信息發送給服務器端,服務器原樣返回信息,阻塞
????while?(fgets(sendbuf,?sizeof(sendbuf),?stdin)?!=?NULL)
????{???
????????ret=send(sock_cli,?sendbuf,?strlen(sendbuf),0);?///發送
????????recv(sock_cli,?recvbuf,?sizeof(recvbuf),0);?///接收
????????fputs(recvbuf,?stdout);
????}
?
????close(sock_cli);?//?關閉連接
????return?0;
}
- 1
S端socket編程代碼
int?main(int?argc,?char?**argv){
????//定義IPV4的TCP連接的套接字描述符
????int?server_sockfd?=?socket(AF_INET,SOCK_STREAM,?0);
????//定義sockaddr_in
????struct?sockaddr_in?server_sockaddr;
????server_sockaddr.sin_family?=?AF_INET;
????server_sockaddr.sin_addr.s_addr?=?htonl(INADDR_ANY);
????server_sockaddr.sin_port?=?htons(PORT);
?
????//bind成功返回0,出錯返回-1
????if(bind(server_sockfd,(struct?sockaddr?*)&server_sockaddr,sizeof(server_sockaddr))==-1)
?
????//listen成功返回0,出錯返回-1,允許同時監聽的連接數為QUEUE_SIZE
????if(listen(server_sockfd,QUEUE_SIZE)?==?-1)
?
????for(;;)
????{
????????struct?sockaddr_in?client_addr;
????????socklen_t?length?=?sizeof(client_addr);
????????//進程阻塞在accept上,成功返回非負描述字,出錯返回-1
????????int?conn?=?accept(server_sockfd,?(struct?sockaddr*)&client_addr,&length);
?
????????//處理數據部分
??????...
????}
?
????close(server_sockfd);
????return?0;
}
- 1
為什么需要三次握手建立鏈接,2次可以么,4次行不行?
這問題問的,面試官是咋了?在這明知故問的,整些有的沒的。肯定是不行啊,RFC 標準就是這樣寫的啊。
可不敢這樣回答啊,標準是說的三次握手建立鏈接,可沒說四次不行啊。要是這樣答,妥妥的會收到,同學我們今天的面試到此基本結束了,你回家等消息...
龍叔來說說這個問題,為什么不能兩次?
如果第二次不發送SYN+ACK,只是發送確認應答消息ACK,會造成只能建立單向通信,而且不能應答。而TCP是全雙工通信的,而且必須保證可靠性。
如果第二發送SYN+ACK,不用應答。此時會出現三種情況
一、二次握手失敗,C端會重復發送SYN報文,等待對端發送確認報文,S端會保存tcp連接的所有資源,大量的這種情況會導致S資源耗盡。
二、二次握手成功,S收不到ACK會重復發送SYN+ACK報文。
三、二次握手完以后,雙方以為連接建立成功,即可開始通信。假如此時連接并沒有真的建立成功,S端開始發送消息,會造成網絡擁堵發生。
為什么不能是四次?
四次其實原則上來說是可以的,就是把第二次的ACK和SYN分兩次發送。在理論上是完全可以行得通的,但是TCP本著節約網絡網絡資源的前提。
還有一種是不拆開二次握手的捎帶應答,三次握手之后C端繼續發送SYN報文,其時這是徒勞的。第三次完成以后鏈接已經建立,后面無論多少次都是徒勞。
如果雙方同時建立連接,會發生什么情況?
TCP同時建立鏈接這就是雙方同時建立鏈接的情況,情況還不錯,反正能建立成功,這點是肯定的。但是要注意兩點
第一、此時只會建立一條全雙工的TCP鏈接,不是兩條。
第二、雙方沒有CS之分,兩端都是同時承擔兩個角色,客戶端和服務器。
四次揮手斷開鏈接
先整個圖看下四次揮手的整個過程和狀態轉移。狀態轉移會考看仔細點。
四次揮手斷開鏈接依舊采用C/S模式解釋此過程。
第一次:當C端的應用程序結束數據傳輸是,會向S端發送一個帶有FIN附加標記的報文段(FIN表示英文finish),此時C端進入FIN_WAIT1狀態,C端不能在發送數據到S端。
第二次:S端收到FIN報文會響應一個ACK報文,S端進入CLOSE_WAIT狀態。進入此狀態后S端把剩余未發送的數據發送到C端,C端收到S端的ACK之后,進入FIN_WAIT2狀態。
同時繼續接受S端傳輸的其他數據包。
第三次:S端處理完自己待發送的數據之后,也會發送FIN斷開鏈接的請求,S端進入LAST_ACK狀態。
第四次:C端收到S端的斷開鏈接請求后會啟動一個定時器,該定時器時長是2MSL(最大段報文生存時間),同時發送最后一次ACK報文。
為什么要四次揮手?
TCP是全雙工的通信機制,每個方向必須單獨進行關閉。
TCP傳輸連接關閉的原則如下:
當一端完成它的數據發送任務后就可以發送一個FIN字段置1的數據段來終止這個方向的數據發送;當另一端收到這個FIN數據段后,必須通知它的應用層 對端已經終止了那個方向的數據傳送。
為什么不能用三次握手中捎帶應答機制減少一次握手?
這點到是很迷惑人,但是掌握了TCP傳輸的一些細節就會發現并不難。
TCP是全雙工通信的,S收到斷開鏈接請求后只是表示C端不會傳輸數據到S端了,但是并不表示S端不傳輸數據到C端。
如果采用捎帶應答,S端將無法把剩余的數據傳輸到C端。
為何最后一次ACK之后需要等待2MSL的時間?
網絡是不可靠的,TCP是可靠協議,必須保證最后一次報文送達之后才能斷開鏈接,否則會再次收到S端的FIN報文信息。
而等待2MSL時間就是為了保證最后最后一次報文丟失時還能重新發送。
為何是2MSL的時間?
2MSL是報文一個往返的最長時間,假設小于這個時間會發生,ACK丟了,但是還沒接收到對方重傳的FIN我方就重新發送了ACK。
如果已經建立了連接,但是客戶端突然出現故障了怎么辦?
這個不難TCP自己做了保證,TCP默認有個定時器,每次收到客戶端的請求后會把定時器設置好,通常設置兩小時,超過兩小時還沒收到數據。
服務端會發送一個探測報文,以后每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認為客戶端出了故障,接著就關閉連接。
總結
三次握手和四次揮手的知識基本告一段落了,就講到這里了,如果有什么不明白的地方可以加我微信探討。
后面還會出一篇網絡編程常用的linux命令行工具,比如ping、tcpdump、netstat、nc等等,在出一篇計算機網絡的總結文章。計算機網絡這部分基本完結了,如果又不懂得可以看看公號里面前面的文章。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的面试官,求你了别再问我TCP三次握手和四次挥手了(含面试题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python 奇技淫巧
- 下一篇: 记录一次失败的面试 大家千万不要学我 请