1. 应用层listen()函数

功能:监听来自客户端的tcp socket的连接请求

listen函数在一般在调用bind之后-调用accept之前调用,它的函数原型是:

#include<sys/socket.h>
int listen(int sockfd, int backlog)

参数sockfd是被listen函数作用的套接字,参数backlog是侦听队列的长度。 在进程正在处理一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理的连接(还没有调用accept函数的连接),这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。


返回值

成功

失败

是否设置errno

0

−1

错误信息:

EADDRINUSE:另一个socket也在监听同一个端口。
EBADF:参数sockfd为非法的文件描述符。
ENOTSOCK:参数sockfd不是文件描述符。
EOPNOTSUPP:套接字类型不支持listen操作。

2.内核层listen系统调用

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;

sock = sockfd_lookup_light(fd, &err, &fput_needed); //通过文件描述符找到socket
if (sock) {
// 根据系统中的设置调整参数backlog
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn; //在sysctl_core_net_init函数内部赋值为128个
if ((unsigned int)backlog > somaxconn) //监听客户端个数冲突检测
backlog = somaxconn;

err = security_socket_listen(sock, backlog); //LSM机制
if (!err)
err = sock->ops->listen(sock, backlog); //对应结构体inet_stream_ops中的成员 inet_listen()

fput_light(sock->file, fput_needed);
}
return err;
}

该函数内部最关键的部分如下

err = sock->ops->listen(sock, backlog); //对应结构体inet_stream_ops中的成员 inet_listen()
/*
* Move a socket into listening state.
*/
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;

lock_sock(sk);

err = -EINVAL;
// 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;

old_state = sk->sk_state;
// 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto out;

/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
// 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化
if (old_state != TCP_LISTEN) {
/* Check special setups for testing purpose to enable TFO w/o
* requiring TCP_FASTOPEN sockopt.
* Note that only TCP sockets (SOCK_STREAM) will reach here.
* Also fastopenq may already been allocated because this
* socket was in TCP_LISTEN state previously but was
* shutdown() (rather than close()).
*/
if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 &&
inet_csk(sk)->icsk_accept_queue.fastopenq == NULL) {
if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0)
err = fastopen_init_queue(sk, backlog);
else if ((sysctl_tcp_fastopen &
TFO_SERVER_WO_SOCKOPT2) != 0)
err = fastopen_init_queue(sk,
((uint)sysctl_tcp_fastopen) >> 16);
else
err = 0;
if (err)
goto out;
}
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}

// 4 设置sock的最大并发连接请求数
sk->sk_max_ack_backlog = backlog;
err = 0;

out:
release_sock(sk);
return err;
}
EXPORT_SYMBOL(inet_listen);

在该函数内部主要完成如下部分

a. 初始化listen监听客户端的队列个数及空间

err = fastopen_init_queue(sk, backlog);
static inline int fastopen_init_queue(struct sock *sk, int backlog)
{
struct request_sock_queue *queue =
&inet_csk(sk)->icsk_accept_queue;

if (queue->fastopenq == NULL) {
//分配空间
queue->fastopenq = kzalloc(
sizeof(struct fastopen_queue),
sk->sk_allocation);
if (queue->fastopenq == NULL)
return -ENOMEM;

sk->sk_destruct = tcp_sock_destruct;
spin_lock_init(&queue->fastopenq->lock);
}
queue->fastopenq->max_qlen = backlog; //监听的客户端个数
return 0;
}

b. socket listen()状态启动

err = inet_csk_listen_start(sk, backlog);
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); //结构体内部转换的一种机制

// 初始化连接等待队列,icsk->icsk_accept_queue表示当前listen监听的并发个数内存空间
int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);

if (rc != 0)
return rc;

sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);

/* There is race window here: we announce ourselves listening,
* but this transition is still not validated by get_port().
* It is OK, because this socket enters to hash table only
* after validation is complete.
*/

// 设置sock的状态为TCP_LISTEN
sk->sk_state = TCP_LISTEN;
//sk->sk_prot指向的是tcp_prot结构体, get_port=inet_csk_get_port()
if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);

sk_dst_reset(sk);
sk->sk_prot->hash(sk); /*把Socket添加到监听HASH表中, 它将调用 inet_hash()函数*/

return 0;
}

sk->sk_state = TCP_CLOSE;
__reqsk_queue_destroy(&icsk->icsk_accept_queue);
return -EADDRINUSE;
}
EXPORT_SYMBOL_GPL(inet_csk_listen_start);

在该函数内部主要完成如下部分

b.1 初始化连接等待队列,icsk->icsk_accept_queue表示当前listen监听的并发个数内存空间

int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size = sizeof(struct listen_sock);
struct listen_sock *lopt;

//获取socket监听个数的范围,下面最小为8个
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog); //sysctl_max_syn_backlog默认256
nr_table_entries = max_t(u32, nr_table_entries, 8);
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);

//lopt_size表示总的长度
lopt_size += nr_table_entries * sizeof(struct request_sock *); //分配nr_table_entries个结构体struct request_sock
if (lopt_size > PAGE_SIZE)
lopt = vzalloc(lopt_size);
else
lopt = kzalloc(lopt_size, GFP_KERNEL);
if (lopt == NULL)
return -ENOMEM;

for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);

get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
rwlock_init(&queue->syn_wait_lock);
queue->rskq_accept_head = NULL;
lopt->nr_table_entries = nr_table_entries;

write_lock_bh(&queue->syn_wait_lock);
queue->listen_opt = lopt; //表示当前socket能够listen的并发个数内存空间
write_unlock_bh(&queue->syn_wait_lock);

return 0;
}

b.2 获取服务器(本地)的端口

if (!sk->sk_prot->get_port(sk, inet->inet_num)) {


b.3 把socket与hash链表进行绑定

sk->sk_prot->hash(sk); /*把Socket添加到监听HASH表中, 它将调用 inet_hash()函数*/
void inet_hash(struct sock *sk)
{
if (sk->sk_state != TCP_CLOSE) {
local_bh_disable();
__inet_hash(sk);
local_bh_enable();
}
}
EXPORT_SYMBOL_GPL(inet_hash);
static void __inet_hash(struct sock *sk)
{
struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; //将调用 tcp_hashinfo 结构体
struct inet_listen_hashbucket *ilb;

/*Socket不处于监听状态*/
if (sk->sk_state != TCP_LISTEN) {
__inet_hash_nolisten(sk, NULL); /*这里对应的是已经建立连接的*/
return;
}

WARN_ON(!sk_unhashed(sk));
/*根据监听的端口号,查找相对应的listen HASH*/
ilb = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];

spin_lock(&ilb->lock);
/*把sock添加到监听HASH桶的头部,连接到sk->sk_nulls_node */
__sk_nulls_add_node_rcu(sk, &ilb->head);
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
spin_unlock(&ilb->lock);
}

关于该函数内部的链表关系见下流程图

linux 服务器端listen(5)_客户端

3. 总结

       其实listen函数内部只是纯粹的将端口添加道hash链表上,并没有启动监听客户端的连接,关于连接其实是在accept函数内部。