您的位置:首页 > 教程 > linux > 详解linux里的backlog参数

详解linux里的backlog参数

2022-06-20 13:51:15 来源:易采站长站 作者:

详解linux里的backlog参数

问题我们在linux上服务器起了一个serversocket,并且设置了backlog为2,并没有让serversock.accept()在客户端上,我们一个一个的启动了连接socket,当连接数目...UrP站长之家-易采站长站-Easck.Com

问题

我们在linux上服务器起了一个serversocket,并且设置了backlog为2,并没有让serversock.accept()UrP站长之家-易采站长站-Easck.Com

在客户端上,我们一个一个的启动了连接socket, 当连接数目超过3的时候,客户端依然可以继续新建连接。UrP站长之家-易采站长站-Easck.Com

什么是backlog

说起backlog, 都会想起socket编程中的listen backlog 参数,而这个backlog 是linux内核中处理的backlog么?UrP站长之家-易采站长站-Easck.Com

int listen(int sockfd, int backlog)

listen 中的backlog解释UrP站长之家-易采站长站-Easck.Com

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.UrP站长之家-易采站长站-Easck.Com

实际上在linux内核2.2版本以后,backlog参数控制的是已经握手成功的还在accept queue的大小。UrP站长之家-易采站长站-Easck.Com

握手过程中的结构体

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! */
u16mss;
u8retrans;
u8cookie_ts; /* syncookie: encode tcpopts in timestamp */
/* The following two fields can be easily recomputed I think -AK */
u32window_clamp; /* window clamp at creation time */
u32rcv_wnd;  /* rcv_wnd offered first time */
u32ts_recent;
unsigned longexpires;
const struct request_sock_ops*rsk_ops;
struct sock*sk;
u32secid;
u32peer_secid;
};
struct sock{
unsigned shortsk_ack_backlog;
unsigned shortsk_max_ack_backlog;
}

首先在linux里可以简单的认为有2个队列,一个就是在握手过程中的队列,而另一个就是握手成功的队列UrP站长之家-易采站长站-Easck.Com

简单的描述一下3个结构体UrP站长之家-易采站长站-Easck.Com

request_sockUrP站长之家-易采站长站-Easck.Com

 是每一个client的连接(无论是握手成功,还是不成功) 里面的 expires代表的是这个request在队列里的存活时间,而 *sk 就是连接成功的socket的数目UrP站长之家-易采站长站-Easck.Com

request_sock_queueUrP站长之家-易采站长站-Easck.Com

rskq_accept_head 队列,也就是握手成功的队列,*listen_opt 是指listen过程中的sockUrP站长之家-易采站长站-Easck.Com

listen_sockUrP站长之家-易采站长站-Easck.Com

*syn_table 是指握手没有成功的队列,而qlen,qlen_young 分别指的是队列的长度和队列新成员的个数UrP站长之家-易采站长站-Easck.Com

在结构体中,我们已经清楚的看到了一个listen_sock中的syn_table,另一个是request_sock_queue中的rskq_accept_head,这就是我们刚才说的两个队列,一个是为正在握手的队列,另一个是已经握手成功的队列。UrP站长之家-易采站长站-Easck.Com

我们在上面都看到了结构体中只是看到了未握手的队列的长度,并没有看到握手的队列长度统计,实际上握手成功的队列长度是在sock 结构中UrP站长之家-易采站长站-Easck.Com

sockUrP站长之家-易采站长站-Easck.Com

当握手成功后每一个client就是一个sock, sk_ack_backlog 是队列长度,而sk_max_ack_backlog是指最大的队列长度UrP站长之家-易采站长站-Easck.Com

在这里我们会有疑问,难道是没个连接上的 sock都会保留队列的长度么?实际上在此时的sock 代表的是server端listen 的sock而不是客户端的sock,也就是在握手没有成功的过程中,在linux使用的sock都是server的listen的sock, 对客户端只是保留成request_sockUrP站长之家-易采站长站-Easck.Com

TCP握手的几个阶段

收到客户端的syn请求 ->将这个请求放入syn_table中去->服务器端回复syn-ack->收到客户端的ack->放入accept queue中UrP站长之家-易采站长站-Easck.Com

我们把整个过程分为5个部分,其中将请求放入syn_table和accept queue中的过程也是backlog相关的,在下面我们会详细阐述。UrP站长之家-易采站长站-Easck.Com

我们先简单的描述一下几个tcp的操作函数,下面针对的也是ip4协议的UrP站长之家-易采站长站-Easck.Com

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_sockUrP站长之家-易采站长站-Easck.Com

我们所重点关注的主要是方法中的drop逻辑UrP站长之家-易采站长站-Easck.Com

tcp_v4_conn_request 函数

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;UrP站长之家-易采站长站-Easck.Com

这里有个 qlen 代表的是listen_opt的 syn_table的长度,那什么是max_qlen_log呢?UrP站长之家-易采站长站-Easck.Com

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 就是我们熟悉的UrP站长之家-易采站长站-Easck.Com

/proc/sys/net/ipv4/tcp_max_syn_backlogUrP站长之家-易采站长站-Easck.Com

我们看一下listen 函数在kernel的实现UrP站长之家-易采站长站-Easck.Com

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的最小值UrP站长之家-易采站长站-Easck.Com

somaxconn的值定义在UrP站长之家-易采站长站-Easck.Com

/proc/sys/net/core/somaxconnUrP站长之家-易采站长站-Easck.Com

2.sk_acceptq_is_fullUrP站长之家-易采站长站-Easck.Com

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中的值UrP站长之家-易采站长站-Easck.Com

3.inet_csk_reqsk_queue_youngUrP站长之家-易采站长站-Easck.Com

在判断sk_acceptq_is_full 的情况下,同是也要求了判断inet_csk_reqsk_queue_young>1,也就是刚才的结构体listen_sock的qlen_youngUrP站长之家-易采站长站-Easck.Com

qlen_young 是对syn_table的计数,进入 syn_table 加1,出了syn_table  -1UrP站长之家-易采站长站-Easck.Com

有的人可能会有疑问了UrP站长之家-易采站长站-Easck.Com

如果accept queue满了,那么qlen_young不就是一直增加,而新来的客户端都会被条件if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) 而drop syn的ack包,那么客户端会出现connected timeout, 而实际上你在测试linux的环境中会发现并没有出现这样的情况。UrP站长之家-易采站长站-Easck.Com

实际上linux在server起socket的时候会调用tcp_keepalive_timer启动tcp_synack_timer,会调用函数inet_csk_reqsk_queue_pruneUrP站长之家-易采站长站-Easck.Com

 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包的请求UrP站长之家-易采站长站-Easck.Com

为了提高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 ,并且不大于120HZUrP站长之家-易采站长站-Easck.Com

关于retry, retry的参数可以通过设置 UrP站长之家-易采站长站-Easck.Com

/proc/sys/net/ipv4/tcp_syn_retriesUrP站长之家-易采站长站-Easck.Com

当然你可以通过设置UrP站长之家-易采站长站-Easck.Com

/proc/sys/net/ipv4/tcp_abort_on_overflow 为1 不允许syn ack 重试UrP站长之家-易采站长站-Easck.Com

因为被inet_csk_reqsk_queue_prune函数清除了syn_table,在没有并发的前提下基本上不会出现inet_csk_reqsk_queue_young>1的情况,也就是说不会出现drop sync的情况,在客户端表现,不会出现connect timeout 的情况(这里的实现linux和mac的实现有很大的不同)而刚开始的问题也能得到合理的解释了UrP站长之家-易采站长站-Easck.Com

通过函数tcp_v4_conn_request的分析,在linux的设计初衷是尽力的允许新的连接握手,而期望服务器端能更快的响应accept.UrP站长之家-易采站长站-Easck.Com

我们也许会问,刚才的服务器syn ack回去后,如果客户端也回复了ack的话,而此时accept的queue满了,将会如何处理UrP站长之家-易采站长站-Easck.Com

我们回到前面提到的步骤,处理客户端的ack 函数也就是UrP站长之家-易采站长站-Easck.Com

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
/* 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_LISTENOVERFLOWSLINUX_MIB_LISTENDROPS而这些参数的值可以通过UrP站长之家-易采站长站-Easck.Com

netstat -s 来查看到UrP站长之家-易采站长站-Easck.Com

在函数tcp_v4_syn_recv_sock中我们看到tcp_create_openreq_child,此时才clone出一个新的socket ,也就是只有通过了3次握手后,linux才会产生新的socket, 而在3次握手中所传的socket 实际上是server的listen的 socket, 那也就是说这个socket 只有一个状态TCP_LISTENUrP站长之家-易采站长站-Easck.Com

netstat的状态UrP站长之家-易采站长站-Easck.Com

通过在tcp_rcv_state_process可以置socket 的状态,而我们通常使用netstat 中看到这些socket的状态UrP站长之家-易采站长站-Easck.Com

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 把这些信息写到了UrP站长之家-易采站长站-Easck.Com

/proc/net/tcp UrP站长之家-易采站长站-Easck.Com

而在TCP_SEQ_STATE_OPENREQ 的情况下(就是 syn synack ack)的3个状态下都显示成TCP_SYN_RECVUrP站长之家-易采站长站-Easck.Com

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里就已经变成状态ESTABLISHEDUrP站长之家-易采站长站-Easck.Com

到此这篇关于详解linux里的backlog参数的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。UrP站长之家-易采站长站-Easck.Com

UrP站长之家-易采站长站-Easck.Com

如有侵权,请联系QQ:279390809 电话:15144810328

相关文章

  • Linux下用GHOST来做系统备份

    Linux下用GHOST来做系统备份

    在Windows系统下备份我们可以用GHOST工具软件完成,Linux系统不能完全依赖于GHOST工具,一则是GHOST本身是有版权的软件,二是GHOST只支持ext2、ext3文件系统的Linux分区,不支持reiserfs、xfs等
    2019-10-25
  • 给 FreeBSD 12.1 安装 GNOME3 图形界面

    给 FreeBSD 12.1 安装 GNOME3 图形界面

    FreeBSD是一个完全开放的、安全的系统,可以Do it yourself的系统。但是个人还是不喜欢呆板的命令行界面,所有就给 FreeBSD 12.1 安装 GNOME3 图形界面。 开始操作,启动FreeBSD 12.1,以root身份
    2019-10-27
  • Ubuntu Studio一个不错的操作系统

    Ubuntu Studio一个不错的操作系统

    Ubuntu Studio 是一个基于 Ubuntu 而面向音频、视频及图形爱好者的操作系统。本次推出的 Ubuntu Studio 为 7.04 版,目前仅支持 Intel i386 兼容的处理器。 Ubuntu Studio 从大量的开源多媒体创作程序
    2019-10-25
  • 最受欢迎的10款Linux免费游戏

    最受欢迎的10款Linux免费游戏

    图为在Linux下运行的免费游戏America's Army 如果你认为 Linux下没有什么好游戏的话,那就错了!我们来看看Linux下最受欢迎的10款游戏吧,而且这些游戏都是完全免费的! # skyreal update(06-12
    2019-10-25
  • compiz fusion特效使用心得(附3D桌面图)

    compiz fusion特效使用心得(附3D桌面图)

    3D桌面图 LINUX发行版就是好,当大多数人还在沉迷于那些什么风格图标鸟玻璃效果甚至孜孜不倦津津乐道的时候,Linux的高手们已经悄悄为我们开启了一个桌面系统3D的时代。 当我第一次
    2019-10-25
  • Ubuntu 下aMule 的安装配置

    Ubuntu 下aMule 的安装配置

    aMule 是一个类似于 eMule 的多平台 P2P 客户端程序。以下将简略叙述 aMule 在 Ubuntu 中的安装及配置 [High ID] 与 [KAD] 的过程。 安装: sudo apt-get install amule sudo apt-get install amule-utils 如果需要
    2019-10-25
  • JMeter jp@gc - stepping thread group插件

    JMeter jp@gc - stepping thread group插件

    这是一个出单接口压力测试的小例子,了解一下压力测试最最基础的基础。出单接口是用来保险出单的,不需要在UI界面下一步下一步的出单,接口一调数据入库完事~~~。再啰嗦一句接
    2019-10-27
  • Ubuntu 下使用K3B软件刻录光盘(图)

    Ubuntu 下使用K3B软件刻录光盘(图)

    不知大家会不会在Ubuntu下刻录光盘呢?k3b是KDE环境下的一个CD/DVD刻录软件,有非常容易使用的界面,功能比较齐全。用起来感觉就象是win下的Nero一样简单自然,比ubuntu里原来的刻录CD方式
    2019-10-25