tcp連接listen的backlog剖析




           

TCP連接中,最重要的是連接TCP連接上,兩個方向之間的各個狀態及各個系統調用與狀態之間的關係。往往可以以兩種圖表示,第一種是狀態轉換圖,第二種是連接時序圖。如下:

狀態圖:

時序圖:

 

 

 

 

可見,listen狀態是服務器接收連接建立的必經之路。調用listen後,服務器即進入了LISTEN狀態。

listen爲:

int listen(int sockfd, int backlog);

其backlog是一個建議值,用於指定內部的隊列大小,以控制同時建立的連接請求數量。

 

針對控制連接這個需求,有兩種方法實現這個backlog:

  1. 單一隊列來控制連接。隊列中既包含了SYN_RCVD的狀態,也包含了ESTABLISHED狀態。accept只處理後面一種狀態。如果三次握手中的ACK到來,則會在隊列中直接改其狀態。顯然,這時backlog爲這一隊列的長度。

  2. 兩個單獨隊列來控制。兩種狀態分別實現單獨的隊列。顯然這種情況下,兩個隊列都必須有明確的大小限制,backlog只能限制其中一個。

 

在UNP中,這個值是這樣描述的:

 

內核維護兩個隊列,一個是未完成隊列,一個是已建立連接的隊列。在第一個sync到達時,先將連接塞入第一個隊列,再回復ACK+SYNC。此時,連接狀態變爲SYN_RCVD;

第二個是完成隊列。客戶端的ACK上來後,對應的連接移入完成隊列。此狀態的連接會被accept系統調用返回,狀態變爲ESTABLISHED。

 

另一方面,如果請求上來時隊列已滿,則TCP忽略之。客戶端來重試。

 

可見,其實現實際是第一種單隊列的形式,即backlog控制兩種狀態連接數的總和。

 

而對於linux:

man listen可見:

The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length forcompletely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog.

 

可見,與UNP書中描述並不一致。是上述的第二種實現方法:兩個隊列,其中未完成隊列長度限制以內核參數來表示,而已完成隊列纔是backlog。

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

2048

 

具體代碼:

TCP收到最終ACK後,重建一個sock:

 

 

tcp_v4_syn_recv_sock函數中:

複製代碼

1     if (sk_acceptq_is_full(sk))2         goto exit_overflow;3  4     newsk = tcp_create_openreq_child(sk, req, skb);5     if (!newsk)6         goto exit_nonewsk;7

複製代碼

 

可見,在新生成一個socket時,先判斷是否full。

 

複製代碼

static inline bool sk_acceptq_is_full(const struct sock *sk)
{    return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
} 
 
/*
 *    Move a socket into listening state. */int inet_listen(struct socket *sock, int backlog)中:
    sk->sk_max_ack_backlog = backlog;

複製代碼

backlog傳給了sk_max_ack_backlog。可見,此backlog用作判斷complete queue。

 

在往外層,調用處:

複製代碼

 1     child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, 2                              req, &own_req); 3     if (!child) 4         goto listen_overflow; 5   6     sock_rps_save_rxhash(child, skb); 7     tcp_synack_rtt_meas(child, req); 8     return inet_csk_complete_hashdance(sk, child, req, own_req); 9  10 listen_overflow:11     if (!sysctl_tcp_abort_on_overflow) {12         inet_rsk(req)->acked = 1;13         return NULL;14     }15 embryonic_reset:16     if (!(flg & TCP_FLAG_RST)) {17         /* Received a bad SYN pkt - for TFO We try not to reset18          * the local connection unless it's really necessary to19          * avoid becoming vulnerable to outside attack aiming at20          * resetting legit local connections.21          */22         req->rsk_ops->send_reset(sk, skb);23     } else if (fastopen) { /* received a valid RST pkt */24         reqsk_fastopen_remove(sk, req, true);25         tcp_reset(sk);26     }27     if (!fastopen) {28         inet_csk_reqsk_queue_drop(sk, req);29         NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);30     }31     return NULL;32

複製代碼

 

可見,如果backlog滿了,需要關注sysctl_tcp_abort_on_overflow,而些值是/proc下配置。

如果沒有配,則啥都沒幹,直接忽略,這會使server端一定時間段重發SYNC/ACK。如果配置了,則走到下面發RST的流程。

 

另一點,客戶端在發送完最後一個ACK,則進入了ESTABLISHED狀態,但如上所述,這個ACK可能因爲server端的backlog問題使得被忽略。但客戶端並不知道,因爲它是ESTABLISHED,它這段時間可以發數據!而後續重發來的SYNC/ACK,會使得客戶端重發已經發出的數據,贊成帶寬浪費。而TCP slow start會緩減這種情況。

 

 

backlog已滿的情況下,不僅對於complete queue,對於ack queue也會有處理,適當限速(?)以丟掉一部分的sync,避免太擁堵。在函數中:

複製代碼

int tcp_conn_request(struct request_sock_ops *rsk_ops,             const struct tcp_request_sock_ops *af_ops,             struct sock *sk, struct sk_buff *skb)
 
。。。    /* Accept backlog is full. If we have already queued enough
     * of warm entries in syn queue, drop request. It is better than
     * clogging syn queue with openreqs with exponentially increasing
     * timeout.     */
    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;
    }

複製代碼

 

總結:

  1. 服務端的連接建立有兩個地方需要排隊,一是sync包上來時,另一個是ACK上來時。對於linux來說,前者長度由配置項決定,機器上是2048;後者由listen帶入的backlog指定;

  2. 如果backlog對應的隊列滿後,判斷配置項,如果配了,rst,否則直接忽略,等待超時重發;

  3. 客戶端只要發送了ACK後,即進入established狀態,而服務端則要accept後。兩者不對等,且客戶端建立後立即可發送數據。

  4. sync隊列也會對complete隊列是否滿進行參考。如果滿了,限制收包速度,適當丟棄一些包。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章