<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們在linux上伺服器起了一個serversocket,並且設定了backlog為2,並沒有讓serversock.accept()
在使用者端上,我們一個一個的啟動了連線socket, 當連線數目超過3的時候,使用者端依然可以繼續新建連線。
說起backlog, 都會想起socket程式設計中的listen backlog 引數,而這個backlog 是linux核心中處理的backlog麼?
int listen(int sockfd, int backlog)
listen 中的backlog解釋
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.
實際上在linux核心2.2版本以後,backlog引數控制的是已經握手成功的還在accept queue的大小。
struct request_sock_queue { /*Points to the request_sock accept queue, when after 3 handshake will add the request_sock from syn_table to here*/ struct request_sock *rskq_accept_head; struct request_sock *rskq_accept_tail; rwlock_t syn_wait_lock; u8 rskq_defer_accept; /* 3 bytes hole, try to pack */ struct listen_sock *listen_opt; }; struct listen_sock { u8 max_qlen_log; /*2^max_qlen_log is the length of the accpet queue, max of max_qlen_log is 10. (2^10=1024)*/ /* 3 bytes hole, try to use */ int qlen; /* qlen is the current length of the accpet queue*/ int qlen_young; int clock_hand; u32 hash_rnd; u32 nr_table_entries; /*nr_table_entries is the number of the syn_table,max is 512*/ struct request_sock *syn_table[0]; }; struct request_sock { struct request_sock *dl_next; /* Must be first member! */ u16 mss; u8 retrans; u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */ /* The following two fields can be easily recomputed I think -AK */ u32 window_clamp; /* window clamp at creation time */ u32 rcv_wnd; /* rcv_wnd offered first time */ u32 ts_recent; unsigned long expires; const struct request_sock_ops *rsk_ops; struct sock *sk; u32 secid; u32 peer_secid; }; struct sock{ unsigned short sk_ack_backlog; unsigned short sk_max_ack_backlog; }
首先在linux裡可以簡單的認為有2個佇列,一個就是在握手過程中的佇列,而另一個就是握手成功的佇列
簡單的描述一下3個結構體
request_sock
是每一個client的連線(無論是握手成功,還是不成功) 裡面的 expires代表的是這個request在佇列裡的存活時間,而 *sk 就是連線成功的socket的數目
request_sock_queue
rskq_accept_head 佇列,也就是握手成功的佇列,*listen_opt 是指listen過程中的sock
listen_sock
*syn_table 是指握手沒有成功的佇列,而qlen,qlen_young 分別指的是佇列的長度和佇列新成員的個數
在結構體中,我們已經清楚的看到了一個listen_sock中的syn_table,另一個是request_sock_queue中的rskq_accept_head,這就是我們剛才說的兩個佇列,一個是為正在握手的佇列,另一個是已經握手成功的佇列。
我們在上面都看到了結構體中只是看到了未握手的佇列的長度,並沒有看到握手的佇列長度統計,實際上握手成功的佇列長度是在sock 結構中
sock
當握手成功後每一個client就是一個sock, sk_ack_backlog 是佇列長度,而sk_max_ack_backlog是指最大的佇列長度
在這裡我們會有疑問,難道是沒個連線上的 sock都會保留佇列的長度麼?實際上在此時的sock 代表的是server端listen 的sock而不是使用者端的sock,也就是在握手沒有成功的過程中,在linux使用的sock都是server的listen的sock, 對使用者端只是保留成request_sock
收到使用者端的syn請求 ->將這個請求放入syn_table中去->伺服器端回覆syn-ack->收到使用者端的ack->放入accept queue中
我們把整個過程分為5個部分,其中將請求放入syn_table和accept queue中的過程也是backlog相關的,在下面我們會詳細闡述。
我們先簡單的描述一下幾個tcp的操作函數,下面針對的也是ip4協定的
const struct inet_connection_sock_af_ops ipv4_specific = { .queue_xmit = ip_queue_xmit, .send_check = tcp_v4_send_check, .rebuild_header = inet_sk_rebuild_header, .conn_request = tcp_v4_conn_request, .syn_recv_sock = tcp_v4_syn_recv_sock, .remember_stamp = tcp_v4_remember_stamp, .net_header_len = sizeof(struct iphdr), .setsockopt = ip_setsockopt, .getsockopt = ip_getsockopt, .addr2sockaddr = inet_csk_addr2sockaddr, .sockaddr_len = sizeof(struct sockaddr_in), .bind_conflict = inet_csk_bind_conflict, #ifdef CONFIG_COMPAT .compat_setsockopt = compat_ip_setsockopt, .compat_getsockopt = compat_ip_getsockopt, #endif };
在剛才所說的兩個步驟,也就是結構體中的 conn_request 和 syn_recv_sock, 所對應的函數是 tcp_v4_conn_request 和 tcp_v4_syn_recv_sock
我們所重點關注的主要是方法中的drop邏輯
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) { /* Never answer to SYNs send to broadcast or multicast */ if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) goto drop; /* TW buckets are converted to open requests without * limitations, they conserve resources and peer is * evidently real one. */ if (inet_csk_reqsk_queue_is_full(sk) && !isn) { #ifdef CONFIG_SYN_COOKIES if (sysctl_tcp_syncookies) { want_cookie = 1; } else #endif goto drop; } /* 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) goto drop; .... }
1. inet_csk_reqsk_queue_is_full(sk)
判斷的是 queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
這裡有個 qlen 代表的是listen_opt的 syn_table的長度,那什麼是max_qlen_log呢?
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog); nr_table_entries = max_t(u32, nr_table_entries, 8); nr_table_entries = roundup_pow_of_two(nr_table_entries + 1); for (lopt->max_qlen_log = 3; (1 << lopt->max_qlen_log) < nr_table_entries; lopt->max_qlen_log++);
也就是max_qlen 是listen 傳入的backlog和sysctl_max_syn_backlog最小值,並且一定大於16 , roudup_pow_of_two 代表著找最靠近nr_table_entries+1的2的倍數 sysctl_max_syn_backlog 就是我們熟悉的
/proc/sys/net/ipv4/tcp_max_syn_backlog
我們看一下listen 函數在kernel的實現
SYSCALL_DEFINE2(listen, int, fd, int, backlog) { struct socket *sock; int err, fput_needed; int somaxconn; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (sock) { <span style="color: rgb(255, 102, 102);">somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; if ((unsigned)backlog > somaxconn) backlog = somaxconn;</span> err = security_socket_listen(sock, backlog); if (!err) err = sock->ops->listen(sock, backlog); fput_light(sock->file, fput_needed); } return err; }
我們清楚的看到backlog 並不是按照你呼叫listen的所設定的backlog大小,實際上取的是backlog和somaxconn的最小值
somaxconn的值定義在
/proc/sys/net/core/somaxconn
2.sk_acceptq_is_full
static inline int sk_acceptq_is_full(struct sock *sk) { return sk->sk_ack_backlog > sk->sk_max_ack_backlog; } int inet_listen(struct socket *sock, int backlog) { sk->sk_max_ack_backlog = backlog; }
就是等於我們剛才在前面部分看到的listen中的值
3.inet_csk_reqsk_queue_young
在判斷sk_acceptq_is_full 的情況下,同是也要求了判斷inet_csk_reqsk_queue_young>1,也就是剛才的結構體listen_sock的qlen_young
qlen_young 是對syn_table的計數,進入 syn_table 加1,出了syn_table -1
有的人可能會有疑問了
如果accept queue滿了,那麼qlen_young不就是一直增加,而新來的使用者端都會被條件if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) 而drop syn的ack包,那麼使用者端會出現connected timeout, 而實際上你在測試linux的環境中會發現並沒有出現這樣的情況。
實際上linux在server起socket的時候會呼叫tcp_keepalive_timer啟動tcp_synack_timer,會呼叫函數inet_csk_reqsk_queue_prune
if (sk->sk_state == TCP_LISTEN) { tcp_synack_timer(sk); goto out; }
static void tcp_synack_timer(struct sock *sk) { inet_csk_reqsk_queue_prune(sk, TCP_SYNQ_INTERVAL, TCP_TIMEOUT_INIT, TCP_RTO_MAX); }
而inet_csk_reqsk_queue_prune會在去檢查syn的table, 而刪除一些這個request 過期後並且完成retry 的syn ack包的請求
為了提高inet_csk_reqsk_queue_prune的效率,在request_sock 里加入了 expires(才前面的結構體中已經提到過) , 這個expires初始值是hardcode的3HZ 時間, inet_csk_reqsk_queue_prune會輪訓syn_table裡的已經exprie request, 發現如果還沒有到到retry的次數,那麼會增加expire的時間直到重試結束,而expire的時間為剩餘retry 次數*3HZ ,並且不大於120HZ
關於retry, retry的引數可以通過設定
/proc/sys/net/ipv4/tcp_syn_retries
當然你可以通過設定
/proc/sys/net/ipv4/tcp_abort_on_overflow 為1 不允許syn ack 重試
因為被inet_csk_reqsk_queue_prune函數清除了syn_table,在沒有並行的前提下基本上不會出現inet_csk_reqsk_queue_young>1的情況,也就是說不會出現drop sync的情況,在使用者端表現,不會出現connect timeout 的情況(這裡的實現linux和mac的實現有很大的不同)而剛開始的問題也能得到合理的解釋了
通過函數tcp_v4_conn_request的分析,在linux的設計初衷是盡力的允許新的連線握手,而期望伺服器端能更快的響應accept.
我們也許會問,剛才的伺服器syn ack回去後,如果使用者端也回覆了ack的話,而此時accept的queue滿了,將會如何處理
我們回到前面提到的步驟,處理使用者端的ack 函數也就是
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct dst_entry *dst) { struct inet_request_sock *ireq; struct inet_sock *newinet; struct tcp_sock *newtp; struct sock *newsk; #ifdef CONFIG_TCP_MD5SIG struct tcp_md5sig_key *key; #endif if (sk_acceptq_is_full(sk)) goto exit_overflow; if (!dst && (dst = inet_csk_route_req(sk, req)) == NULL) goto exit; newsk = tcp_create_openreq_child(sk, req, skb); if (!newsk) goto exit; newsk->sk_gso_type = SKB_GSO_TCPV4; sk_setup_caps(newsk, dst); newtp = tcp_sk(newsk); newinet = inet_sk(newsk); ireq = inet_rsk(req); newinet->inet_daddr = ireq->rmt_addr; newinet->inet_rcv_saddr = ireq->loc_addr; newinet->inet_saddr = ireq->loc_addr; newinet->opt = ireq->opt; ireq->opt = NULL; newinet->mc_index = inet_iif(skb); newinet->mc_ttl = ip_hdr(skb)->ttl; inet_csk(newsk)->icsk_ext_hdr_len = 0; if (newinet->opt) inet_csk(newsk)->icsk_ext_hdr_len = newinet->opt->optlen; newinet->inet_id = newtp->write_seq ^ jiffies; tcp_mtup_init(newsk); tcp_sync_mss(newsk, dst_mtu(dst)); newtp->advmss = dst_metric(dst, RTAX_ADVMSS); if (tcp_sk(sk)->rx_opt.user_mss && tcp_sk(sk)->rx_opt.user_mss < newtp->advmss) newtp->advmss = tcp_sk(sk)->rx_opt.user_mss; tcp_initialize_rcv_mss(newsk); #ifdef CONFIG_TCP_MD5SIG /* Copy over the MD5 key from the original socket */ key = tcp_v4_md5_do_lookup(sk, newinet->inet_daddr); if (key != NULL) { /* * We're using one, so create a matching key * on the newsk structure. If we fail to get * memory, then we end up not copying the key * across. Shucks. */ char *newkey = kmemdup(key->key, key->keylen, GFP_ATOMIC); if (newkey != NULL) tcp_v4_md5_do_add(newsk, newinet->inet_daddr, newkey, key->keylen); newsk->sk_route_caps &= ~NETIF_F_GSO_MASK; } #endif __inet_hash_nolisten(newsk, NULL); __inet_inherit_port(sk, newsk); return newsk; exit_overflow: NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); exit: NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS); dst_release(dst); return NULL; }
我們看到了熟悉的函數 sk_acceptq_is_full, 而在此時在無函數inet_csk_reqsk_queue_young>1來保護,也就是說在此時如果發現queue是滿的,將直接丟棄只是統計了引數LINUX_MIB_LISTENOVERFLOWS,LINUX_MIB_LISTENDROPS而這些引數的值可以通過
netstat -s
來檢視到
在函數tcp_v4_syn_recv_sock中我們看到tcp_create_openreq_child,此時才clone出一個新的socket ,也就是隻有通過了3次握手後,linux才會產生新的socket, 而在3次握手中所傳的socket 實際上是server的listen的 socket, 那也就是說這個socket 只有一個狀態TCP_LISTEN
netstat的狀態
通過在tcp_rcv_state_process可以置socket 的狀態,而我們通常使用netstat 中看到這些socket的狀態
case TCP_SYN_RECV: if (acceptable) { tp->copied_seq = tp->rcv_nxt; smp_mb(); tcp_set_state(sk, TCP_ESTABLISHED);
我們看到從 SYN_RECV的狀態直接設定成ESTABLISHED,也就是當server收到client的ack回來,狀態置為 TCP_SYN_RECV,而馬上進入tcp_rcv_state_process函數置為狀態ESTABLISHED,基本沒有TCP_SYN_RECV 的狀態期,但我們通過netstat 的使用,還是會發現有部分socket 還是會處於SYN_RECV狀態,實際上這通常是在syn_table的request, 為了顯示還沒有通過三次握手的連線的狀態,這時候request 還在syn table裡,並且還沒有屬於自己的socket物件,linux 把這些資訊寫到了
/proc/net/tcp
而在TCP_SEQ_STATE_OPENREQ 的情況下(就是 syn synack ack)的3個狀態下都顯示成TCP_SYN_RECV
static void get_openreq4(struct sock *sk, struct request_sock *req, struct seq_file *f, int i, int uid, int *len) { const struct inet_request_sock *ireq = inet_rsk(req); int ttd = req->expires - jiffies; seq_printf(f, "%4d: %08X:%04X %08X:%04X" " %02X %08X:%08X %02X:%08lX %08X %5d %8d %u %d %p%n", i, ireq->loc_addr, ntohs(inet_sk(sk)->inet_sport), ireq->rmt_addr, ntohs(ireq->rmt_port), TCP_SYN_RECV, 0, 0, /* could print option size, but that is af dependent. */ 1, /* timers active (only the expire timer) */ jiffies_to_clock_t(ttd), req->retrans, uid, 0, /* non standard timer */ 0, /* open_requests have no inode */ atomic_read(&sk->sk_refcnt), req, len); }
而對ESTABLISHED狀態,並不需要server.accept,只要在accept queue裡就已經變成狀態ESTABLISHED
到此這篇關於詳解linux裡的backlog引數的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45