tcp/ip协议栈--socket API 之listen()

来源:互联网 发布:淘宝移动wi fi 编辑:程序博客网 时间:2024/05/16 08:34

0x01 缘由

     上篇博文介绍了bind的连接,了解了相关细节,这章继续学习socket API ,这篇关注listen。listen几个关键参数和队列是经常被面试官提出的问题。

0x02 API介绍


全连接队列的最大长度:
     backlog保存的是完成三次握手、等待accept的全连接,而不是半连接。
     负载不高时,backlog不用太大。(For complete connections)
     系统最大的、未处理的全连接数量为:min(backlog, somaxconn),net.core.somaxconn默认为128。
     这个值最终存储于sk->sk_max_ack_backlog。
半连接队列的最大长度:
     tcp_max_syn_backlog默认值为256。(For incomplete connections)
     当使用SYN Cookie时,这个参数变为无效。
     半连接队列的最大长度为backlog、somaxconn、tcp_max_syn_backlog的最小值。

0x03 单步跟踪分析


3.1 SYSCALL_DEFINE2

  /* *    系统调用向量 */SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args){    unsigned long a[6];    unsigned long a0, a1;    int err;    unsigned int len;    if (call < 1 || call > SYS_ACCEPT4)        return -EINVAL;    len = nargs[call];    if (len > sizeof(a))        return -EINVAL;    /* 用户空间复制相关参数 */    if (copy_from_user(a, args, len))        return -EFAULT;    audit_socketcall(nargs[call] / sizeof(unsigned long), a);    a0 = a[0];    a1 = a[1];    /* 根据call子调用号,来处理socket相关调用状态。*/    switch (call) {     ......    case SYS_LISTEN:        /* a0 = 3,a1 = 20 是在server.c代码中设设置队列一样*/        err = sys_listen(a0, a1);        break;    .......    default:        err = -EINVAL;        break;    }    return err;}

3.2 SYSCALL_DEFINE2  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);    if (sock) {        /*        * [root@B200-45 test]# sysctl -a | grep somaxconn        *    net.core.somaxconn = 128        */        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;        /*如果backlog大于somaxconn则默认为somaxconn。k可以通过调整相关参数来提高相关连接数*/        if ((unsigned)backlog > somaxconn)            backlog = somaxconn;        /*SELInux相关 */        err = security_socket_listen(sock, backlog);        if (!err)            /*如果tcp,调用inet_listen*/            err = sock->ops->listen(sock, backlog);        /* 将相关参数放入*/        fput_light(sock->file, fput_needed);    }    return err;}

3.3 inet_listen

/*    启动监听时,做的工作主要包括:    1.创建半连接队列的实例,初始化全连接队列。    2.初始化sock的一些变量,把它的状态设为TCP_LISTEN。    3.检查端口是否可用,防止bind()后其它进程修改了端口信息。    4.把sock链接进入监听哈希表listening_hash中。*/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);    /* 初始化全连接队列,创建半连接队列的实例 */    int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);    if (rc != 0)        return rc;    /* 在返回inet_listen()时赋值 *    sk->sk_max_ack_backlog = 0;    sk->sk_ack_backlog = 0;    /* icsk->icsk_ack c初始化清零 */    inet_csk_delack_init(sk);    /* There is race window here: we announce ourselves listening,        此处有一个竞争窗口:我们宣告我们正在监听,但是这个事务仍然没有被get_port校验。        这是可以的,因为这个套接字只有在验证完成后才进入哈希表。     */    sk->sk_state = TCP_LISTEN; /* 把sock的状态置为LISTEN */     if (!sk->sk_prot->get_port(sk, inet->num)) {        inet->sport = htons(inet->num); //源端口        sk_dst_reset(sk);        /*要么把自己加入到 tcp_hashinfo 中的 ehash 中,要么加入到 listening_hash 中,这要根据            sk_state 的值来操作,如果是 LISTEN,就加入后者,如果是除 LISTEN 之外的值,那么就加入            ehash 表,我们会在研究 connect 的代码中看到。*/        sk->sk_prot->hash(sk);/* 把sock链接入监听哈希表中 */        return 0;    }    sk->sk_state = TCP_CLOSE;    /* 如果端口不可用,则释放半连接队列 */     __reqsk_queue_destroy(&icsk->icsk_accept_queue);    return -EADDRINUSE;}
相关可参考:http://blog.csdn.net/zhangskd/article/details/14446581

0x04 总结

关键参数backlog再次强调配张图: