diecui1202 发表于 2013-2-4 13:37:01

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()库函数,其作用是设置sock的最大并发连接请求数;
下面看看inet_csk_listen_start()函数:

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;sk->sk_max_ack_backlog = 0;sk->sk_ack_backlog = 0;inet_csk_delack_init(sk);// 设置sock的状态为TCP_LISTENsk->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;__reqsk_queue_destroy(&icsk->icsk_accept_queue);return -EADDRINUSE;} 这里nr_table_entries是参数backlog经过最大值调整后的值;

相关数据结构
先看下接下来的代码中提到了几个数据结构,一起来看一下:
1、request_sock
    struct request_sock {         struct request_sock *dl_next; /* Must be first */         u16 mss;         u8 retrans;         u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */         /* The following two fields can be easily recomputed I think -AK */         u32 window_clamp; /* window clamp at creation time */         u32 rcv_wnd; /* rcv_wnd offered first time */         u32 ts_recent;         unsigned long expires;         const struct request_sock_ops *rsk_ops;         struct sock *sk;         u32 secid;         u32 peer_secid;    }; socket在侦听的时候,那些来自其它主机的tcp socket的连接请求一旦被接受(完成三次握手协议),便会建立一个request_sock,建立与请求socket之间的一个tcp连接。该request_sock会被放在一个先进先出的队列中,等待accept系统调用的处理;
2、listen_sock
    struct listen_sock {         u8 max_qlen_log;         /* 3 bytes hole, try to use */         int qlen;         int qlen_young;         int clock_hand;         u32 hash_rnd;         u32 nr_table_entries;         struct request_sock *syn_table;    }; 新建立的request_sock就存放在syn_table中;这是一个哈希数组,总共有nr_table_entries项;
成员max_qlen_log以2的对数的形式表示request_sock队列的最大值;
qlen是队列的当前长度;
hash_rnd是一个随机数,计算哈希值用;
3、request_sock_queue
    struct request_sock_queue {         struct request_sock *rskq_accept_head;         struct request_sock *rskq_accept_tail;         rwlock_t syn_wait_lock;         u16 rskq_defer_accept;         /* 2 bytes hole, try to pack */         struct listen_sock *listen_opt;    }; 结构体struct request_sock_queue中的rskq_accept_head和rskq_accept_tail分别指向request_sock队列的队列头和队列尾;
 
等待连接队列初始化
先看下reqsk_queue_alloc()的源代码:
    int reqsk_queue_alloc(struct request_sock_queue *queue,               unsigned int nr_table_entries)    {       size_t lopt_size = sizeof(struct listen_sock);       struct listen_sock *lopt;       // 1 控制nr_table_entries在8~ sysctl_max_syn_backlog之间       nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);       nr_table_entries = max_t(u32, nr_table_entries, 8);       // 2 向上取2的幂       nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);       // 3 申请等待队列空间       lopt_size += nr_table_entries * sizeof(struct request_sock *);       if (lopt_size > PAGE_SIZE)         lopt = __vmalloc(lopt_size,               GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,               PAGE_KERNEL);       else         lopt = kzalloc(lopt_size, GFP_KERNEL);       if (lopt == NULL)         return -ENOMEM;       // 4 设置listen_sock的成员max_qlen_log最小为3,最大为nr_table_entries的对数       for (lopt->max_qlen_log = 3;            (1 << lopt->max_qlen_log) < nr_table_entries;            lopt->max_qlen_log++);            // 5 相关字段赋值       get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));       rwlock_init(&queue->syn_wait_lock);       queue->rskq_accept_head = NULL;       lopt->nr_table_entries = nr_table_entries;            write_lock_bh(&queue->syn_wait_lock);       queue->listen_opt = lopt;       write_unlock_bh(&queue->syn_wait_lock);       return 0;    } 整个过程中,先计算request_sock的大小并申请空间,然后初始化request_sock_queue的相应成员的值;
 
TCP_LISTEN的socket管理
在《端口管理》一文中提到管理socket的哈希表结构inet_hashinfo,其中的成员listening_hash用于存放处于TCP_LISTEN状态的sock;
当socket通过listen()调用完成等待连接队列的初始化后,需要将当前sock放到该结构体中:
    if (!sk->sk_prot->get_port(sk, inet->num)) {      // 这里再次判断端口是否被占用      inet->sport = htons(inet->num);      sk_dst_reset(sk);      // 将当前socket哈希到inet_hashinfo中      sk->sk_prot->hash(sk);      return 0;    } 这里调用了net/ipv4/Inet_hashtables.c:inet_hash()方法:
    void inet_hash(struct sock *sk)    {         if (sk->sk_state != TCP_CLOSE) {                  local_bh_disable();                  __inet_hash(sk);                  local_bh_enable();         }    }    static void __inet_hash(struct sock *sk)    {         // 取得inet_hashinfo结构         struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;         struct hlist_head *list;         rwlock_t *lock;         // 状态检查         if (sk->sk_state != TCP_LISTEN) {                  __inet_hash_nolisten(sk);                  return;         }         BUG_TRAP(sk_unhashed(sk));         // 计算hash值,取得链表         list = &hashinfo->listening_hash;         lock = &hashinfo->lhash_lock;                inet_listen_wlock(hashinfo);         // 将sock添加到链表中         __sk_add_node(sk, list);         sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);         write_unlock(lock);         wake_up(&hashinfo->lhash_wait);    } 了解到这里,回答文初提出的3个问题,应该没什么问题了吧 :)
页: [1]
查看完整版本: Linux内核网络协议栈8—socket监听