(本文部分参考了《Linux内核源代码情景分析》)
操作SYS_LISTEN 设定server插口,是由 sys_listen()实现的。
需要了解的是“有连接”模式的插口天生就是按 client/server 的模式运转的,只有在一个 server 插口和一个 client 插口之间建立起连接。区分这两种插口的方法是:只要在插口创建以后为其调用了 listen(),这个插口就成为 server 插口了。凡是 server 插口都不能主动去与别的插口建立连接,而只能被动地通过 accept()接受来自 client 插口的连接请求。而 client 插口则相反,不能调用 accept()来接受连接请求,而只能主动地通过 connect()提出连接请求。
listen系统调用:asmlinkage long sys_listen(int fd, int backlog)的操作与sys_bind()十分类似,都是根据打开文件号 fd 找到插口的 socket 数据结构( inode 的一部分),进而通过结构中的指针 ops执行相应的回调函数。sys_listen的回调函数是:inet_listen(),在此函数中会首先判断传进来的插口类型和状态,合法后会调动tcp_listen_start()函数开始侦听,其实是相当简单的:除状态本身的变化以外,就是设置(或改变几个参数),主要就是最大队列长度sk_max_ack_backlog。
函数调用过程如图所示:
下面是完整代码:
inet_listen代码如下:
/* 将一个socket转入listen状态 */
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;
lock_sock(sk);
err = -EINVAL;
/* 检测系统调用的状态和类型,如果不合法则退出 */
/*
*只有插口的类型为 SOCK_STREAM,即“有连接”模式的插口,并且已经为其 bind()了插口地址,才允许 listen()。
*对于符合这些条件的插口也不是什么时候都可以调用 listen()的。
*插口的 sock结构中有个成分 state,用来实现一种“有限状态机”。只有当这个状态机处于 TCP_CLOSE 或 TCP_LISTEN这两种状态时才可以对其调用 listen()。
*在前面 sock_create()的代码中可以看到在创建一个插口时要调用函数 sock_init_data()对分配的sock数据结构进行初始化,在那里state被设置成 TCP_CLOSE。
*状态TCP_CLOSE 表示插口只是刚刚建立,尚未宣布成为 server 插口;
* TCP_LISTEN 则表示插口已经设置成 server 插口,当尚未建立起连接,并且不是在等待来自 client 一方的连接请求。
*只有在这两种状态下才允许改变插口的参数(主要是连接请求队列的容量)。
*/
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;
if (old_state != TCP_LISTEN) {
err = tcp_listen_start(sk);/* 开始侦听 */
if (err)
goto out;
}
/* 设置传输控制块的连接队列长度上限 */
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
tcp_listen_start()代码如下:
int tcp_listen_start(struct sock *sk)
{
struct inet_sock *inet = inet_sk(sk);
struct tcp_sock *tp = tcp_sk(sk);
struct tcp_listen_opt *lopt;
/* 初始连接队列长度上限 */
sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
tp->accept_queue = tp->accept_queue_tail = NULL;
/* 初始化传输控制块中与延时发送ACK有关的数据结构 */
rwlock_init(&tp->syn_wait_lock);
tcp_delack_init(tp);
/* 为管理连接请求块的散列表分配存储空间,如果失败则退出 */
lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL);
if (!lopt)
return -ENOMEM;
memset(lopt, 0, sizeof(struct tcp_listen_opt));
for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++)
if ((1 << lopt->max_qlen_log) >= sysctl_max_syn_backlog)
break;
/* 计算哈希表的哈希种子 */
get_random_bytes(&lopt->hash_rnd, 4);
/* 将散列块与传输控制块绑定 */
write_lock_bh(&tp->syn_wait_lock);
tp->listen_opt = lopt;
write_unlock_bh(&tp->syn_wait_lock);
sk->sk_state = TCP_LISTEN;/* 设置控制块的状态 */
if (!sk->sk_prot->get_port(sk, inet->num)) {/* 进行端口绑定 */
inet->sport = htons(inet->num);/* 设置网络字节序的端口号 */
sk_dst_reset(sk);/* 清除路由缓存 */
sk->sk_prot->hash(sk);/* 将传输控制块添加到侦听散列表中 */
return 0;
}
/* 绑定失败,设置其状态 */
sk->sk_state = TCP_CLOSE;
/* 解除侦听连接请求块与传输控制块的绑定 */
write_lock_bh(&tp->syn_wait_lock);
tp->listen_opt = NULL;
write_unlock_bh(&tp->syn_wait_lock);
kfree(lopt);/* 释放侦听连接请求块 */
return -EADDRINUSE;
}