日期:2014-05-16  浏览次数:20706 次

Linux内核网络协议栈8—socket监听

几个问题
了解以下几个问题的同学可以直接忽略下文:

1、listen 库函数主要做了什么?
2、 什么是最大并发连接请求数?
3、什么是等待连接队列?

socket 监听相对还是比较简单的,先看下应用程序代码:

listen( server_sockfd, 5) ; 
?

其中,第一个参数 server_sockfd为服务端 socket所对应的文件描述符,第二个参数5 代表监听socket 能处理的最大并发连接请求数,在2.6.26 内核中,该值为 256

listen 库函数调用的主要工作可以分为以下几步:
1 、根据 socket文件描述符找到内核中对应的 socket结构体变量;这个过程在《socket地址绑定 》 一文中描述过,这里不再重述;
2 、设置 socket的状态并初始化等待连接队列;
3 、将 socket放入listen 哈希表中;

listen 调用代码跟踪
下面是 listen库函数对应的内核处理函数:

    asmlinkage long sys_listen(int fd, int backlog)
    {
       struct socket *sock;
       int err, fput_needed;
       int somaxconn;
       // 根据文件描述符取得内核中的socket
       sock = sockfd_lookup_light(fd, &err, &fput_needed);
       if (sock) {
           // 根据系统中的设置调整参数backlog
           somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
           if ((unsigned)backlog > somaxconn)
               backlog = somaxconn;
           err = security_socket_listen(sock, backlog);
           // 调用相应协议簇的listen函数
           if (!err)
               err = sock->ops->listen(sock, backlog);
           fput_light(sock->file, fput_needed);
       }
       return err;
    }
?

根据《创建socket 》 一文的介绍,例子中,这里sock->ops-> listen(sock, backlog) 实际上调用的是 net/ipv4/Af_inet.c:inet_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;
       // 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息
       if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
           goto out;
       old_state = sk->sk_state;
       // 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息
       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.
        */
       // 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化
       if (old_state != TCP_LISTEN) {
           err = inet_csk_listen_start(sk, backlog);
       if (err)
           goto out;
       }
       // 4 设置sock的最大并发连接请求数
       sk->sk_max_ack_backlog = backlog;
       err = 0;
    out:
       release_sock(sk);
       return err;
    }
?

上面的代码中,有点值得注意的是,当 sock 状态已经是 TCP_LISTEN 时,也可以继续调用 listen() 库函数,其作用是设置