大家好,我是飛哥!
在后端相關(guān)崗位的入職面試中,三次握手的出場頻率非常的高,甚至說它是必考題也不為過。一般的答案都是說客戶端如何發(fā)起 SYN 握手進(jìn)入 SYN_SENT 狀態(tài),服務(wù)器響應(yīng) SYN 并回復(fù) SYNACK,然后進(jìn)入 SYN_RECV,...... , 吧啦吧啦諸如此類。
但我今天想給出一份不一樣的答案。其實三次握手在內(nèi)核的實現(xiàn)中,并不只是簡單的狀態(tài)的流轉(zhuǎn),還包括半連接隊列、syncookie、全連接隊列、重傳計時器等關(guān)鍵操作。如果能深刻理解這些,你對線上把握和理解將更進(jìn)一步。如果有面試官問起你三次握手,相信這份答案一定能幫你在面試官面前贏得非常多的加分。
在基于 TCP 的服務(wù)開發(fā)中,三次握手的主要流程圖如下。
服務(wù)器中的核心代碼是創(chuàng)建 socket,綁定端口,listen 監(jiān)聽,最后 accept 接收客戶端的請求。
//服務(wù)端核心代碼
int main(int argc, char const *argv[])
{
int fd = socket(AF_INET, SOCK_STREAM,
0);
bind(fd, ...);
listen(fd,
128);
accept(fd, ...);
...
}
客戶端的相關(guān)代碼是創(chuàng)建 socket,然后調(diào)用 connect 連接 server。
//客戶端核心代碼
int main(){
fd = socket(AF_INET,SOCK_STREAM,
0);
connect(fd, ...);
...
}
圍繞這個三次握手圖,以及客戶端,服務(wù)端的核心代碼,我們來深度探索一下三次握手過程中的內(nèi)部操作。我們從和三次握手過程關(guān)系比較大的 listen 講起!
友情提示:本文中內(nèi)核源碼會比較多。如果你能理解的了更好,如果覺得理解起來有困難,那直接重點看本文中的描述性的文字,尤其是加粗部分的即可。另外文章最后有一張總結(jié)圖歸納和整理了全文內(nèi)容。
一、服務(wù)器的 listen
我們都知道,服務(wù)器在開始提供服務(wù)之前都需要先 listen 一下。但 listen 內(nèi)部究竟干了啥,我們平時很少去琢磨。
今天就讓我們詳細(xì)來看看,直接上一段 listen 時執(zhí)行到的內(nèi)核代碼。
//file: net/core/request_sock.c
int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size =
sizeof(struct listen_sock);
struct listen_sock *lopt;
//計算半連接隊列的長度
nr_table_entries =
min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = ......
//為半連接隊列申請內(nèi)存
lopt_size = nr_table_entries *
sizeof(struct request_sock *);
if (lopt_size > PAGE_SIZE)
lopt = vzalloc(lopt_size);
else
lopt = kzalloc(lopt_size, GFP_KERNEL);
//全連接隊列頭初始化
queue->rskq_accept_head =
NULL;
//半連接隊列設(shè)置
lopt->nr_table_entries = nr_table_entries;
queue->listen_opt = lopt;
......
}
在這段代碼里,內(nèi)核計算了半連接隊列的長度。然后據(jù)此算出半連接隊列所需要的實際內(nèi)存大小,開始申請用于管理半連接隊列對象的內(nèi)存(半連接隊列需要快速查找,所以內(nèi)核是用哈希表來管理半連接隊列的,具體在 listen_sock 下的 syn_table 下)。最后將半連接隊列掛到了接收隊列 queue 上。
另外 queue->rskq_accept_head 代表的是全連接隊列,它是一個鏈表的形式。在 listen 這里因為還沒有連接,所以將全連接隊列頭 queue->rskq_accept_head 設(shè)置成 NULL。
當(dāng)全連接隊列和半連接隊列中有元素的時候,他們在內(nèi)核中的結(jié)構(gòu)圖大致如下。
在服務(wù)器 listen 的時候,主要是進(jìn)行了全/半連接隊列的長度限制計算,以及相關(guān)的內(nèi)存申請和初始化。全/連接隊列初始化了以后才可以相應(yīng)來自客戶端的握手請求。
二、客戶端 connect
客戶端通過調(diào)用 connect 來發(fā)起連接。在 connect 系統(tǒng)調(diào)用中會進(jìn)入到內(nèi)核源碼的 tcp_v4_connect。
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
//設(shè)置 socket 狀態(tài)為 TCP_SYN_SENT
tcp_set_state(sk, TCP_SYN_SENT);
//動態(tài)選擇一個端口
err = inet_hash_connect(
本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。