我的编程空间,编程开发者的网络收藏夹
学习永远不晚

详解linux里的backlog参数

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

详解linux里的backlog参数

问题

我们在linux上服务器起了一个serversocket,并且设置了backlog为2,并没有让serversock.accept()

在客户端上,我们一个一个的启动了连接socket, 当连接数目超过3的时候,客户端依然可以继续新建连接。

什么是backlog

说起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 {

    struct request_sock    *rskq_accept_head;
    struct request_sock    *rskq_accept_tail;
    rwlock_t        syn_wait_lock;
    u8            rskq_defer_accept;
    
    struct listen_sock    *listen_opt;
};
struct listen_sock {
    u8            max_qlen_log; 
    
    int            qlen; 
    int            qlen_young;
    int            clock_hand;
    u32            hash_rnd;
    u32            nr_table_entries; 
    struct request_sock    *syn_table[0];
};
struct request_sock {
	struct request_sock		*dl_next; 
	u16				mss;
	u8				retrans;
	u8				cookie_ts; 
	
	u32				window_clamp; 
	u32				rcv_wnd;	  
	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

TCP握手的几个阶段

收到客户端的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逻辑

tcp_v4_conn_request 函数

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	
	if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
		goto drop;

	
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
		if (sysctl_tcp_syncookies) {
			want_cookie = 1;
		} else
#endif
		goto drop;
	}

	
	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_qledlaOUloAn_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会在去检查sydlaOUloAn的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 函数也就是

tcp_v4_syn_recv_sock 函数

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
	
	key = tcp_v4_md5_do_lookup(sk, newinet->inet_daddr);
	if (key != NULL) {
		
		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_LISTENOVERFLOWSLINUX_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, 
		1,    
		jiffies_to_clock_t(ttd),
		req->retrans,
		uid,
		0,  
		0, 
		atomic_read(&sk->sk_refcnt),
		req,
		len);
}

而对ESTABLISHED状态,并不需要server.accept,只要在accept queue里就已经变成状态ESTABLISHED

到此这篇关于详解linux里的backlog参数的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

详解linux里的backlog参数

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

详解linux里的backlog参数

问题 我们在linux上服务器起了一个serversocket,并且设置了backlog为2,并没有让serversock.accept() 在客户端上,我们一个一个的启动了连接socket, 当连接数目超过3的时候,客户端依然可以继续新建
2022-06-05

linux的ln命令使用参数详解

本篇内容主要讲解“linux的ln命令使用参数详解”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“linux的ln命令使用参数详解”吧!这是linux中一个非常重要命令,请大家一定要熟悉。它的功能
2023-06-13

阿里云服务器的参数详解

阿里云服务器的参数主要分为几个部分:CPU:服务器使用的CPU类型对服务器性能影响非常大,因此我们需要了解阿里云服务器中使用的CPU数量和速度。内存:阿里云服务器通常使用大量的内存来存储数据。如果内存不够,系统可能会崩溃,这会影响系统的性能和稳定性。GPU:GPU是一种计算单元,用于加速图像和视频渲染。GPU可以同时处理多个任务,并且可以以更低的延迟完成更高级别的计算。网络:网络是阿里
2023-10-26

Linux rpm 命令参数使用详解

rpm 执行安装包 二进制包(Binary)以及源代码包(Source)两种。二进制包可以直接安装在计算机中,而源代码包将会由RPM自动编译、安装。源代码包经常以src.rpm作为后缀名。 常用命令组合: -ivh:安装显示安装进度--in
2022-06-04

阿里云服务器的CPU参数详解

阿里云服务器是阿里云提供的云基础设施服务之一,它可以满足企业、开发者和个人的各种需求。而在使用阿里云服务器的过程中,了解服务器的CPU参数是非常重要的。本篇文章将详细介绍如何查看阿里云服务器的CPU参数。阿里云服务器的CPU参数主要包括以下几个方面:CPU型号:这是阿里云服务器CPU的基本参数,可以通过阿里云的控
阿里云服务器的CPU参数详解
2023-11-02

linux shell命令行参数用法详解

习惯使用linux命令行来管理linux系统,例如: $ date 二 11 23 01:34:58 CST 1999 $用户登录时,实际进入了shell,它遵循一定的语法将输入的命令加以解释并传给系统。命令行中输入的第一个字必须是一个
2022-06-04
2024-04-02

linux中find命令的12个常用参数详解

这篇文章主要介绍“linux中find命令的12个常用参数详解”,在日常操作中,相信很多人在linux中find命令的12个常用参数详解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”linux中find命令的
2023-06-13

C++ 函数的默认参数和可变参数详解

c++++ 默认参数允许为函数参数设置默认值,而在可变参数中,可以为函数提供任意数量的参数。具体而言:默认参数:允许在函数声明时为参数指定默认值,在调用时如果没有提供值则使用默认值。可变参数:使用 ... 表示,允许函数接受任意数量的参数,
C++ 函数的默认参数和可变参数详解
2024-04-19

python函数的5种参数详解

(1) 位置参数,调用函数时按位置传入参数 (2) 默认参数,即在函数定义时就给出参数的值,设置默认参数时要注意两点,一是必选参数在前,默认参数在后。二是把变化小的参数放在后面可作为默认参数。具有默认参数的函数被调用
2022-06-04

canvas的drawImage方法参数详解

canvas的drawImage方法是用来在画布上绘制图像的方法,它有三个不同的参数组合。1. drawImage(image, x, y)这个参数组合是最基本的,用来在画布上绘制完整的图像。其中,- image:要绘制的图像,可以是一个图
2023-09-09

Python中的默认参数详解

文章的主题不要使用可变对象作为函数的默认参数例如 list,dict,因为def是一个可执行语句,只有def执行的时候才会计算默认默认参数的值,所以使用默认参数会造成函数执行的时候一直在使用同一个对象,引起bug。基本原理在 Python
2023-01-31

编程热搜

目录