当绑定完端口后,就可以开启监听了,那么 listen
是如何实现的呢?还做哪些初始化操作呢?
listen 函数声明
先 man
一下,看看函数声明
int listen(int sockfd, int backlog);
返回值是错误码,参数 sockfd
是文件描述符,backlog
有这么一段解释:The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. 等待 tcp
建立连接的队列最大长度。
入口
首先肯定是系统调用,找到处理函数 __sys_listen
int __sys_listen(int fd, int backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
sockfd_lookup_light
不用看,根据 fd
找到 socket
. 可以看到 backlog
参数受限于系统参数 somaxconn
, 一般机器默认是 128, 肯定是不够的,线上一般大于 1024,如果短连接压力比较大,可以继续调大。security_socket_listen
所有以 security_ 开头的都是新版内核轻量级安全框架,在起动时注册安全检查回调,比如 selinux
,忽略。最后调用 inet_stream_ops.inet_listen
inet_listen 源码分析
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err, tcp_fastopen;
lock_sock(sk);
err = -EINVAL;
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
判断 socket
状态,如果不处于 SS_UNCONNECTED
, 或是类型不是 SOCK_STREAM
不能监听
old_state = sk->sk_state;
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto out;
检查 struct sock
状态,只有处于 TCPF_CLOSE
和 TCPF_LISTEN
状态的才可以监听。可以参考 tcp
连接状态机。另外 struct sock
这些结构体都是套娃,可以参考前面的分享。
/* Really, if the socket is already in listen state
* we can only allow the backlog to be adjusted.
*/
if (old_state != TCP_LISTEN) {
/* Enable TFO w/o requiring TCP_FASTOPEN socket option.
* Note that only TCP sockets (SOCK_STREAM) will reach here.
* Also fastopen backlog may already been set via the option
* because the socket was in TCP_LISTEN state previously but
* was shutdown() rather than close().
*/
tcp_fastopen = sock_net(sk)->ipv4.sysctl_tcp_fastopen;
if ((tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) &&
(tcp_fastopen & TFO_SERVER_ENABLE) &&
!inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
fastopen_queue_tune(sk, backlog);
tcp_fastopen_init_key_once(sock_net(sk));
}
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}
核心就在这里,首先判断是否允许 tcp_fastopen
, 初始化相关队列。然后调用核心 inet_csk_listen_start
函数初始化。tcp_fastopen
简称 TFO
, 网上有很多介绍,大致就是将 tcp
三次握手建连,变成两次,对于移动网络优化非常明显。nginx
也支持 TFO
,暂时忽略不看。
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
如果己经处于 TCP_LISTEN
状态了,那么再次调用 listen
也可以,但只能改变 backlog
值。
inet_csk_listen_start 源码分析
这块代码也比较短,分段来看
int inet_csk_listen_start(struct sock *sk, int backlog)
{
struct inet_connection_sock *icsk = inet_csk(sk);
struct inet_sock *inet = inet_sk(sk);
int err = -EADDRINUSE;
reqsk_queue_alloc(&icsk->icsk_accept_queue);
reqsk_queue_alloc
初始化请求建立连接队列,初始化一个 FIFO
队列,还有 TFO
队列
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);
inet_csk_delack_init
用来初始化延迟发送 ack
队列
/* 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.
*/
inet_sk_state_store(sk, TCP_LISTEN);
此处真正的改变状态为 TCP_LISTEN
, 由于 smp
的原因,不简单的赋值。
if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);
get_port
查看源码,回调 tcp_prot.inet_csk_get_port
用来分配监听端口,如果己经 bind
过了,那么不做任何操作,否则随机分配一个。一般也不会用随机端口,没有意义。
sk_dst_reset(sk);
err = sk->sk_prot->hash(sk);
if (likely(!err))
return 0;
}
inet_sk_set_state(sk, TCP_CLOSE);
return err;
}
hash
回调 tcp_prot.inet_hash
, 最终调用 __inet_hash
,将 socket
注册到全局哈希表里。struct inet_hashinfo
这个结构体是一个全局单例,用来保存各种 socket
连接。
小结
至此,listen
粗略流程分析完毕。
网友评论