天天看點

Linux核心分析 - 網絡[十六]:TCP三向交握

  核心:2.6.34

      TCP是應用最廣泛的傳輸層協定,其提供了面向連接配接的、可靠的位元組流服務,但也正是因為這些特性,使得TCP較之UDP異常複雜,還是分兩部分[建立與使用]來進行分析。這篇主要包括TCP的建立及三次握手的過程。

      程式設計時一般用如下語句建立TCP Socket:

[cpp]

​​ view plain​​

​​ copy​​

  1. socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP)  

      由此開始分析,調用接口[net/socket.c]: SYSCALL_DEFINE3(socket)

      其中執行兩步關鍵操作:sock_create()與sock_map_fd()

[cpp]

​​ view plain​​

​​ copy​​

  1. retval = sock_create(family, type, protocol, &sock);  
  2. if
  3. goto
  4. retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));  
  5. if
  6. goto

      sock_create()用于建立socket,sock_map_fd()将之映射到檔案描述符,使socket能通過fd進行通路,着重分析sock_create()的建立過程。

      sock_create() -> __sock_create()

      從__sock_create()代碼看到建立包含兩步:sock_alloc()和pf->create()。sock_alloc()配置設定了sock記憶體空間并初始化inode;pf->create()初始化了sk。

[cpp]

​​ view plain​​

​​ copy​​

  1. sock = sock_alloc();  
  2. sock->type = type;  
  3. ……  
  4. pf = rcu_dereference(net_families[family]);  
  5. ……  
  6. pf->create(net, sock, protocol, kern);  

sock_alloc()

      配置設定空間,通過new_inode()配置設定了節點(包括socket),然後通過SOCKET_I宏獲得sock,實際上inode和sock是在new_inode()中一起配置設定的,結構體叫作sock_alloc。

[cpp]

​​ view plain​​

​​ copy​​

  1. inode = new_inode(sock_mnt->mnt_sb);  
  2. sock = SOCKET_I(inode);  

      設定inode的參數,并傳回sock。

[cpp]

​​ view plain​​

​​ copy​​

  1. inode->i_mode = S_IFSOCK | S_IRWXUGO;  
  2. inode->i_uid = current_fsuid();  
  3. inode->i_gid = current_fsgid();  
  4. return

      繼續往下看具體的建立過程:new_inode(),在配置設定後,會設定i_ino和i_state的值。

[cpp]

​​ view plain​​

​​ copy​​

  1. struct inode *new_inode(struct
  2. {  
  3.  ……  
  4.  inode = alloc_inode(sb);  
  5. if
  6.   spin_lock(&inode_lock);  
  7.   __inode_add_to_lists(sb, NULL, inode);  
  8.   inode->i_ino = ++last_ino;  
  9.   inode->i_state = 0;  
  10.   spin_unlock(&inode_lock);  
  11.  }  
  12. return
  13. }  

      其中的alloc_inode() -> sb->s_op->alloc_inode(),sb是sock_mnt->mnt_sb,是以alloc_inode()指向的是sockfs的操作函數sock_alloc_inode。

[cpp]

​​ view plain​​

​​ copy​​

  1. static const struct
  2.  .alloc_inode = sock_alloc_inode,  
  3.  .destroy_inode =sock_destroy_inode,  
  4.  .statfs = simple_statfs,  
  5. };  

      sock_alloc_inode()中通過kmem_cache_alloc()配置設定了struct socket_alloc結構體大小的空間,而struct socket_alloc結構體定義如下,但隻傳回了inode,實際上socket和inode都已經配置設定了空間,在之後就可以通過container_of取到socket。

[cpp]

​​ view plain​​

​​ copy​​

  1. static struct inode *sock_alloc_inode(struct
  2. {  
  3. struct
  4.  ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);  
  5.  …..  
  6. return
  7. }  
  8. struct
  9. struct
  10. struct
  11. };  
  12. net_families[AF_INET]:  
  13. static const struct
  14.  .family = PF_INET,  
  15.  .create = inet_create,  
  16.  .owner = THIS_MODULE,  
  17. };  

err = pf->create(net, sock, protocol, kern); ==> inet_create()

      這段代碼就是從inetsw[]中取到适合的協定類型answer,sock->type就是傳入socket()函數的type參數SOCK_DGRAM,最終取得結果answer->ops==inet_stream_ops,從上面這段代碼還可以看出以下問題:

      socket(AF_INET, SOCK_RAW, IPPROTO_IP)這樣是不合法的,因為SOCK_RAW沒有預設的協定類型;同樣socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)與socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP)是一樣的,因為TCP的預設協定類型是IPPTOTO_TCP;SOCK_STREAM與IPPROTO_UDP同上。

[cpp]

​​ view plain​​

​​ copy​​

  1. sock->state = SS_UNCONNECTED;  
  2. list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {  
  3.  err = 0;  
  4. /* Check the non-wild match. */
  5. if
  6. if
  7. break;  
  8. else
  9. /* Check for the two wild cases. */
  10. if
  11.    protocol = answer->protocol;  
  12. break;  
  13.   }  
  14. if
  15. break;  
  16.  }  
  17.  err = -EPROTONOSUPPORT;  
  18. }  

      sock->ops指向inet_stream_ops,然後建立sk,sk->proto指向tcp_prot,注意這裡配置設定的大小是struct tcp_sock,而不僅僅是struct sock大小

[cpp]

​​ view plain​​

​​ copy​​

  1. sock->ops = answer->ops;  
  2. answer_prot = answer->prot;  
  3. ……  
  4. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);  

      然後設定inet的一些參數,這裡直接将sk類型轉換為inet,因為在sk_alloc()中配置設定的是struct tcp_sock結構大小,傳回的是struct sock,利用了第一個成員的特性,三者之間的關系如下圖:

[cpp]

​​ view plain​​

​​ copy​​

  1. inet = inet_sk(sk);  
  2. ……  
  3. inet->inet_id = 0;  
  4. sock_init_data(sock, sk);  
Linux核心分析 - 網絡[十六]:TCP三向交握

      其中有些設定是比較重要的,如

[cpp]

​​ view plain​​

​​ copy​​

  1. sk->sk_state = TCP_CLOSE;  
  2. sk_set_socket(sk, sock);  
  3. sk->sk_protocol = protocol;  
  4. sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;  

      建立socket後,接下來的流程會因為用戶端或伺服器的不同而有所差異,下面着重于分析建立連接配接的三次握手過程。典型的用戶端流程:

connect() -> send() -> recv()

      典型的伺服器流程:

bind() -> listen() -> accept() -> recv() -> send()

用戶端流程

*發送SYN封包,向伺服器發起tcp連接配接

      connect(fd, servaddr, addrlen);

       -> SYSCALL_DEFINE3() 

       -> sock->ops->connect() == inet_stream_connect (sock->ops即inet_stream_ops)

       -> tcp_v4_connect()

      查找到達[daddr, dport]的路由項,路由項的查找與更新與”路由表”章節所述一樣。要注意的是由于是作為用戶端調用,建立socket後調用connect,因而saddr, sport都是0,同樣在未查找路由前,要走的出接口oif也是不知道的,是以也是0。在查找完路由表後(注意不是路由緩存),可以得知出接口,但并未存儲到sk中。是以插入的路由緩存是特别要注意的:它的鍵值與實際值是不相同的,這個不同點就在于oif與saddr,鍵值是[saddr=0, sport=0, daddr, dport, oif=0],而緩存項值是[saddr, sport=0, daddr, dport, oif]。

[cpp]

​​ view plain​​

​​ copy​​

  1. tmp = ip_route_connect(&rt, nexthop, inet->inet_saddr,  
  2.       RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,  
  3.       IPPROTO_TCP,  
  4.       inet->inet_sport, usin->sin_port, sk, 1);  
  5. if
  6. if
  7.   IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);  
  8. return
  9. }  

      通過查找到的路由項,對inet進行指派,可以看到,除了sport,都賦予了值,sport的選擇複雜點,因為它要随機從未使用的本地端口中選擇一個。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.  inet->inet_saddr = rt_rt_src;   
  3. inet->inet_rcv_addr = inet->inet_saddr;  
  4. ……  
  5. inet->inet_dport = usin->sin_port;  
  6. inet->inet_daddr = daddr;  

      狀态從CLOSING轉到TCP_SYN_SENT,也就是我們熟知的TCP的狀态轉移圖。

[cpp]

​​ view plain​​

​​ copy​​

  1. tcp_set_state(sk, TCP_SYN_SENT);  

      插入到bind連結清單中

[cpp]

​​ view plain​​

​​ copy​​

  1. err = inet_hash_connect(&tcp_death_row, sk); //== > __inet_hash_connect()

      當snum==0時,表明此時源端口沒有指定,此時會随機選擇一個空閑端口作為此次連接配接的源端口。low和high分别表示可用端口的下限和上限,remaining表示可用端口的數,注意這裡的可用隻是指端口可以用作源端口,其中部分端口可能已經作為其它socket的端口号在使用了,是以要循環1~remaining,直到查找到空閑的源端口。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.  inet_get_local_port_range(&low, &high);  
  3.  remaining = (high - low) + 1;  
  4.  ……  
  5. for
  6. ……// choose a valid port
  7. }  
  8. }  

      下面來看下對每個端口的檢查,即//choose a valid port部分的代碼。這裡要先了解下tcp的核心表組成,udp的表核心表udptable隻是一張hash表,tcp的表則稍複雜,它的名字是tcp_hashinfo,在tcp_init()中被初始化,這個資料結構定義如下(省略了不相關的資料):

[cpp]

​​ view plain​​

​​ copy​​

  1. struct
  2. struct
  3.  ……  
  4. struct
  5.  ……  
  6. struct
  7.      ____cacheline_aligned_in_smp;  
  8. };  

      從定義可以看出,tcp表又分成了三張表ehash, bhash, listening_hash,其中ehash, listening_hash對應于socket處在TCP的ESTABLISHED, LISTEN狀态,bhash對應于socket已綁定了本地位址。三者間并不互斥,如一個socket可同時在bhash和ehash中,由于TIME_WAIT是一個比較特殊的狀态,是以ehash又分成了chain和twchain,為TIME_WAIT的socket單獨形成一張表。

回到剛才的代碼,現在還隻是建立socket連接配接,使用的就應該是tcp表中的bhash。首先取得核心tcp表的bind表 – bhash,檢視是否已有socket占用:

      如果沒有,則調用inet_bind_bucket_create()建立一個bind表項tb,并插入到bind表中,跳轉至goto ok代碼段;

如果有,則跳轉至goto ok代碼段。

      進入ok代碼段表明已找到合适的bind表項(無論是建立的還是查找到的),調用inet_bind_hash()指派源端口inet_num。

[cpp]

​​ view plain​​

​​ copy​​

  1. for
  2.  port = low + (i + offset) % remaining;  
  3.  head = &hinfo->bhash[inet_bhashfn(net, port, hinfo->bhash_size)];  
  4.  ……  
  5.  inet_bind_bucket_for_each(tb, node, &head->chain) {  
  6. if
  7. if
  8. goto
  9.    WARN_ON(hlist_empty(&tb->owners));  
  10. if
  11. goto
  12. goto
  13.   }  
  14.  }  
  15.  tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep, net, head, port);  
  16.  ……  
  17.  next_port:  
  18.   spin_unlock(&head->lock);  
  19. }  
  20. ok:  
  21.  ……  
  22. inet_bind_hash(sk, tb, port);  
  23.  ……  
  24. goto

      在擷取到合适的源端口号後,會重建路由項來進行更新:

[cpp]

​​ view plain​​

​​ copy​​

  1. err = ip_route_newports(&rt, IPPROTO_TCP, inet->inet_sport, inet->inet_dport, sk);  

      函數比較簡單,在擷取sport前已經查找過一次路由表,并插入了key=[saddr=0, sport=0, daddr, dport, oif=0]的路由緩存項;現在擷取到了sport,調用ip_route_output_flow()再次更新路由緩存表,它會添加key=[saddr=0, sport, daddr, dport, oif=0]的路由緩存項。這裡可以看出一個政策選擇,查詢路由表->擷取sport->查詢路由表,為什麼不是擷取sport->查詢路由表的原因可能是效率的問題。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.     dport != (*rp)->fl.fl_ip_dport) {  
  3. struct
  4. sizeof(fl));  
  5.  fl.fl_ip_sport = sport;  
  6.  fl.fl_ip_dport = dport;  
  7.  fl.proto = protocol;  
  8.  ip_rt_put(*rp);  
  9.  *rp = NULL;  
  10.  security_sk_classify_flow(sk, &fl);  
  11. return
  12. }  

      write_seq相當于第一次發送TCP封包的ISN,如果為0,則通過計算擷取初始值,否則延用上次的值。在擷取完源端口号,并查詢過路由表後,TCP正式發送SYN封包,注意在這之前TCP狀态已經更新成了TCP_SYN_SENT,而在函數最後才調用tcp_connect(sk)發送SYN封包,這中間是有時差的。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.  tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,  
  3.          inet->inet_daddr,  
  4.          inet->inet_sport,  
  5.          usin->sin_port);  
  6. inet->inet_id = tp->write_seq ^ jiffies;  
  7. err = tcp_connect(sk);  

tcp_connect() 發送SYN封包

      幾步重要的代碼如下,tcp_connect_init()中設定了tp->rcv_nxt=0,tcp_transmit_skb()負責發送封包,其中seq=tcb->seq=tp->write_seq,ack_seq=tp->rcv_nxt。

[cpp]

​​ view plain​​

​​ copy​​

  1. tcp_connect_init(sk);  
  2. tp->snd_nxt = tp->write_seq;  
  3. ……  
  4. tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);  

*收到服務端的SYN+ACK,發送ACK

tcp_rcv_synsent_state_process()

      此時已接收到對方的ACK,狀态變遷到TCP_ESTABLISHED。最後發送對方SYN的ACK封包。

[cpp]

​​ view plain​​

​​ copy​​

  1. tcp_set_state(sk, TCP_ESTABLISHED);  
  2. tcp_send_ack(sk);  

服務端流程

*bind() -> inet_bind()

      bind操作的主要作用是将建立的socket與給定的位址相綁定,這樣建立的服務才能公開的讓外部調用。當然對于socket伺服器的建立來說,這一步不是必須的,在listen()時如果沒有綁定位址,系統會選擇一個随機可用位址作為伺服器位址。

一個socket位址分為ip和port,inet->inet_saddr指派了傳入的ip,snum是傳入的port,對于端口,要檢查它是否已被占用,這是由sk->sk_prot->get_port()完成的(這個函數前面已經分析過,在傳入port時它檢查是否被占用;傳入port=0時它選擇未用的端口)。如果沒有被占用,inet->inet_sport被指派port,因為是服務監聽端,不需要遠端位址,inet_daddr和inet_dport都置0。

注意bind操作不會改變socket的狀态,仍為建立時的TCP_CLOSE。

[cpp]

​​ view plain​​

​​ copy​​

  1. snum = ntohs(addr->sin_port);  
  2. ……  
  3. inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;  
  4. if
  5.  inet->inet_saddr = inet->inet_rcv_saddr = 0;  
  6.  err = -EADDRINUSE;  
  7. goto
  8. }  
  9. ……  
  10. inet->inet_sport = htons(inet->inet_num);  
  11. inet->inet_daddr = 0;  
  12. inet->inet_dport = 0;  

listen() -> inet_listen()

      listen操作開始伺服器的監聽,此時服務就可以接受到外部連接配接了。在開始監聽前,要檢查狀态是否正确,sock->state==SS_UNCONNECTED確定仍是未連接配接的socket,sock->type==SOCK_STREAM確定是TCP協定,old_state確定此時狀态是TCP_CLOSE或TCP_LISTEN,在其它狀态下進行listen都是錯誤的。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2. goto
  3. old_state = sk->sk_state;  
  4. if
  5. goto

      如果已是TCP_LISTEN态,則直接跳過,不用再執行listen了,而隻是重新設定listen隊列長度sk_max_ack_backlog,改變listen隊列長也是多次執行listen的作用。如果還沒有執行listen,則還要調用inet_csk_listen_start()開始監聽。

      inet_csk_listen_start()變遷狀态至TCP_LISTEN,配置設定監聽隊列,如果之前沒有調用bind()綁定位址,則這裡會配置設定一個随機位址。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.  err = inet_csk_listen_start(sk, backlog);  
  3. if
  4. goto
  5. }  
  6. sk->sk_max_ack_backlog = backlog;  

accept()

accept() -> sys_accept4() -> inet_accept() -> inet_csk_accept()

      accept()實際要做的事件并不多,它的作用是傳回一個已經建立連接配接的socket(即經過了三次握手),這個過程是異步的,accept()并不親自去處理三次握手過程,而隻是監聽icsk_accept_queue隊列,當有socket經過了三次握手,它就會被加到icsk_accept_queue中,是以accept要做的就是等待隊列中插入socket,然後被喚醒并傳回這個socket。而三次握手的過程完全是協定棧本身去完成的。換句話說,協定棧相當于寫者,将socket寫入隊列,accept()相當于讀者,将socket從隊列讀出。這個過程從listen就已開始,是以即使不調用accept(),客戶仍可以和伺服器建立連接配接,但由于沒有處理,隊列很快會被占滿。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2. long
  3.  ……  
  4.  error = inet_csk_wait_for_connect(sk, timeo);  
  5.  ……  
  6. }  
  7. newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);  

      協定棧向隊列中加入socket的過程就是完成三次握手的過程,用戶端通過向已知的listen fd發起連接配接請求,對于到來的每個連接配接,都會建立一個新的sock,當它經曆了TCP_SYN_RCV -> TCP_ESTABLISHED後,就會被添加到icsk_accept_queue中,而監聽的socket狀态始終為TCP_LISTEN,保證連接配接的建立不會影響socket的接收。

*接收用戶端發來的SYN,發送SYN+ACK

tcp_v4_do_rcv()

      tcp_v4_do_rcv()是TCP子產品接收的入口函數,用戶端發起請求的對象是listen fd,是以sk->sk_state == TCP_LISTEN,調用tcp_v4_hnd_req()來檢查是否處于半連接配接,隻要三次握手沒有完成,這樣的連接配接就稱為半連接配接,具體而言就是收到了SYN,但還沒有收到ACK的連接配接,是以對于這個查找函數,如果是SYN封包,則會傳回listen的socket(連接配接尚未建立);如果是ACK封包,則會傳回SYN封包進行中插入的半連接配接socket。其中存儲這些半連接配接的資料結構是syn_table,它在listen()調用時被建立,大小由sys_ctl_max_syn_backlog和listen()傳入的隊列長度決定。

此時是收到SYN封包,tcp_v4_hnd_req()傳回的仍是sk,調用tcp_rcv_state_process()來接收SYN封包,并發送SYN+ACK封包,同時向syn_table中插入一項表明此次連接配接的sk。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2. struct
  3. if
  4. goto
  5. if
  6. if
  7.    rsk = nsk;  
  8. goto
  9.   }  
  10. return
  11.  }  
  12. }  
  13. TCP_CHECK_TIMER(sk);  
  14. if
  15.  rsk = sk;  
  16. goto
  17. }  

      tcp_rcv_state_process()處理各個狀态上socket的情況。下面是處于TCP_LISTEN的代碼段,處于TCP_LISTEN的socket不會再向其它狀态變遷,它負責監聽,并在連接配接建立時建立新的socket。實際上,當收到第一個SYN封包時,會執行這段代碼,conn_request() => tcp_v4_conn_request。

[cpp]

​​ view plain​​

​​ copy​​

  1. case
  2. ……  
  3. if
  4. if
  5. return
  6.   kfree_skb(skb);  
  7. return
  8.  }  

      tcp_v4_conn_request()中注意兩個函數就可以了:tcp_v4_send_synack()向用戶端發送了SYN+ACK封包,inet_csk_reqsk_queue_hash_add()将sk添加到了syn_table中,填充了該用戶端相關的資訊。這樣,再次收到用戶端的ACK封包時,就可以在syn_table中找到相應項了。

[cpp]

​​ view plain​​

​​ copy​​

  1. if (tcp_v4_send_synack(sk, dst, req, (struct
  2. goto
  3. inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);  

*接收用戶端發來的ACK

tcp_v4_do_rcv()

      過程與收到SYN封包相同,不同點在于syn_table中已經插入了有關該連接配接的條目,tcp_v4_hnd_req()會傳回一個新的sock: nsk,然後會調用tcp_child_process()來進行處理。在tcp_v4_hnd_req()中會建立新的sock,下面詳細看下這個函數。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2. struct
  3. if
  4. goto
  5. if
  6. if
  7.    rsk = nsk;  
  8. goto
  9.   }  
  10. return
  11.  }  
  12. }  

tcp_v4_hnd_req()

      之前已經分析過,inet_csk_search_req()會在syn_table中找到req,此時進入tcp_check_req()

[cpp]

​​ view plain​​

​​ copy​​

  1. struct
  2. if
  3. return

tcp_check_req()

      syn_recv_sock() -> tcp_v4_syn_recv_sock()會建立一個新的sock并傳回,建立的sock狀态被直接設定為TCP_SYN_RECV,然後因為此時socket已經建立,将它添加到icsk_accept_queue中。

      狀态TCP_SYN_RECV的設定可能比較奇怪,按照TCP的狀态轉移圖,在服務端收到SYN封包後變遷為TCP_SYN_RECV,但看到在實作中收到ACK後才有了狀态TCP_SYN_RECV,并且馬上會變為TCP_ESTABLISHED,是以這個狀态變得無足輕重。這樣做的原因是listen和accept傳回的socket是不同的,而隻有真正連接配接建立時才會建立這個新的socket,在收到SYN封包時新的socket還沒有建立,就無從談狀态變遷了。這裡同樣是一個平衡的存在,你也可以在收到SYN時建立一個新的socket,代價就是無用的socket大大增加了。

[cpp]

​​ view plain​​

​​ copy​​

  1. child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);  
  2. if
  3. goto
  4. inet_csk_reqsk_queue_unlink(sk, req, prev);  
  5. inet_csk_reqsk_queue_removed(sk, req);  
  6. inet_csk_reqsk_queue_add(sk, req, child);  

tcp_child_process()

      如果此時sock: child被使用者程序鎖住了,那麼就先添加到backlog中__sk_add_backlog(),待解鎖時再處理backlog上的sock;如果此時沒有被鎖住,則先調用tcp_rcv_state_process()進行處理,處理完後,如果child狀态到達TCP_ESTABLISHED,則表明其已就緒,調用sk_data_ready()喚醒等待在isck_accept_queue上的函數accept()。

[cpp]

​​ view plain​​

​​ copy​​

  1. if
  2.  ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb), skb->len);  
  3. if
  4.   parent->sk_data_ready(parent, 0);  
  5. } else
  6.  __sk_add_backlog(child, skb);  
  7. }  

      tcp_rcv_state_process()處理各個狀态上socket的情況。下面是處于TCP_SYN_RECV的代碼段,注意此時傳入函數的sk已經是新建立的sock了(在tcp_v4_hnd_req()中),并且狀态是TCP_SYN_RECV,而不再是listen socket,在收到ACK後,sk狀态變遷為TCP_ESTABLISHED,而在tcp_v4_hnd_req()中也已将sk插入到了icsk_accept_queue上,此時它就已經完全就緒了,回到tcp_child_process()便可執行sk_data_ready()。

[cpp]

​​ view plain​​

​​ copy​​

  1. case
  2. if
  3.   ……  
  4.   tcp_set_state(sk, TCP_ESTABLISHED);  
  5.   sk->sk_state_change(sk);  
  6.   ……  
  7.   tp->snd_una = TCP_SKB_CB(skb)->ack_seq;  
  8.   tp->snd_wnd = ntohs(th->window) << tp->rx_opt.snd_wscale;  
  9.   tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);   
  10.   ……  
  11. }  

      最後總結三次握手的過程

Linux核心分析 - 網絡[十六]:TCP三向交握
  • 本文已收錄于以下專欄:
  • ​​Linux核心協定棧​​

繼續閱讀