精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-08-02
最后修改:2011-08-29
几个问题
1、listen
库函数主要做了什么?
listen( server_sockfd, 5) ;
其中,第一个参数
server_sockfd为服务端
socket所对应的文件描述符,第二个参数5
代表监听socket
能处理的最大并发连接请求数,在2.6.26
内核中,该值为
256
;
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
的最大并发连接请求数;
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_LISTEN sk->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[0]; }; 新建立的 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[INET_LHTABLE_SIZE] 用于存放处于 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[inet_sk_listen_hashfn(sk)]; 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 个问题,应该没什么问题了吧 :) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 2231 次