<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入理解Linux的TCP三次握手!

          共 12571字,需瀏覽 26分鐘

           ·

          2022-07-25 10:39


          導(dǎo)語(yǔ) | 關(guān)于三次握手一般的答案都是說(shuō)客戶(hù)端發(fā)起SYN,服務(wù)器響應(yīng)SYN并回復(fù)SYNACK,諸如此類(lèi)。但我今天想給出一份非常有深度的的答案,在從Linux實(shí)現(xiàn)層面帶你重新深度認(rèn)識(shí)一下三次握手!


          引言


          其實(shí)三次握手在內(nèi)核的實(shí)現(xiàn)中,并不只是簡(jiǎn)單的狀態(tài)的流轉(zhuǎn),還包括端口選擇,半連接隊(duì)列、syncookie、全連接隊(duì)列、重傳計(jì)時(shí)器等關(guān)鍵操作。如果能深刻理解這些,你對(duì)線(xiàn)上把握和理解將更進(jìn)一步。如果有面試官問(wèn)起你三次握手,相信這份答案一定能幫你在面試官面前贏得非常多的加分。


          在基于TCP的服務(wù)開(kāi)發(fā)中,三次握手的主要流程圖如下。



          服務(wù)器中的核心代碼是創(chuàng)建socket,綁定端口,listen監(jiān)聽(tīng),最后accept接收客戶(hù)端的請(qǐng)求。


          //服務(wù)端核心代碼int main(int argc, char const *argv[]){  int fd = socket(AF_INET, SOCK_STREAM, 0);  bind(fd, ...);  listen(fd, 128);  accept(fd, ...);  ...}


          客戶(hù)端的相關(guān)代碼是創(chuàng)建socket,然后調(diào)用connect連接server。


          //客戶(hù)端核心代碼int main(){  fd = socket(AF_INET,SOCK_STREAM, 0);  connect(fd, ...);  ...}


          看起來(lái)簡(jiǎn)單的幾個(gè)系統(tǒng)調(diào)用,實(shí)際上卻包含了非常復(fù)雜的內(nèi)核底層操作。根據(jù)內(nèi)核工作原理,我深度展開(kāi)一下三次握手過(guò)程中的內(nèi)部操作。




          上圖中描述的步驟較多。所以接下來(lái)我們按照每一個(gè)步驟分開(kāi)來(lái)講,先從和三次握手過(guò)程關(guān)系比較大的listen講起!


          友情提示:本文中內(nèi)核源碼會(huì)比較多。如果你能理解的了更好,如果覺(jué)得理解起來(lái)有困難,那直接重點(diǎn)看本文中的描述性的文字,尤其是加粗部分的即可。另外文章最后有一張總結(jié)圖歸納和整理了全文內(nèi)容。


          一、服務(wù)器的listen


          我們都知道,服務(wù)器在開(kāi)始提供服務(wù)之前都需要先listen一下。但listen內(nèi)部究竟干了啥,我們平時(shí)很少去琢磨。


          今天就讓我們?cè)敿?xì)來(lái)看看,直接上一段listen時(shí)執(zhí)行到的內(nèi)核代碼。


          //file: net/core/request_sock.cint 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;
          //計(jì)算半連接隊(duì)列的長(zhǎng)度 nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog); nr_table_entries = ......
          //為半連接隊(duì)列申請(qǐng)內(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);
          //全連接隊(duì)列頭初始化 queue->rskq_accept_head = NULL;
          //半連接隊(duì)列設(shè)置 lopt->nr_table_entries = nr_table_entries; queue->listen_opt = lopt; ......}


          在這段代碼里,內(nèi)核計(jì)算了半連接隊(duì)列的長(zhǎng)度。然后據(jù)此算出半連接隊(duì)列所需要的實(shí)際內(nèi)存大小,開(kāi)始申請(qǐng)用于管理半連接隊(duì)列對(duì)象的內(nèi)存(半連接隊(duì)列需要快速查找,所以?xún)?nèi)核是用哈希表來(lái)管理半連接隊(duì)列的,具體在listen_sock下的syn_table下)。最后將半連接隊(duì)列掛到了接收隊(duì)列queue上。


          另外queue->rskq_accept_head代表的是全連接隊(duì)列,它是一個(gè)鏈表的形式。在listen這里因?yàn)檫€沒(méi)有連接,所以將全連接隊(duì)列頭queue->rskq_accept_head設(shè)置成NULL。


          當(dāng)全連接隊(duì)列和半連接隊(duì)列中有元素的時(shí)候,他們?cè)趦?nèi)核中的結(jié)構(gòu)圖大致如下。



          在服務(wù)器listen的時(shí)候,主要是進(jìn)行了全/半連接隊(duì)列的長(zhǎng)度限制計(jì)算,以及相關(guān)的內(nèi)存申請(qǐng)和初始化。全/連接隊(duì)列初始化了以后才可以相應(yīng)來(lái)自客戶(hù)端的握手請(qǐng)求。


          如果想了解更多的listen內(nèi)部操作細(xì)節(jié)可以看之前的一篇文章《為什么服務(wù)端程序都需要先listen一下?》



          二、客戶(hù)端connect


          客戶(hù)端通過(guò)調(diào)用connect來(lái)發(fā)起連接。在connect系統(tǒng)調(diào)用中會(huì)進(jìn)入到內(nèi)核源碼的tcp_v4_connect。


          //file: net/ipv4/tcp_ipv4.cint 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);
          //動(dòng)態(tài)選擇一個(gè)端口 err = inet_hash_connect(&tcp_death_row, sk);
          //函數(shù)用來(lái)根據(jù) sk 中的信息,構(gòu)建一個(gè)完成的 syn 報(bào)文,并將它發(fā)送出去。 err = tcp_connect(sk);}


          在這里將完成把socket狀態(tài)設(shè)置為T(mén)CP_SYN_SENT。再通過(guò)inet_hash_connect來(lái)動(dòng)態(tài)地選擇一個(gè)可用的端口后(端口選擇詳細(xì)過(guò)程參考TCP連接中客戶(hù)端的端口號(hào)是如何確定的?,進(jìn)入到tcp_connect中。


          //file:net/ipv4/tcp_output.cint tcp_connect(struct sock *sk){  tcp_connect_init(sk);
          //申請(qǐng) skb 并構(gòu)造為一個(gè) SYN 包 ......
          //添加到發(fā)送隊(duì)列 sk_write_queue 上 tcp_connect_queue_skb(sk, buff);
          //實(shí)際發(fā)出 syn err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) : tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
          //啟動(dòng)重傳定時(shí)器 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)->icsk_rto, TCP_RTO_MAX);}


          在tcp_connect申請(qǐng)和構(gòu)造SYN包,然后將其發(fā)出。同時(shí)還啟動(dòng)了一個(gè)重傳定時(shí)器,該定時(shí)器的作用是等到一定時(shí)間后收不到服務(wù)器的反饋的時(shí)候來(lái)開(kāi)啟重傳。在3.10版本中首次超時(shí)時(shí)間是1s,一些老版本中是3s。


          總結(jié)一下,客戶(hù)端在connect的時(shí)候,把本地socket狀態(tài)設(shè)置成了 TCP_SYN_SENT,選了一個(gè)可用的端口,接著發(fā)出SYN握手請(qǐng)求并啟動(dòng)重傳定時(shí)器



          三、服務(wù)器響應(yīng)SYN


          在服務(wù)器端,所有的TCP包(包括客戶(hù)端發(fā)來(lái)的SYN握手請(qǐng)求)都經(jīng)過(guò)網(wǎng)卡、軟中斷,進(jìn)入到tcp_v4_rcv。在該函數(shù)中根據(jù)網(wǎng)絡(luò)包(skb)TCP頭信息中的目的IP信息查到當(dāng)前在listen的socket。然后繼續(xù)進(jìn)入tcp_v4_do_rcv處理握手過(guò)程。


          //file: net/ipv4/tcp_ipv4.cint tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb){  ...  //服務(wù)器收到第一步握手 SYN 或者第三步 ACK 都會(huì)走到這里  if (sk->sk_state == TCP_LISTEN) {    struct sock *nsk = tcp_v4_hnd_req(sk, skb);  }
          if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) { rsk = sk; goto reset; }}


          在tcp_v4_do_rcv中判斷當(dāng)前socket是listen狀態(tài)后,首先會(huì)到tcp_v4_hnd_req去查看半連接隊(duì)列。服務(wù)器第一次響應(yīng)SYN的時(shí)候,半連接隊(duì)列里必然是空空如也,所以相當(dāng)于什么也沒(méi)干就返回了。


          //file:net/ipv4/tcp_ipv4.cstatic struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb){  // 查找 listen socket 的半連接隊(duì)列  struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,                 iph->saddr, iph->daddr);  ...  return sk;}


          在tcp_rcv_state_process里根據(jù)不同的socket狀態(tài)進(jìn)行不同的處理。


          //file:net/ipv4/tcp_input.cint tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,        const struct tcphdr *th, unsigned int len){  switch (sk->sk_state) {    //第一次握手    case TCP_LISTEN:      if (th->syn) { //判斷是 SYN 握手包        ...        if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)          return 1;  ......}


          其中conn_request是一個(gè)函數(shù)指針,指向tcp_v4_conn_request。服務(wù)器響應(yīng)SYN的主要處理邏輯都在這個(gè)tcp_v4_conn_request里


          //file: net/ipv4/tcp_ipv4.cint tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb){  //看看半連接隊(duì)列是否滿(mǎn)了  if (inet_csk_reqsk_queue_is_full(sk) && !isn) {    want_cookie = tcp_syn_flood_action(sk, skb, "TCP");    if (!want_cookie)      goto drop;  }
          //在全連接隊(duì)列滿(mǎn)的情況下,如果有 young_ack,那么直接丟 if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); goto drop; } ... //分配 request_sock 內(nèi)核對(duì)象 req = inet_reqsk_alloc(&tcp_request_sock_ops);
          //構(gòu)造 syn+ack 包 skb_synack = tcp_make_synack(sk, dst, req, fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL);
          if (likely(!do_fastopen)) { //發(fā)送 syn + ack 響應(yīng) err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr, ireq->rmt_addr, ireq->opt);
          //添加到半連接隊(duì)列,并開(kāi)啟計(jì)時(shí)器 inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT); }else ...}


          在這里首先判斷半連接隊(duì)列是否滿(mǎn)了,如果滿(mǎn)了的話(huà)進(jìn)入tcp_syn_flood_action去判斷是否開(kāi)啟了tcp_syncookies內(nèi)核參數(shù)。 如果隊(duì)列滿(mǎn),且未開(kāi)啟tcp_syncookies,那么該握手包將直接被丟棄


          接著還要判斷全連接隊(duì)列是否滿(mǎn)。因?yàn)槿B接隊(duì)列滿(mǎn)也會(huì)導(dǎo)致握手異常的,那干脆就在第一次握手的時(shí)候也判斷了。如果全連接隊(duì)列滿(mǎn)了,且有young_ack的話(huà),那么同樣也是直接丟棄


          young_ack是半連接隊(duì)列里保持著的一個(gè)計(jì)數(shù)器。記錄的是剛有SYN到達(dá),沒(méi)有被SYN_ACK重傳定時(shí)器重傳過(guò)SYN_ACK,同時(shí)也沒(méi)有完成過(guò)三次握手的sock數(shù)量。


          接下來(lái)是構(gòu)造synack包,然后通過(guò)ip_build_and_send_pkt把它發(fā)送出去。


          最后把當(dāng)前握手信息添加到半連接隊(duì)列,并開(kāi)啟計(jì)時(shí)器。計(jì)時(shí)器的作用是如果某個(gè)時(shí)間之內(nèi)還收不到客戶(hù)端的第三次握手的話(huà),服務(wù)器會(huì)重傳synack包。


          總結(jié)一下,服務(wù)器響應(yīng)ack是主要工作是判斷下接收隊(duì)列是否滿(mǎn)了,滿(mǎn)的話(huà)可能會(huì)丟棄該請(qǐng)求,否則發(fā)出synack。申請(qǐng)request_sock添加到半連接隊(duì)列中,同時(shí)啟動(dòng)定時(shí)器



          四、客戶(hù)端響應(yīng)SYNACK


          客戶(hù)端收到服務(wù)器端發(fā)來(lái)的synack包的時(shí)候,也會(huì)進(jìn)入到tcp_rcv_state_process函數(shù)中來(lái)。不過(guò)由于自身socket的狀態(tài)是 TCP_SYN_SENT,所以會(huì)進(jìn)入到另一個(gè)不同的分支中去。


          //file:net/ipv4/tcp_input.c//除了 ESTABLISHED 和 TIME_WAIT,其他狀態(tài)下的 TCP 處理都走這里int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,        const struct tcphdr *th, unsigned int len){  switch (sk->sk_state) {    //服務(wù)器收到第一個(gè)ACK包    case TCP_LISTEN:      ...    //客戶(hù)端第二次握手處理     case TCP_SYN_SENT:      //處理 synack 包      queued = tcp_rcv_synsent_state_process(sk, skb, th, len);      ...      return 0;}


          tcp_rcv_synsent_state_process是客戶(hù)端響應(yīng)synack的主要邏輯。


          //file:net/ipv4/tcp_input.cstatic int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,           const struct tcphdr *th, unsigned int len){  ...
          tcp_ack(sk, skb, FLAG_SLOWPATH);
          //連接建立完成 tcp_finish_connect(sk, skb);
          if (sk->sk_write_pending || icsk->icsk_accept_queue.rskq_defer_accept || icsk->icsk_ack.pingpong) //延遲確認(rèn)... else { tcp_send_ack(sk); }}


          tcp_ack()->tcp_clean_rtx_queue()


          //file: net/ipv4/tcp_input.cstatic int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets,           u32 prior_snd_una){  //刪除發(fā)送隊(duì)列  ...
          //刪除定時(shí)器 tcp_rearm_rto(sk);}


          //file: net/ipv4/tcp_input.cvoid tcp_finish_connect(struct sock *sk, struct sk_buff *skb){  //修改 socket 狀態(tài)  tcp_set_state(sk, TCP_ESTABLISHED);
          //初始化擁塞控制 tcp_init_congestion_control(sk); ...
          //保活計(jì)時(shí)器打開(kāi) if (sock_flag(sk, SOCK_KEEPOPEN)) inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));}


          客戶(hù)端修改自己的socket狀態(tài)為ESTABLISHED,接著打開(kāi)TCP的保活計(jì)時(shí)器。


          //file:net/ipv4/tcp_output.cvoid tcp_send_ack(struct sock *sk){  //申請(qǐng)和構(gòu)造 ack 包  buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));  ...
          //發(fā)送出去 tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));}


          在tcp_send_ack中構(gòu)造ack包,并把它發(fā)送了出去。


          客戶(hù)端響應(yīng)來(lái)自服務(wù)器端的synack時(shí)清除了connect時(shí)設(shè)置的重傳定時(shí)器,把當(dāng)前socket狀態(tài)設(shè)置為ESTABLISHED,開(kāi)啟保活計(jì)時(shí)器后發(fā)出第三次握手的ack確認(rèn)



          五、服務(wù)器響應(yīng)ACK


          服務(wù)器響應(yīng)第三次握手的ack時(shí)同樣會(huì)進(jìn)入到tcp_v4_do_rcv


          //file: net/ipv4/tcp_ipv4.cint tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb){  ...  if (sk->sk_state == TCP_LISTEN) {    struct sock *nsk = tcp_v4_hnd_req(sk, skb);
          if (nsk != sk) { if (tcp_child_process(sk, nsk, skb)) { ... } return 0; } } ...}


          不過(guò)由于這已經(jīng)是第三次握手了,半連接隊(duì)列里會(huì)存在上次第一次握手時(shí)留下的半連接信息。所以 tcp_v4_hnd_req 的執(zhí)行邏輯會(huì)不太一樣。


          //file:net/ipv4/tcp_ipv4.cstatic struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb){  ...  struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,                 iph->saddr, iph->daddr);  if (req)    return tcp_check_req(sk, skb, req, prev, false);  ...}


          inet_csk_search_req負(fù)責(zé)在半連接隊(duì)列里進(jìn)行查找,找到以后返回一個(gè)半連接request_sock對(duì)象。然后進(jìn)入到tcp_check_req中。

          ?


          //file:net/ipv4/tcp_minisocks.cstruct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,         struct request_sock *req,         struct request_sock **prev,         bool fastopen){  ...  //創(chuàng)建子 socket  child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);  ...
          //清理半連接隊(duì)列 inet_csk_reqsk_queue_unlink(sk, req, prev); inet_csk_reqsk_queue_removed(sk, req);
          //添加全連接隊(duì)列 inet_csk_reqsk_queue_add(sk, req, child); return child;}



          (一)創(chuàng)建子socket


          icsk_af_ops->syn_recv_sock對(duì)應(yīng)的是tcp_v4_syn_recv_sock函數(shù)。


          //file:net/ipv4/tcp_ipv4.cconst struct inet_connection_sock_af_ops ipv4_specific = {  ......  .conn_request      = tcp_v4_conn_request,  .syn_recv_sock     = tcp_v4_syn_recv_sock,
          //三次握手接近就算是完畢了,這里創(chuàng)建 sock 內(nèi)核對(duì)象struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct dst_entry *dst){ //判斷接收隊(duì)列是不是滿(mǎn)了 if (sk_acceptq_is_full(sk)) goto exit_overflow;
          //創(chuàng)建 sock && 初始化 newsk = tcp_create_openreq_child(sk, req, skb);


          注意,在第三次握手的這里又繼續(xù)判斷一次全連接隊(duì)列是否滿(mǎn)了,如果滿(mǎn)了修改一下計(jì)數(shù)器就丟棄了。如果隊(duì)列不滿(mǎn),那么就申請(qǐng)創(chuàng)建新的sock對(duì)象。



          (二)刪除半連接隊(duì)列


          把連接請(qǐng)求塊從半連接隊(duì)列中刪除。


          //file: include/net/inet_connection_sock.h static inline void inet_csk_reqsk_queue_unlink(struct sock *sk, struct request_sock *req,  struct request_sock **prev){  reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);}


          reqsk_queue_unlink中把連接請(qǐng)求塊從半連接隊(duì)列中刪除。



          (三)添加全連接隊(duì)列


          接著添加到全連接隊(duì)列里邊來(lái)。


          //file:net/ipv4/syncookies.cstatic inline void inet_csk_reqsk_queue_add(struct sock *sk,            struct request_sock *req,            struct sock *child){  reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);}


          在reqsk_queue_add中將握手成功的request_sock對(duì)象插入到全連接隊(duì)列鏈表的尾部。


          //file: include/net/request_sock.hstatic inline void reqsk_queue_add(...){  req->sk = child;  sk_acceptq_added(parent);
          if (queue->rskq_accept_head == NULL) queue->rskq_accept_head = req; else queue->rskq_accept_tail->dl_next = req;
          queue->rskq_accept_tail = req; req->dl_next = NULL;}



          (四)設(shè)置連接為ESTABLISHED


          tcp_v4_do_rcv=>tcp_child_process=>tcp_rcv_state_process


          //file:net/ipv4/tcp_input.cint tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,        const struct tcphdr *th, unsigned int len){  ...  switch (sk->sk_state) {
          //服務(wù)端第三次握手處理 case TCP_SYN_RECV:
          //改變狀態(tài)為連接 tcp_set_state(sk, TCP_ESTABLISHED); ... }}


          將連接設(shè)置為T(mén)CP_ESTABLISHED狀態(tài)。


          服務(wù)器響應(yīng)第三次握手ack所做的工作是把當(dāng)前半連接對(duì)象刪除,創(chuàng)建了新的sock后加入到全連接隊(duì)列中,最后將新連接狀態(tài)設(shè)置為ESTABLISHED



          六、服務(wù)器accept


          最后accept一步咱們長(zhǎng)話(huà)短說(shuō)。


          //file: net/ipv4/inet_connection_sock.cstruct sock *inet_csk_accept(struct sock *sk, int flags, int *err){  //從全連接隊(duì)列中獲取  struct request_sock_queue *queue = &icsk->icsk_accept_queue;  req = reqsk_queue_remove(queue);
          newsk = req->sk; return newsk;}


          reqsk_queue_remove這個(gè)操作很簡(jiǎn)單,就是從全連接隊(duì)列的鏈表里獲取出第一個(gè)元素返回就行了。


          //file:include/net/request_sock.hstatic inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue){  struct request_sock *req = queue->rskq_accept_head;
          queue->rskq_accept_head = req->dl_next; if (queue->rskq_accept_head == NULL) queue->rskq_accept_tail = NULL;
          return req;}


          所以,accept的重點(diǎn)工作就是從已經(jīng)建立好的全連接隊(duì)列中取出一個(gè)返回給用戶(hù)進(jìn)程



          七、總結(jié)


          在后端相關(guān)崗位的入職面試中,三次握手的出場(chǎng)頻率非常的高。其實(shí)在三次握手的過(guò)程中,不僅僅是一個(gè)握手包的發(fā)送和TCP狀態(tài)的流轉(zhuǎn)。還包含了端口選擇,連接隊(duì)列創(chuàng)建與處理等很多關(guān)鍵技術(shù)點(diǎn)。通過(guò)今天一篇文章,我們深度去了解了三次握手過(guò)程中內(nèi)核中的這些內(nèi)部操作。


          全文洋洋灑灑上萬(wàn)字字,其實(shí)可以用一幅圖總結(jié)起來(lái)。



          • 服務(wù)器listen時(shí),計(jì)算了全/半連接隊(duì)列的長(zhǎng)度,還申請(qǐng)了相關(guān)內(nèi)存并初始化。


          • 客戶(hù)端connect時(shí),把本地socket狀態(tài)設(shè)置成了TCP_SYN_SENT,選則一個(gè)可用的端口,發(fā)出SYN握手請(qǐng)求并啟動(dòng)重傳定時(shí)器。


          • 服務(wù)器響應(yīng)ack時(shí),會(huì)判斷下接收隊(duì)列是否滿(mǎn)了,滿(mǎn)的話(huà)可能會(huì)丟棄該請(qǐng)求。否則發(fā)出synack,申請(qǐng)request_sock添加到半連接隊(duì)列中,同時(shí)啟動(dòng)定時(shí)器。


          • 客戶(hù)端響應(yīng)synack時(shí),清除了connect時(shí)設(shè)置的重傳定時(shí)器,把當(dāng)前socket狀態(tài)設(shè)置為ESTABLISHED,開(kāi)啟保活計(jì)時(shí)器后發(fā)出第三次握手的ack確認(rèn)。


          • 服務(wù)器響應(yīng)ack時(shí),把對(duì)應(yīng)半連接對(duì)象刪除,創(chuàng)建了新的sock后加入到全連接隊(duì)列中,最后將新連接狀態(tài)設(shè)置為ESTABLISHED。


          • accept從已經(jīng)建立好的全連接隊(duì)列中取出一個(gè)返回給用戶(hù)進(jìn)程。


          另外要注意的是,如果握手過(guò)程中發(fā)生丟包(網(wǎng)絡(luò)問(wèn)題,或者是連接隊(duì)列溢出),內(nèi)核會(huì)等待定時(shí)器到期后重試,重試時(shí)間間隔在3.10版本里分別是1s 2s 4s...。在一些老版本里,比如2.6里,第一次重試時(shí)間是3秒。最大重試次數(shù)分別由tcp_syn_retries和tcp_synack_retries控制。


          如果你的線(xiàn)上接口正常都是幾十毫秒內(nèi)返回,但偶爾出現(xiàn)了1s、或者3s等這種偶發(fā)的響應(yīng)耗時(shí)變長(zhǎng)的問(wèn)題,那么你就要去定位一下看看是不是出現(xiàn)了握手包的超時(shí)重傳了。


          以上就是三次握手中一些更詳細(xì)的內(nèi)部操作。深度理解這個(gè)握手過(guò)程對(duì)于你排查線(xiàn)上問(wèn)題會(huì)有極大的幫助的。下一講我們將介紹三次握手中常見(jiàn)的異常問(wèn)題。



           作者簡(jiǎn)介


          張彥飛

          騰訊開(kāi)發(fā)工程師

          騰訊開(kāi)發(fā)工程師,有騰訊搜狗累計(jì)十多年的開(kāi)發(fā)經(jīng)驗(yàn),目前負(fù)責(zé)騰訊瀏覽器業(yè)務(wù)后端開(kāi)發(fā)。



           推薦閱讀


          如何用Go實(shí)現(xiàn)一個(gè)異步網(wǎng)絡(luò)庫(kù)?

          如何優(yōu)雅地實(shí)現(xiàn)C++編譯期多態(tài)?

          C++異步:libunifex的scheduler實(shí)現(xiàn)!

          5G正當(dāng)時(shí),無(wú)人駕駛未來(lái)將駛向何方?



          溫馨提示:因公眾號(hào)平臺(tái)更改了推送規(guī)則,公眾號(hào)推送的文章文末需要點(diǎn)一下“贊”“在看”,新的文章才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里噢~

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99在线视频观看 | 亚洲无码精品电影 | 亚洲热免费视频 | 成人婷婷五月 | 人人操人人网站 |