能將三次握手理解到這個深度,面試官拍案叫絕
大家好,我是飛哥!
在后端的求職面試中,三次握手的出現頻率非常高,甚至可以不夸張地說是必答題一般的回答是客戶端如何發起SYN握手進入SYN_SENT狀態,服務器響應SYN并回復SYNACK,然后進入SYN_RECV,...諸如此類
但是今天我想給出一個不一樣的答案實際上,在內核的實現中,三次握手不僅僅是簡單的狀態轉移,還包括半連接隊列,syncookie,全連接隊列,重傳定時器等關鍵操作如果你能深刻理解這些,你會進一步在線把握和理解如果面試官問你三次握手的問題,我相信這個回答一定會幫你在面試官面前贏得很多加分
在基于TCP的服務開發中,三次握手的主要流程圖如下。
服務器端的核心代碼是創建socket,綁定端口,監聽并最終接受客戶端的請求。
//服務器核心代碼intmain) intfd = socket (af _ inet,sock _ stream,0),綁定(fd,...),聽(fd,128),接受(fd,...),...
客戶端的相關代碼是創建一個socket,然后調用connect連接服務器。
//客戶端核心代碼int mainfd = socket (AF _ inet,sock _ stream,0),連接(fd,),
圍繞這張三次握手圖,以及客戶端和服務器端的核心代碼,我們來深入探討一下三次握手過程中的內部操作。先說listen,和三次握手過程有很大關系!
友情提示:本文會有更多內核源代碼如果你能更好地理解它,如果你覺得很難理解它,就專注于本文中的描述性詞語,尤其是粗體字此外,在文章的最后還有一個總結圖表,對全文進行總結和梳理
首先,服務器的監聽
眾所周知,服務器在開始提供服務之前需要監聽但是聽里面做了什么,我們很少去想
今天我們來詳細看看直接監聽時執行的內核代碼。
//file:net/core/request _ sock . cintreqsk _ queue _ allocsize _ tlopt _ size = sizeof(struct listen _ sock),structlisten _ sock * lopt//計算半連通隊列的長度NR _ table _ entries = min _ t (U32,NR _ table _ entries,sysctl _ max _ syn _ backlog),Nr_table_entries=......//半連接隊列請求內存lopt _ size+= NR _ table _ entries * sizeof(struct Request _ sock *),if(lopt _ size gt,PAGE _ SIZE)lopt = vzalloc(lopt _ SIZE),elselopt=kzalloc(lopt_size,GFP _ KERNEL),//用全連接隊列頭初始化queue—gt,rskq _ accept _ head = NULL//為半連接隊列設置lopt—gt,NR _表_條目= nr _表_條目,queue—gt,listen _ opt = lopt......
在這段代碼中,內核計算半連接隊列的長度然后,計算半連接隊列所需的實際內存大小,并啟動用于管理半連接隊列對象的內存的應用程序最后一個半連接隊列掛在接收隊列上
另外,queue—gt,Rsq _ accept _ head代表全連接隊列,采用鏈表的形式在listen中,因為還沒有連接,所以隊列頭queue—gt,Rsq _ accept _ head設置為NULL
當全連接隊列和半連接隊列中都有元素時,它們在內核中的結構圖大致如下。
服務器偵聽時,主要計算全/半連接隊列的長度限制,以及相關的內存應用和初始化在可以響應來自客戶端的握手請求之前,已滿/連接隊列被初始化
如果想進一步了解listen的內部操作細節,可以閱讀之前的文章《為什么服務器端程序需要先監聽?!?/p>
第二,客戶端連接
客戶端通過調用connect來啟動連接在connect系統調用中,您將輸入內核源代碼的tcp_v4_connect
//file:net/IP v4/TCP _ IP v4 . CIN TCP _ v4 _ connect//將套接字狀態設置為TCP _ syn _ sent TCP _ set _ state (SK,TCP _ syn _ sent),//動態選擇一個端口err = inet _ hash _ connect(amp,tcp_death_row,sk),//該函數用于根據sk中的信息構造一條完整的syn報文并發送出去。err = TCP _ connect(sk),
將套接字狀態設置為TCP_SYN_SENT將在此完成然后通過inet_hash_connect動態選擇一個可用端口,進入tcp_connect
//file:net/IP v4/TCP _ output . cinttcp _ connect TCP _ connect _ init(sk),//申請skb,構造成SYN包......//添加到發送隊列sk_write_queue上的tcp_connect_queue_skb(sk,buff),//實際發出syne RR = TP—gt,fastopen_req。tcp_send_syn_data(sk,buff):tcp_transmit_skb(sk,buff,1,sk—gt,sk _ allocation),//啟動重傳定時器inet _ csk _ reset _ xmit _ timer (sk,icsk _ time _ retries,inet _ csk(sk)—gt,icsk_rto,TCP _ RTO _ MAX),
在tcp_connect上申請并構造SYN包,然后發送出去同時啟動重傳定時器,用于在一定時間后沒有收到服務器的反饋時開始重傳在3.10版本中,第一次超時是1 s,在一些舊版本中,是3 s
綜上所述,客戶端連接時,將本地socket狀態設置為TCP_SYN_SENT,選擇一個可用端口,然后發出SYN握手請求,啟動重傳定時器。
第三,服務器響應SYN
在服務器端,所有的TCP包通過網卡和軟中斷進入tcp_v4_rcv在這個函數中,根據SKB的TCP報頭信息中的目的IP信息找到當前監聽的套接字然后繼續進入tcp_v4_do_rcv握手流程
//file:net/IP v4/TCP _ IP v4 . CIN TCP _ v4 _ do _ rcv//當服務器收到第一個握手SYN或者第三個ACK時,如果(sk—gt,sk _ state TCP _ LISTEN)struct sock * NSK = TCP _ v4 _ hnd _ req(sk,skb),if(tcp_rcv_state_process(sk,skb,tcp_hdr(skb),sk b—gt,len))rsk = sk,gotoreset
在tcp_v4_do_rcv中判斷出當前套接字處于listen狀態后,首先會檢查tcp_v4_hnd_req中的半連接隊列服務器第一次響應SYN時,半連接隊列必須為空,所以相當于什么都不做就返回
在tcp_rcv_state_process中,根據不同的套接字狀態執行不同的進程。
//file:net/IP v4/TCP _ input . cinttcp _ rcv _ state _ process switch(sk—gt,Sk_state)//第一次握手案例TCP _ LISTEN:if(th—gt,Syn)//判斷是Syn握手包...if(icsk—gt,icsk _ af _ ops—gt,conn_request(sk,skb)lt,0)返回1,......
其中conn_request是指向tcp_v4_conn_request的函數指針響應服務器SYN的主要處理邏輯在這個tcp_v4_conn_request中
//file:net/IP v4/TCP _ IP v4 . CIN TCP _ v4 _ conn _ request//查看半連通隊列是否已滿if(inet _ csk _ reqsk _ queue _ is _ full(sk)amp,amp!isn)want _ cookie = TCP _ syn _ flood _ action(sk,skb, " TCP "),如果(!want _ cookie)gotodrop,//在全連接隊列已滿的情況下,如果有young_ack,那么直接拋出if(sk _ acceptq _ is _ full(sk)amp,ampinet _ csk _ reqsk _ queue _ young(sk)gt,1)NET_INC_STATS_BH(sock_net(sk),LINUX _ MIB _ LISTENOVERFLOWSgotodrop//allocate request_sock內核對象req = inet _ reqsk _ alloc(amp,TCP _ request _ sock _ ops),//構造syn+ack包skb _ synack = TCP _ make _ synack (sk,dst,req,fast open _ cookie _ present(amp,valid_foc)。ampvalid _ foc:NULL),如果(有可能(!do _ fast open))//Send syn+ack response ERR = IP _ Build _ and _ Send _ PKT(SKB _ SYNACK,SK,IREQ—gt,loc_addr,ireq—gt,rmt_addr,ireq—gt,opt),//加入半連接隊列,啟動定時器inet _ csk _ reqsk _ queue _ hash _ Add(SK,REQ,TCP _ time out _ init),其他
這里先判斷半連接隊列是否已滿,如果是,輸入tcp_syn_flood_action判斷tcp_syncookies內核參數是否開啟。如果隊列滿了,沒有開啟tcp_syncookies,握手包會直接丟棄??!
那么就要判斷全連接隊列是否滿了因為滿連接隊列也會導致握手異常,所以在第一次握手時簡單判斷如果全連接隊列已滿且有young_ack,也直接丟棄
Young_ack是保存在半連接隊列中的計數器它記錄了剛到達SYN,還沒有被SYN_ACK重傳定時器重傳,還沒有完成三次握手的sock的數量
下一步是構造synack包,然后通過ip_build_and_send_pkt發送出去。
最后,將當前握手信息添加到半連接隊列中,并啟動計時器定時器的作用是,如果在一定時間內沒有收到客戶端的三次握手,服務器會重新發送synack包
綜上所述,服務器響應ack的主要工作是確定接收隊列是否已滿如果它已滿,該請求可能會被丟棄,否則,將發送一個synack將request_sock添加到半連接隊列中,并啟動定時器
第四,客戶端響應SYNACK
當接收到來自服務器的synack包時,客戶端也會進入tcp_rcv_state_process函數但是,由于它自己的套接字的狀態是TCP_SYN_SENT,所以它將進入另一個不同的分支
//file:net/ipv4/tcp_input.c//除了ESTABLISHED和TIME_WAIT,狀態中的tcp處理都到這里int TCP _ RCV _ state _ process switch(SK—gt,Sk_state)//服務器收到第一個ACK包caseTCP_LISTEN://客戶端處理第二個握手caseTCP_SYN_SENT://處理synack包queued = TCP _ RCV _ SYN SENT _ state _ process(SK,SKB,TH,LEN),return0
Tcp_rcv_synsent_state_process是客戶端響應synack的主要邏輯。
//file:net/IP v4/TCP _ input . cstaticinttcp _ rcv _ syn sent _ state _ process TCP _ ack(sk,skb,FLAG _ slow path),//連接建立后tcp_finish_connect(sk,skb),if(sk—gt,sk _ write _ pendingicsk—gt,icsk _ accept _ queue . rskq _ defer _ accept icsk—gt,Icsk_ack.pingpong)//延遲確認elsetcp _ send _ ack(sk),
TCP _ ack—gt,tcp _清理_ rtx _隊列
//file:net/IP v4/TCP _ input . cstaticitcp _ clean _ RTX _ queue//刪除發送隊列...//刪除定時器TCP _ rearm _ RTO(sk),
//file:net/IP v4/TCP _ input . cvoidtcp _ finish _ connect//修改套接字狀態TCP _ set _ state (SK,TCP _ established),//初始化擁塞控制TCP _ init _ congestion _ control(sk),...//keep—alive timer打開if (sock _ flag (SK,sock _ keepOpen)) inet _ CSK _重置_ keepalive _ timer (SK,keepalive _ time _ when(TP)),
客戶端將其套接字狀態修改為已建立,然后啟動TCP保持活動計時器。
//file:net/IP v4/TCP _ output . cvoidtcp _ send _ ack//申請并構造ack包buff = alloc _ skb (Max _ TCP _ header,SK _ GFP _ atomic (SK,GFP _ atomic)),//發出TCP _ transmit _ skb (sk,buff,0,sk _ GFP _ atomic (sk,GFP _ atomic)),
ack包在tcp_send_ack中構造并發送出去。
當客戶端響應來自服務器的synack時,它清除連接期間設置的重新傳輸計時器,將當前套接字狀態設置為已建立,啟動?;钣嫊r器,并發送三次握手的ack確認。
5.服務器響應ACK
當服務器響應三次握手的ack時,它也會進入tcp_v4_do_rcv
//file:net/IP v4/TCP _ IP v4 . cinttcp _ v4 _ do _ RC Vif(sk—sk _ state TCP _ LISTEN)struct sock * NSK = TCP _ v4 _ hnd _ req(sk,skb),if(tcp_rcv_state_process(sk,skb,tcp_hdr(skb),sk b—len))rsk = sk,gotoreset
但是,由于這是第一次三次握手,因此在半連接隊列中會有上次第一次握手留下的半連接信息所以tcp_v4_hnd_req的執行邏輯會有所不同
//file:net/IP v4/TCP _ IP v4 . cstaticstructsock * TCP _ v4 _ hnd _ reqstructrequest _ sock * req = inet _ csk _ search _ req(sk,ampprev,th—source,iph—saddr,iph—daddr),if(req)returntcp_check_req(sk,skb,req,prev,false),
Inet_csk_search_req負責在半連通隊列中搜索,找到后返回一個半連通的request_sock對象然后輸入tcp_check_req
//file:net/IP v4/TCP _ mini socks . cstructsock * TCP _ check _ req//創建一個子socket child = inet _ csk(sk)—gt,icsk _ af _ ops—gt,syn_recv_sock(sk,skb,req,NULL),//清理半連通隊列inet _ csk _ reqsk _ queue _ unlink (sk,req,prev),inet _ csk _ reqsk _ queue _ removed(sk,req),//添加全連接隊列inet _ csk _ reqsk _ queue _ add (sk,req,child),returnchild5.1創建子套接字
icsk _ af _ ops—gt,Syn_recv_sock對應tcp_v4_syn_recv_sock函數。
//file:net/IP v4/TCP _ IP v4 . cconstructinet _ connection _ sock _ af _ opsipv4 _ specific =conn _請求= tcp _ v4 _ conn _請求,syn _ recv _ sock = TCP _ v4 _ syn _ recv _ sock,//就算三次握手接近,也完了
* *注意,這里在三次握手中,繼續判斷全連接隊列是否再次滿如果已滿,修改計數器并將其丟棄* *如果隊列不滿足,則申請創建新的sock對象
5.2刪除半連接隊列
從半連接隊列中刪除連接請求塊。
//file:include/net/inet _ connection _ sock . hstaticinlinevoidinet _ csk _ reqsk _ queue _ unlinkreqsk _ queue _ unlink(amp,inet_csk(sk)—icsk_accept_queue,req,prev),
Reqsk_queue_unlink從半連接隊列中刪除連接請求塊。
5.3添加完整的連接隊列
然后將其添加到完整的連接隊列中。
//file:net/IP v4/syncookies . cstaticinlinevoidinet _ csk _ reqsk _ queue _ addreqsk _ queue _ add(amp,inet_csk(sk)—icsk_accept_queue,req,sk,child),
在reqsk_queue_add中,握手成功的request_sock對象被插入到全連接隊列的鏈表末尾。
//file:include/net/request _ sock . hstaticinlinevoidreqsk _ queue _ addreq—sk = child,sk _ acceptq _ added(parent),if(queue—rskq _ accept _ head null)queue—rskq _ accept _ head = req,elsequeue—rskq _ accept _ tail—dl _ next = req,queue—rskq _ accept _ tail = req,req—dl _ next = NULL,5.4將連接設置為已建立。
//file:net/IP v4/TCP _ input . CIN TCP _ rcv _ state _ process switch(sk—sk _ state)//服務器端三方握手處理caseTCP_SYN_RECV://并改變狀態連接TCP _ set _ state (sk,TCP _ established),
將連接設置為TCP_ESTABLISHED狀態。
服務器響應三次握手ack所做的是刪除當前的半連接對象,創建一個新的sock并將其添加到全連接隊列中,最后將新的連接狀態設置為ESTABLISHED。
不及物動詞服務器接受
接受最后一步讓我們長話短說
//file:net/IP v4/inet _ connection _ sock . cstructsock * inet _ csk _ accept//從全連接隊列中獲取struct request _ sock _ queue * queue = amp,icsk—gt,icsk _ accept _ queuereq = reqsk _ queue _ remove(queue),newsk = req—gt,sk,returnnewsk
Reqsk_queue_remove這個操作很簡單,從全連接隊列的鏈表中獲取第一個元素并返回即可。
//file:include/net/request _ sock . hstaticinlinestructrequest _ sock * reqsk _ queue _ removestructrequest _ sock * req = queue—rskq _ accept _ head,queue—rskq _ accept _ head = req—dl _ next,if(queue—rskq _ accept _ head NULL)queue—rskq _ accept _ tail = NULL,returnreq
因此,accept的關鍵工作是從已建立的全連接隊列中取出一個進程并返回給用戶。
本文摘要
在后端工作面試中,三次握手的出現頻率很高實際上,在三次握手的過程中,不僅僅是發送一個握手包和傳輸TCP狀態它還包括端口選擇,連接隊列創建和處理等許多關鍵技術點通過今天的文章,我們對三次握手過程內核中的這些內部操作有了深入的了解
全文足有幾萬字,其實一張圖就能概括。
1.當服務器監聽時,計算全/半連接隊列的長度,并申請和初始化相關的內存。
2.當客戶端連接時,它將本地套接字狀態設置為TCP_SYN_SENT,選擇一個可用端口,發送一個SYN握手請求,并啟動重新傳輸計時器。
3.當服務器響應ack時,它將確定下一個接收隊列是否已滿,如果已滿,它可能會丟棄該請求否則發出synack,申請將request_sock加入半連接隊列,同時啟動定時器
4.當客戶端響應synack時,它清除連接期間設置的重傳計時器,將當前套接字狀態設置為已建立,并啟動?;钣嫊r器發送第一次三次握手的ack確認。
5.當服務器響應ack時,它刪除相應的半連接對象,創建一個新的sock,然后將其添加到全連接隊列中最后,它將新的連接狀態設置為ESTABLISHED
6.accept從已建立的完整連接隊列中取出一個,并將其返回給用戶進程。
另外,需要注意的是,如果握手過程中發生丟包,內核會等待定時器超時后重試3.10版的重試間隔分別為1s 2s 4s在一些老版本中,比如2.6,第一次重試時間是3秒最大重試次數分別由tcp_syn_retries和tcp_synack_retries控制
如果您的在線界面正常情況下會在幾十毫秒內返回,但偶爾會出現1 s,3 s等響應時間的問題變長了,那你就要定位一下,看看有沒有握手包超時重傳
這是三次握手中一些更詳細的內部操作。如果你能在面試官面前說出內核的底層邏輯,我相信面試官一定會對你印象深刻!
鄭重聲明:此文內容為本網站轉載企業宣傳資訊,目的在于傳播更多信息,與本站立場無關。僅供讀者參考,并請自行核實相關內容。
版權聲明:凡注明“來源:“生活消費網”的所有作品,版權歸生活消費網 | 專注于國內外今日生活資訊網站所有。任何媒體轉載、摘編、引用,須注明來源生活消費網 | 專注于國內外今日生活資訊網站和署著作者名,否則將追究相關法律責任。
-
北京環球影城一周歲:“吃喝玩樂游購住”一應俱全帶動力尚未充分釋放日前,北京環球影城正式開業一周年去哪兒網數據顯示,北京環球影城自開業以來一直是主題公園搜索指數的頂流雖然中途因疫情臨時閉園,但今年6月22日宣布恢復運營后,北京環球影城的搜索人氣瞬間增長6倍,位居全國......2022-10-04 18:29
-
特斯拉交付不及預期,蔚小理抓緊駛入“彎道”周一,特斯拉收盤下跌8.61%,為近四個月來最大單日跌幅前兩天,特斯拉公布了2022年第三季度的交付數據,交付量為34.3萬輛,低于華爾街預期的35.8萬輛 可是在中國,情況就不同了:最近造車新勢力......2022-10-04 18:14
-
三星GalaxyS23Ultra渲染圖曝光:熟悉的外觀變化體現在細節伴隨著年底的臨近,大家的注意力逐漸轉移到了搭載新一代高通旗艦平臺驍龍8Gen2的新一代頂級旗艦上,其中三星新一代年度旗艦GalaxyS23系列作為老機王,自然成為了大家關注的焦點現在最新消息,繼Gal......2022-10-04 18:11
-
電子紙行業景氣度大增多家上市公司布局相關業務最近幾年來,在全球碳中和的背景下,全球電子紙產業迅速崛起TrendForce集邦咨詢公司預計,2022年全球電子紙市場規模約為46.5億美元,年增長率為48.1%預計2026年將達到203.4億美元 ......2022-10-04 17:51
-
培育更多新型職業農民在傳統印象中,職稱評定多見于教師,工程師等職業,似乎與農民聯系不大伴隨著鄉村振興戰略的穩步推進,許多地區出臺了相關政策,并在試點的基礎上開展了職業農民職稱評定工作農藝師,畜牧牧師,農技推廣研究員等新頭......2022-10-04 17:47