2.3 Listen系统调用

  在C/S模式中,服务器进程必须调用bind系统调用,这样才能确定对外提供服务的地址;客户端进程可以调用bind,这是为了确定客户端在发送请求时使用的源IP地址和端口。而listen系统调用服务器进程必须调用,对于客户端进程而言此函数无用。listen系统调用究竟干了什么?

  listen系统调用的函数原型:

int listen(int sockfd, int backlog);

  sockfd是socket文件描述符,backlog是保存等待处理的请求的队列的最大长度。来看看对应的内核代码:

1526 SYSCALL_DEFINE2(listen, int, fd, int, backlog)                                                                                             
1527 {
1528     struct socket *sock;
1529     int err, fput_needed;
1530     int somaxconn;
1531 
1532     sock = sockfd_lookup_light(fd, &err, &fput_needed);                                                                                    
1533     if (sock) {
1534         somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
1535         if ((unsigned int)backlog > somaxconn)
1536             backlog = somaxconn;                                                                                                           
1537 
1538         err = security_socket_listen(sock, backlog);                                                                                       
1539         if (!err)
1540             err = sock->ops->listen(sock, backlog);                                                                                        
1541 
1542         fput_light(sock->file, fput_needed);                                                                                               
1543     }
1544     return err;
1545 }

  sock->ops->listen指向inet_listen函数:

 195 int inet_listen(struct socket *sock, int backlog)
 196 {
 197     struct sock *sk = sock->sk;
 198     unsigned char old_state;
 199     int err;
...
 214     if (old_state != TCP_LISTEN) {
...
 222         if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 &&
 223             inet_csk(sk)->icsk_accept_queue.fastopenq == NULL) {
 224             if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0)
 225                 err = fastopen_init_queue(sk, backlog);
 226             else if ((sysctl_tcp_fastopen &
 227                   TFO_SERVER_WO_SOCKOPT2) != 0)
 228                 err = fastopen_init_queue(sk,
 229                     ((uint)sysctl_tcp_fastopen) >> 16);
 230             else
 231                 err = 0;
 232             if (err)
 233                 goto out;
 234         }
 235         err = inet_csk_listen_start(sk, backlog);
 236         if (err)
 237             goto out;
 238     }
 239     sk->sk_max_ack_backlog = backlog;  //记录backlog队列最大大小,如果之前调用过listen系统调用,则只是重新设置队列大小而已
 240     err = 0;
 241 
 242 out:
 243     release_sock(sk);
 244     return err;
 245 }

  222-233行与TFO(TCP Fast Open)功能有关,这个功能在后续章节中有详细分析。

  239行中设置的数值的含义是“已经完成三次握手并等待server端进程进行accept系统调用的连接的最大数量”,这些连接所在的队列可以称为“backlog队列”。

  listen系统调用的主要功能由inet_csk_listen_start实现:

751 int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
752 {   
753     struct inet_sock *inet = inet_sk(sk);
754     struct inet_connection_sock *icsk = inet_csk(sk);
755     int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);  //根据backlog参数初始化accept_queue
756 
757     if (rc != 0)
758         return rc;
759 
760     sk->sk_max_ack_backlog = 0;
761     sk->sk_ack_backlog = 0;
762     inet_csk_delack_init(sk);
763     
764     /* There is race window here: we announce ourselves listening,
765      * but this transition is still not validated by get_port().
766      * It is OK, because this socket enters to hash table only
767      * after validation is complete.
768      */
769     sk->sk_state = TCP_LISTEN;  //设置TCP状态机为listen
770     if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
771         inet->inet_sport = htons(inet->inet_num);
772 
773         sk_dst_reset(sk);
774         sk->sk_prot->hash(sk);
775     
776         return 0;
777     }
778 
779     sk->sk_state = TCP_CLOSE;
780     __reqsk_queue_destroy(&icsk->icsk_accept_queue);
781     return -EADDRINUSE;
782 }
  75行代码的功能是初始化backlog队列:

 40 int reqsk_queue_alloc(struct request_sock_queue *queue,
 41               unsigned int nr_table_entries)
 42 {
 43     size_t lopt_size = sizeof(struct listen_sock);
 44     struct listen_sock *lopt;
 45 
 46     nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
 47     nr_table_entries = max_t(u32, nr_table_entries, 8);
 48     nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
 49     lopt_size += nr_table_entries * sizeof(struct request_sock *);
 50     if (lopt_size > PAGE_SIZE)
 51         lopt = vzalloc(lopt_size);
 52     else
 53         lopt = kzalloc(lopt_size, GFP_KERNEL);
 54     if (lopt == NULL)
 55         return -ENOMEM;
 56 
 57     for (lopt->max_qlen_log = 3;
 58          (1 << lopt->max_qlen_log) < nr_table_entries;
 59          lopt->max_qlen_log++);  //计算socket能够保存的SYN请求的最大数量
 60 
 61     get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
 62     rwlock_init(&queue->syn_wait_lock);
 63     queue->rskq_accept_head = NULL;
 64     lopt->nr_table_entries = nr_table_entries;
 65 
 66     write_lock_bh(&queue->syn_wait_lock);
 67     queue->listen_opt = lopt;
 68     write_unlock_bh(&queue->syn_wait_lock);
 69 
 70     return 0;
 71 }

  根据43行、49行和67行,queue->listen_opt所指向的内存的大小为sizeof(struct listen_sock) + nr_table_entries * sizeof(struct request_sock *),而struct listen_sock的定义为:

 94 struct listen_sock {
 95     u8          max_qlen_log;
 96     u8          synflood_warned;
 97     /* 2 bytes hole, try to use */
 98     int         qlen;
 99     int         qlen_young;
100     int         clock_hand;
101     u32         hash_rnd;
102     u32         nr_table_entries;
103     struct request_sock *syn_table[0];
104 };  

  可以看出queue->listen_opt->syn_table是一个大小为backlog的指针数组,而这个syn_table就是用来保存SYN请求信息的。

  回到inet_csk_listen_start函数。sk->sk_prot->get_port指向inet_csk_get_port函数。在调用listen之前调用bind系统调用时已经调用过sk->sk_prot->get_port一次,这里再次调用是因为bind与listen之间存在时间窗口,进程可能在这个时间窗口中修改了socket的属性(如sk->reuse),所以需要再次检查端口以确认其可用。
  sk->sk_prot->hash指向的函数为inet_hash(IPv4)或tcp_v6_hash(IPv6),其作用是将socket加入到listen hash表中。TCPv4(即使用IPv4的TCP)和TCPv6(即使用IPv6的TCP)的hash表是同一个。

  listen系统调用功能总结:

1、设置server端TFO功能;

2、初始化accept_queue;其中的listen_opt->syn_table就是yn queue,用于保存等待三次握手完成的SYN请求的信息;rskq_accept_head和rskq_accept_tail用于保存三次握手完成并等待accept系统调用的连接的信息;

3、再次检查bind系统调用中所绑定的地址是否可用;

4、将sokcet加入到listen hash表中以便SYN请求到来时进行连接查找。

  以上功能都是server端必备二client端不需要的,故server端进程必须调用listen系统调用而client端则不需要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值