从内核角度分析 listen() 系统调用的 backlog 参数作用

编写服务端程序时,需要调用 listen()
系统调用来开始监听请求连接, listen()
系统调用的原型如下:

int listen(int sockfd, int backlog);

可以看到, listen()
系统调用需要传入两个参数,第一个  sockfd
表示监听的 socket 句柄,而  backlog
参数表示接收请求队列的长度。对于第一个参数比较容易理解,那么第二个参数的作用是什么呢?下面我们来分析一下。

listen() 系统调用在内核的实现

当我们在程序中调用 listen()
系统调用时,会触发调用内核的  sys_listen()
函数, sys_listen()
函数的实现如下:

asmlinkage long sys_listen(int fd, int backlog)
{
struct socket *sock;
int err;


if ((sock = sockfd_lookup(fd, &err)) != NULL) { /* 步骤1 */
if ((unsigned) backlog > SOMAXCONN)
backlog = SOMAXCONN;
err=sock->ops->listen(sock, backlog); /* 步骤2 */
sockfd_put(sock);
}
return err;
}

sys_listen()
函数的实现比较简单,过程如下:

  • 步骤1:首先调用 sockfd_lookup()
    函数查找文件句柄  fd
    对应的  socket
    对象。

  • 步骤2:通过调用 socket
    对象的的  listen()
    方法来进行监听操作。

对于 TCP协议
来说, socket
对象的  listen()
方法会绑定到  inet_listen()
函数。所以  步骤2
最后会调用  inet_listen()
函数, inet_listen()
函数的实现如下:

int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;


lock_sock(sk);


...


old_state = sk->state;
if (!((1<<old_state)&(TCPF_CLOSE|TCPF_LISTEN)))
goto out;


if (old_state != TCP_LISTEN) {
err = tcp_listen_start(sk); /* 步骤1 */
if (err)
goto out;
}


sk->max_ack_backlog = backlog; /* 步骤2 */
err = 0;


out:
release_sock(sk);
return err;
}

inet_listen()
函数的实现也非常简单,主要分为两个步骤:

  • 步骤1:调用 tcp_listen_start()
    函数把  socket
    对象的状态设置为  TCP_LISTEN

  • 步骤2:把 socket
    对象的  sk
    成员变量的  max_ack_backlog
    字段设置为  backlog

其中 max_ack_backlog
字段就是用于保存最大接收连接队列的长度,至此  listen()
函数的工作就完成了,那么内核在哪里限制接收连接队列的呢?

内核限制TCP连接队列

当网卡接收到数据时,会接收到数据包并封装成 sk_buff
对象,如果接收到的数据包是一个  TCP协议
的数据包,那么内核将会把数据包提交给  tcp_v4_rcv()
函数处理。我们只关注限制TCP连接队列的实现,所以这里直接给出限制逻辑相关的调用链:

tcp_v4_rcv() 
\__> tcp_v4_do_rcv()
\__> tcp_v4_hnd_req()
\__> tcp_check_req()
\__> tcp_v4_syn_recv_sock()

从上面的调用链可以看出,最后调用的函数是 tcp_v4_syn_recv_sock()
tcp_v4_syn_recv_sock()
函数的作用是当对端连接完成  TCP三次握手
后,将创建一个新的 socket 连接对象。我们来看看  tcp_v4_syn_recv_sock()
函数对连接队列的限制逻辑:

struct sock * 
tcp_v4_syn_recv_sock(struct sock *sk,
struct sk_buff *skb,
struct open_request *req,
struct dst_entry *dst)
{
struct tcp_opt *newtp;
struct sock *newsk;


if (tcp_acceptq_is_full(sk)) /* 判断接收队列是否超过限制 */
goto exit_overflow;


...


return newsk;


exit_overflow:
NET_INC_STATS_BH(ListenOverflows);
exit:
NET_INC_STATS_BH(ListenDrops);
dst_release(dst);
return NULL;
}

tcp_v4_syn_recv_sock()
函数首先调用了  tcp_acceptq_is_full()
来判断接收队列是否已经超过限制,如果超过限制就不再创建新的连接, tcp_acceptq_is_full()
函数的实现如下:

static inline int tcp_acceptq_is_full(struct sock *sk) {
return sk->ack_backlog > sk->max_ack_backlog;
}

tcp_acceptq_is_full()
函数很简单,就是判断当前接收队列的数量是否超过了限制的最大数量,如果是就返回true,至此我们对  backlog
参数的作用分析完毕。

Linux内核那些事
我还没有学会写个人说明!
上一篇

富时罗素将把海康威视和中芯国际从富时中国A50和50指数中剔除

下一篇

选择一款适合SaaS应用的客户端:杰和科技VT20云终端

你也可能喜欢

评论已经被关闭。

插入图片