全连接队列的最大长度:
backlog保存的是完成三次握手、等待accept的全连接,而不是半连接。
负载不高时,backlog不用太大。(For complete connections)
系统最大的、未处理的全连接数量为:min(backlog, somaxconn),net.core.somaxconn默认为128。
这个值最终存储于sk->sk_max_ack_backlog。
半连接队列的最大长度:
tcp_max_syn_backlog默认值为256。(For incomplete connections)
当使用SYN Cookie时,这个参数变为无效。
半连接队列的最大长度为backlog、somaxconn、tcp_max_syn_backlog的最小值。
/* * 系统调用向量 */ SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args) { unsigned long a[6]; unsigned long a0, a1; int err; unsigned int len; if (call < 1 || call > SYS_ACCEPT4) return -EINVAL; len = nargs[call]; if (len > sizeof(a)) return -EINVAL; /* 用户空间复制相关参数 */ if (copy_from_user(a, args, len)) return -EFAULT; audit_socketcall(nargs[call] / sizeof(unsigned long), a); a0 = a[0]; a1 = a[1]; /* 根据call子调用号,来处理socket相关调用状态。*/ switch (call) { ...... case SYS_LISTEN: /* a0 = 3,a1 = 20 是在server.c代码中设设置队列一样*/ err = sys_listen(a0, a1); break; ....... default: err = -EINVAL; break; } return err; }
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) { /* * [root@B200-45 test]# sysctl -a | grep somaxconn * net.core.somaxconn = 128 */ somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; /*如果backlog大于somaxconn则默认为somaxconn。k可以通过调整相关参数来提高相关连接数*/ if ((unsigned)backlog > somaxconn) backlog = somaxconn; /*SELInux相关 */ err = security_socket_listen(sock, backlog); if (!err) /*如果tcp,调用inet_listen*/ err = sock->ops->listen(sock, backlog); /* 将相关参数放入*/ fput_light(sock->file, fput_needed); } return err; }
/* 启动监听时,做的工作主要包括: 1.创建半连接队列的实例,初始化全连接队列。 2.初始化sock的一些变量,把它的状态设为TCP_LISTEN。 3.检查端口是否可用,防止bind()后其它进程修改了端口信息。 4.把sock链接进入监听哈希表listening_hash中。 */ int inet_csk_listen_start(struct sock *sk, const int nr_table_entries) { struct inet_sock *inet = inet_sk(sk); struct inet_connection_sock *icsk = inet_csk(sk); /* 初始化全连接队列,创建半连接队列的实例 */ int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries); if (rc != 0) return rc; /* 在返回inet_listen()时赋值 * sk->sk_max_ack_backlog = 0; sk->sk_ack_backlog = 0; /* icsk->icsk_ack c初始化清零 */ inet_csk_delack_init(sk); /* There is race window here: we announce ourselves listening, 此处有一个竞争窗口:我们宣告我们正在监听,但是这个事务仍然没有被get_port校验。 这是可以的,因为这个套接字只有在验证完成后才进入哈希表。 */ sk->sk_state = TCP_LISTEN; /* 把sock的状态置为LISTEN */ if (!sk->sk_prot->get_port(sk, inet->num)) { inet->sport = htons(inet->num); //源端口 sk_dst_reset(sk); /*要么把自己加入到 tcp_hashinfo 中的 ehash 中,要么加入到 listening_hash 中,这要根据 sk_state 的值来操作,如果是 LISTEN,就加入后者,如果是除 LISTEN 之外的值,那么就加入 ehash 表,我们会在研究 connect 的代码中看到。*/ sk->sk_prot->hash(sk);/* 把sock链接入监听哈希表中 */ return 0; } sk->sk_state = TCP_CLOSE; /* 如果端口不可用,则释放半连接队列 */ __reqsk_queue_destroy(&icsk->icsk_accept_queue); return -EADDRINUSE; }
关键参数backlog再次强调配张图: