色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

詳解linux里的backlog參數(shù)

瀏覽:241日期:2023-03-07 14:40:03

問(wèn)題

我們?cè)趌inux上服務(wù)器起了一個(gè)serversocket,并且設(shè)置了backlog為2,并沒(méi)有讓serversock.accept()

在客戶端上,我們一個(gè)一個(gè)的啟動(dòng)了連接socket, 當(dāng)連接數(shù)目超過(guò)3的時(shí)候,客戶端依然可以繼續(xù)新建連接。

什么是backlog

說(shuō)起backlog, 都會(huì)想起socket編程中的listen backlog 參數(shù),而這個(gè)backlog 是linux內(nèi)核中處理的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.

實(shí)際上在linux內(nèi)核2.2版本以后,backlog參數(shù)控制的是已經(jīng)握手成功的還在accept queue的大小。

握手過(guò)程中的結(jié)構(gòu)體

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_tsyn_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里可以簡(jiǎn)單的認(rèn)為有2個(gè)隊(duì)列,一個(gè)就是在握手過(guò)程中的隊(duì)列,而另一個(gè)就是握手成功的隊(duì)列

簡(jiǎn)單的描述一下3個(gè)結(jié)構(gòu)體

request_sock

 是每一個(gè)client的連接(無(wú)論是握手成功,還是不成功) 里面的 expires代表的是這個(gè)request在隊(duì)列里的存活時(shí)間,而 *sk 就是連接成功的socket的數(shù)目

request_sock_queue

rskq_accept_head 隊(duì)列,也就是握手成功的隊(duì)列,*listen_opt 是指listen過(guò)程中的sock

listen_sock

*syn_table 是指握手沒(méi)有成功的隊(duì)列,而qlen,qlen_young 分別指的是隊(duì)列的長(zhǎng)度和隊(duì)列新成員的個(gè)數(shù)

在結(jié)構(gòu)體中,我們已經(jīng)清楚的看到了一個(gè)listen_sock中的syn_table,另一個(gè)是request_sock_queue中的rskq_accept_head,這就是我們剛才說(shuō)的兩個(gè)隊(duì)列,一個(gè)是為正在握手的隊(duì)列,另一個(gè)是已經(jīng)握手成功的隊(duì)列。

我們?cè)谏厦娑伎吹搅私Y(jié)構(gòu)體中只是看到了未握手的隊(duì)列的長(zhǎng)度,并沒(méi)有看到握手的隊(duì)列長(zhǎng)度統(tǒng)計(jì),實(shí)際上握手成功的隊(duì)列長(zhǎng)度是在sock 結(jié)構(gòu)中

sock

當(dāng)握手成功后每一個(gè)client就是一個(gè)sock, sk_ack_backlog 是隊(duì)列長(zhǎng)度,而sk_max_ack_backlog是指最大的隊(duì)列長(zhǎng)度

在這里我們會(huì)有疑問(wèn),難道是沒(méi)個(gè)連接上的 sock都會(huì)保留隊(duì)列的長(zhǎng)度么?實(shí)際上在此時(shí)的sock 代表的是server端listen 的sock而不是客戶端的sock,也就是在握手沒(méi)有成功的過(guò)程中,在linux使用的sock都是server的listen的sock, 對(duì)客戶端只是保留成request_sock

TCP握手的幾個(gè)階段

收到客戶端的syn請(qǐng)求 ->將這個(gè)請(qǐng)求放入syn_table中去->服務(wù)器端回復(fù)syn-ack->收到客戶端的ack->放入accept queue中

我們把整個(gè)過(guò)程分為5個(gè)部分,其中將請(qǐng)求放入syn_table和accept queue中的過(guò)程也是backlog相關(guān)的,在下面我們會(huì)詳細(xì)闡述。

我們先簡(jiǎn)單的描述一下幾個(gè)tcp的操作函數(shù),下面針對(duì)的也是ip4協(xié)議的

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
};

在剛才所說(shuō)的兩個(gè)步驟,也就是結(jié)構(gòu)體中的 conn_request 和 syn_recv_sock,  所對(duì)應(yīng)的函數(shù)是 tcp_v4_conn_request 和 tcp_v4_syn_recv_sock

我們所重點(diǎn)關(guān)注的主要是方法中的drop邏輯

tcp_v4_conn_request 函數(shù)

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;

這里有個(gè) qlen 代表的是listen_opt的 syn_table的長(zhǎng)度,那什么是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的倍數(shù) sysctl_max_syn_backlog 就是我們熟悉的

/proc/sys/net/ipv4/tcp_max_syn_backlog

我們看一下listen 函數(shù)在kernel的實(shí)現(xiàn)

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>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 并不是按照你調(diào)用listen的所設(shè)置的backlog大小,實(shí)際上取的是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,也就是剛才的結(jié)構(gòu)體listen_sock的qlen_young

qlen_young 是對(duì)syn_table的計(jì)數(shù),進(jìn)入 syn_table 加1,出了syn_table  -1

有的人可能會(huì)有疑問(wèn)了

如果accept queue滿了,那么qlen_young不就是一直增加,而新來(lái)的客戶端都會(huì)被條件if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) 而drop syn的ack包,那么客戶端會(huì)出現(xiàn)connected timeout, 而實(shí)際上你在測(cè)試linux的環(huán)境中會(huì)發(fā)現(xiàn)并沒(méi)有出現(xiàn)這樣的情況。

實(shí)際上linux在server起socket的時(shí)候會(huì)調(diào)用tcp_keepalive_timer啟動(dòng)tcp_synack_timer,會(huì)調(diào)用函數(shù)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會(huì)在去檢查syn的table, 而刪除一些這個(gè)request 過(guò)期后并且完成retry 的syn ack包的請(qǐng)求

為了提高inet_csk_reqsk_queue_prune的效率,在request_sock 里加入了 expires(才前面的結(jié)構(gòu)體中已經(jīng)提到過(guò)) , 這個(gè)expires初始值是hardcode的3HZ 時(shí)間, inet_csk_reqsk_queue_prune會(huì)輪訓(xùn)syn_table里的已經(jīng)exprie request, 發(fā)現(xiàn)如果還沒(méi)有到到retry的次數(shù),那么會(huì)增加expire的時(shí)間直到重試結(jié)束,而expire的時(shí)間為剩余retry 次數(shù)*3HZ ,并且不大于120HZ

關(guān)于retry, retry的參數(shù)可以通過(guò)設(shè)置 

/proc/sys/net/ipv4/tcp_syn_retries

當(dāng)然你可以通過(guò)設(shè)置

/proc/sys/net/ipv4/tcp_abort_on_overflow 為1 不允許syn ack 重試

因?yàn)楸籭net_csk_reqsk_queue_prune函數(shù)清除了syn_table,在沒(méi)有并發(fā)的前提下基本上不會(huì)出現(xiàn)inet_csk_reqsk_queue_young>1的情況,也就是說(shuō)不會(huì)出現(xiàn)drop sync的情況,在客戶端表現(xiàn),不會(huì)出現(xiàn)connect timeout 的情況(這里的實(shí)現(xiàn)linux和mac的實(shí)現(xiàn)有很大的不同)而剛開(kāi)始的問(wèn)題也能得到合理的解釋了

通過(guò)函數(shù)tcp_v4_conn_request的分析,在linux的設(shè)計(jì)初衷是盡力的允許新的連接握手,而期望服務(wù)器端能更快的響應(yīng)accept.

我們也許會(huì)問(wèn),剛才的服務(wù)器syn ack回去后,如果客戶端也回復(fù)了ack的話,而此時(shí)accept的queue滿了,將會(huì)如何處理

我們回到前面提到的步驟,處理客戶端的ack 函數(shù)也就是

tcp_v4_syn_recv_sock 函數(shù)

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;
}

我們看到了熟悉的函數(shù) sk_acceptq_is_full, 而在此時(shí)在無(wú)函數(shù)inet_csk_reqsk_queue_young>1來(lái)保護(hù),也就是說(shuō)在此時(shí)如果發(fā)現(xiàn)queue是滿的,將直接丟棄只是統(tǒng)計(jì)了參數(shù)LINUX_MIB_LISTENOVERFLOWSLINUX_MIB_LISTENDROPS而這些參數(shù)的值可以通過(guò)

netstat -s 來(lái)查看到

在函數(shù)tcp_v4_syn_recv_sock中我們看到tcp_create_openreq_child,此時(shí)才clone出一個(gè)新的socket ,也就是只有通過(guò)了3次握手后,linux才會(huì)產(chǎn)生新的socket, 而在3次握手中所傳的socket 實(shí)際上是server的listen的 socket, 那也就是說(shuō)這個(gè)socket 只有一個(gè)狀態(tài)TCP_LISTEN

netstat的狀態(tài)

通過(guò)在tcp_rcv_state_process可以置socket 的狀態(tài),而我們通常使用netstat 中看到這些socket的狀態(tài)

case TCP_SYN_RECV:
			if (acceptable) {
				tp->copied_seq = tp->rcv_nxt;
				smp_mb();
				tcp_set_state(sk, TCP_ESTABLISHED);

我們看到從 SYN_RECV的狀態(tài)直接設(shè)置成ESTABLISHED,也就是當(dāng)server收到client的ack回來(lái),狀態(tài)置為 TCP_SYN_RECV,而馬上進(jìn)入tcp_rcv_state_process函數(shù)置為狀態(tài)ESTABLISHED,基本沒(méi)有TCP_SYN_RECV 的狀態(tài)期,但我們通過(guò)netstat  的使用,還是會(huì)發(fā)現(xiàn)有部分socket 還是會(huì)處于SYN_RECV狀態(tài),實(shí)際上這通常是在syn_table的request, 為了顯示還沒(méi)有通過(guò)三次握手的連接的狀態(tài),這時(shí)候request 還在syn table里,并且還沒(méi)有屬于自己的socket對(duì)象,linux 把這些信息寫(xiě)到了

/proc/net/tcp

而在TCP_SEQ_STATE_OPENREQ 的情況下(就是 syn synack ack)的3個(gè)狀態(tài)下都顯示成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);
}

而對(duì)ESTABLISHED狀態(tài),并不需要server.accept,只要在accept queue里就已經(jīng)變成狀態(tài)ESTABLISHED

到此這篇關(guān)于詳解linux里的backlog參數(shù)的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持。

標(biāo)簽: Linux Apache
相關(guān)文章:
主站蜘蛛池模板: 国产成人在线播放 | 国产成人不卡亚洲精品91 | 成年人免费在线视频网站 | 国产欧美日韩在线观看一区二区三区 | 国产一区二区三区四区波多野结衣 | 最新国产成人综合在线观看 | 老色99久久九九精品尤物 | 91国偷自产一区二区三区 | 精品国产午夜肉伦伦影院 | 一级毛片免费观看久 | 美女动作一级毛片 | 国产精品秦先生手机在线 | 国产成人精品久久一区二区小说 | 国产高清美女一级a毛片久久 | 18黄网站 | 成人做爰视频www在线观看 | 中国一级毛片录像 | 欧美亚洲一区二区三区 | 亚洲成人福利在线 | 亚洲欧美高清视频 | 亚洲视频免费在线观看 | a天堂中文在线官网 | 国产成人久久久精品一区二区三区 | 草草视频在线播放 | 欧美一级大尺度毛片 | 91亚洲精品 | 久久99久久99精品免观看 | 亚洲视频免费在线观看 | 日韩欧美一区二区三区免费看 | 亚洲国产一区二区三区四区 | 性欧美一级 | 亚洲综合在线另类色区奇米 | 国产精品9999久久久久 | 亚洲精品一区二区在线播放 | 91亚洲自偷手机在线观看 | 欧美一级日韩一级亚洲一级 | 亚洲 欧美 精品专区 极品 | 久久99热精品免费观看欧美 | 国产精品成人一区二区三区 | 欧美精品久久久久久久久大尺度 | 5388国产亚洲欧美在线观看 |