Chinaunix首页 | 论坛 | 博客
  • 博客访问: 107671
  • 博文数量: 51
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 12
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-08 10:52
文章分类

全部博文(51)

文章存档

2016年(5)

2015年(3)

2014年(43)

我的朋友

分类: LINUX

2014-03-07 08:16:09

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

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

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

  1. 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库函数对应的内核处理函数:

  1. asmlinkage long sys_listen(int fd, int backlog)
  2. {
  3.    struct socket *sock;
  4.    int err, fput_needed;
  5.    int somaxconn;

  6.    // 根据文件描述符取得内核中的socket
  7.    sock = sockfd_lookup_light(fd, &err, &fput_needed);
  8.    if (sock) {
  9.        // 根据系统中的设置调整参数backlog
  10.        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
  11.        if ((unsigned)backlog > somaxconn)
  12.            backlog = somaxconn;

  13.        err = security_socket_listen(sock, backlog);
  14.        // 调用相应协议簇的listen函数
  15.        if (!err)
  16.            err = sock->ops->listen(sock, backlog);

  17.        fput_light(sock->file, fput_needed);
  18.    }
  19.    return err;
  20. }

根据《创建socket》一文的介绍,例子中,这里sock->ops->listen(sock, backlog)实际上调用的是net/ipv4/Af_inet.c:inet_listen()函数:

  1. int inet_listen(struct socket *sock, int backlog)
  2. {
  3.    struct sock *sk = sock->sk;
  4.    unsigned char old_state;
  5.    int err;

  6.    lock_sock(sk);

  7.    err = -EINVAL;
  8.    // 1 这里首先检查socket的状态和类型,如果状态或类型不正确,返回出错信息
  9.    if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
  10.        goto out;

  11.    old_state = sk->sk_state;
  12.    // 2 这里检查sock的状态是否是TCP_CLOSE或TCP_LISTEN,如果不是,返回出错信息
  13.    if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
  14.        goto out;

  15.    /* Really, if the socket is already in listen state
  16.     * we can only allow the backlog to be adjusted.
  17.     */
  18.    // 3 当sock的状态不是TCP_LISTEN时,做监听相关的初始化
  19.    if (old_state != TCP_LISTEN) {
  20.        err = inet_csk_listen_start(sk, backlog);
  21.    if (err)
  22.        goto out;
  23.    }
  24.    // 4 设置sock的最大并发连接请求数
  25.    sk->sk_max_ack_backlog = backlog;
  26.    err = 0;

  27. out:
  28.    release_sock(sk);
  29.    return err;
  30. }

上面的代码中,有点值得注意的是,当sock状态已经是TCP_LISTEN时,也可以继续调用listen()库函数,其作用是设置sock的最大并发连接请求数;
下面看看inet_csk_listen_start()函数:

  1. int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
  2. {

  3.   struct inet_sock *inet = inet_sk(sk);
  4.   struct inet_connection_sock *icsk = inet_csk(sk);
  5.   // 初始化连接等待队列
  6.   int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);

  7.   if (rc != 0)
  8.       return rc;

  9.   sk->sk_max_ack_backlog = 0;
  10.   sk->sk_ack_backlog = 0;
  11.   inet_csk_delack_init(sk);

  12.   // 设置sock的状态为TCP_LISTEN
  13.   sk->sk_state = TCP_LISTEN;
  14.   if (!sk->sk_prot->get_port(sk, inet->num)) {
  15.       inet->sport = htons(inet->num);
  16.       sk_dst_reset(sk);
  17.       sk->sk_prot->hash(sk);
  18.       return 0;
  19.   }

  20.   sk->sk_state = TCP_CLOSE;
  21.   __reqsk_queue_destroy(&icsk->icsk_accept_queue);

  22.   return -EADDRINUSE;
  23. }

这里nr_table_entries是参数backlog经过最大值调整后的值;

相关数据结构
先看下接下来的代码中提到了几个数据结构,一起来看一下:

1request_sock

  1. struct request_sock {
  2.        struct request_sock *dl_next; /* Must be first */
  3.        u16 mss;
  4.        u8 retrans;
  5.        u8 cookie_ts; /* syncookie: encode tcpopts in timestamp */

  6.        /* The following two fields can be easily recomputed I think -AK */
  7.        u32 window_clamp; /* window clamp at creation time */
  8.        u32 rcv_wnd; /* rcv_wnd offered first time */
  9.        u32 ts_recent;
  10.        unsigned long expires;
  11.        const struct request_sock_ops *rsk_ops;
  12.        struct sock *sk;
  13.        u32 secid;
  14.        u32 peer_secid;
  15. };

socket在侦听的时候,那些来自其它主机的tcp socket的连接请求一旦被接受(完成三次握手协议),便会建立一个request_sock,建立与请求socket之间的一个tcp连接。该request_sock会被放在一个先进先出的队列中,等待accept系统调用的处理;

2listen_sock

  1. struct listen_sock {
  2.        u8 max_qlen_log;

  3.        /* 3 bytes hole, try to use */
  4.        int qlen;
  5.        int qlen_young;
  6.        int clock_hand;
  7.        u32 hash_rnd;
  8.        u32 nr_table_entries;
  9.        struct request_sock *syn_table[0];
  10. };

新建立的request_sock就存放在syn_table中;这是一个哈希数组,总共有nr_table_entries项;

成员max_qlen_log2的对数的形式表示request_sock队列的最大值;

qlen是队列的当前长度;

hash_rnd是一个随机数,计算哈希值用;

3request_sock_queue

  1. struct request_sock_queue {
  2.        struct request_sock *rskq_accept_head;
  3.        struct request_sock *rskq_accept_tail;
  4.        rwlock_t syn_wait_lock;
  5.        u16 rskq_defer_accept;

  6.        /* 2 bytes hole, try to pack */
  7.        struct listen_sock *listen_opt;
  8. };

结构体struct request_sock_queue中的rskq_accept_headrskq_accept_tail分别指向request_sock队列的队列头和队列尾;

 

等待连接队列初始化

先看下reqsk_queue_alloc()的源代码:

  1. int reqsk_queue_alloc(struct request_sock_queue *queue,
  2.              unsigned int nr_table_entries)
  3. {
  4.    size_t lopt_size = sizeof(struct listen_sock);
  5.    struct listen_sock *lopt;

  6.    // 1 控制nr_table_entries在8~ sysctl_max_syn_backlog之间
  7.    nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
  8.    nr_table_entries = max_t(u32, nr_table_entries, 8);
  9.    // 2 向上取2的幂
  10.    nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
  11.    // 3 申请等待队列空间
  12.    lopt_size += nr_table_entries * sizeof(struct request_sock *);

  13.    if (lopt_size > PAGE_SIZE)
  14.        lopt = __vmalloc(lopt_size,
  15.            GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
  16.            PAGE_KERNEL);
  17.    else
  18.        lopt = kzalloc(lopt_size, GFP_KERNEL);

  19.    if (lopt == NULL)
  20.        return -ENOMEM;

  21.    // 4 设置listen_sock的成员max_qlen_log最小为3,最大为nr_table_entries的对数
  22.    for (lopt->max_qlen_log = 3;
  23.         (1 << lopt->max_qlen_log) < nr_table_entries;
  24.         lopt->max_qlen_log++);
  25.  
  26.    // 5 相关字段赋值
  27.    get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
  28.    rwlock_init(&queue->syn_wait_lock);
  29.    queue->rskq_accept_head = NULL;
  30.    lopt->nr_table_entries = nr_table_entries;
  31.  
  32.    write_lock_bh(&queue->syn_wait_lock);
  33.    queue->listen_opt = lopt;
  34.    write_unlock_bh(&queue->syn_wait_lock);

  35.    return 0;
  36. }

整个过程中,先计算request_sock的大小并申请空间,然后初始化request_sock_queue的相应成员的值;


TCP_LISTENsocket管理

在《端口管理》一文中提到管理socket的哈希表结构inet_hashinfo,其中的成员listening_hash[INET_LHTABLE_SIZE]用于存放处于TCP_LISTEN状态的sock

socket通过listen()调用完成等待连接队列的初始化后,需要将当前sock放到该结构体中:

  1. if (!sk->sk_prot->get_port(sk, inet->num)) {
  2.   // 这里再次判断端口是否被占用
  3.   inet->sport = htons(inet->num);
  4.   sk_dst_reset(sk);
  5.   // 将当前socket哈希到inet_hashinfo中
  6.   sk->sk_prot->hash(sk);
  7.   return 0;
  8. }

这里调用了net/ipv4/Inet_hashtables.c:inet_hash()方法:

  1. void inet_hash(struct sock *sk)
  2. {
  3.        if (sk->sk_state != TCP_CLOSE) {
  4.               local_bh_disable();
  5.               __inet_hash(sk);
  6.               local_bh_enable();
  7.        }
  8. }

  9. static void __inet_hash(struct sock *sk)
  10. {
  11.        // 取得inet_hashinfo结构
  12.        struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
  13.        struct hlist_head *list;
  14.        rwlock_t *lock;

  15.        // 状态检查
  16.        if (sk->sk_state != TCP_LISTEN) {
  17.               __inet_hash_nolisten(sk);
  18.               return;
  19.        }

  20.        BUG_TRAP(sk_unhashed(sk));
  21.        // 计算hash值,取得链表
  22.        list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
  23.        lock = &hashinfo->lhash_lock;
  24.  
  25.        inet_listen_wlock(hashinfo);
  26.        // 将sock添加到链表中
  27.        __sk_add_node(sk, list);
  28.        sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
  29.        write_unlock(lock);
  30.        wake_up(&hashinfo->lhash_wait);
  31. }

了解到这里,回答文初提出的3个问题,应该没什么问题了吧J

阅读(546) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~