Linux内核源码-sys_listen()

(本文部分参考了《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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值