listen主要是设置套接字为TCP_LISTEN状态,并设置等待连接队列的长度,下面是listen的代码:
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
// 根据fd查找socket内核对象
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
// 获取内核参数 net.core.somaxconn
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned int)backlog > somaxconn)
backlog = somaxconn;
// 调用协议栈注册的listen函数
err = security_socket_listen(sock, backlog);
if (!err)
err = sock->ops->listen(sock, backlog); // inet_listen
fput_light(sock->file, fput_needed);
}
return err;
}
sockfd_lookup_light通过fd查找struct socket。
sock->ops->listen指向的是inet_listen。
sock->ops指向的是inet_stream_ops,见前一篇《网络篇之bind》
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;
old_state = sk->sk_state;
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.
*/
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;
}
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
对类型和状态进行检查。当前不是TCP_LISTEN状态,执行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);
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0; // current listen backlog
inet_csk_delack_init(sk); // 对inet_connection_sock.icsk_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.
*/
sk_state_store(sk, TCP_LISTEN); // 设置sock_common.skc_state为监听状态
if (!sk->sk_prot->get_port(sk, inet->inet_num)) { // inet_csk_get_port
inet->inet_sport = htons(inet->inet_num);
sk_dst_reset(sk); // 删除sk保存的路由
err = sk->sk_prot->hash(sk); // inet_hash
if (likely(!err))
return 0;
}
sk->sk_state = TCP_CLOSE;
return err;
}
sk_state_store(sk, TCP_LISTEN);
通过此方法将socket更新为TCP_LISTEN状态。
get_port指向的是inet_csk_get_port,根据传入的端口进行绑定。这个函数在调用bind的时候已经执行过了,参考前一篇《网络篇之bind》。