在看listen的代码之前.我们也先来看相关的数据结构:
inet_connection_sock它包含了一个icsk_accept_queue的域,这个域是一个request_sock_queue类型,.我们就先来看这个结构:
request_sock_queue也就表示一个request_sock队列.这里我们知道,tcp中分为半连接队列(处于SYN_RECVD状态)和已完成连接队列(处于established状态).这两个一个是刚接到syn,等待三次握手完成,一个是已经完成三次握手,等待accept来读取.
这里每个syn分节到来都会新建一个request_sock结构,并将它加入到listen_sock的request_sock hash表中.然后3次握手完毕后,将它放入到request_sock_queue的rskq_accept_head和rskq_accept_tail队列中.这样当accept的时候就直接从这个队列中读取了.
- struct request_sock_queue {
- ///一个指向头,一个指向结尾.
- struct request_sock *rskq_accept_head;
- struct request_sock *rskq_accept_tail;
- rwlock_t syn_wait_lock;
- u8 rskq_defer_accept;
- /* 3 bytes hole, try to pack */
- ///相应的listen_socket结构.
- struct listen_sock *listen_opt;
- };
- struct listen_sock {
- u8 max_qlen_log;
- /*qlen最大长度取对数log,即log2 (max_qlen),这个值在进入SYN/ACK定时器时有用*/
- /* 3 bytes hole, try to use */
- int qlen;
- ///当前的半连接队列的长度.
- int qlen_young;
- /*也是指当前的半开连接队列长度,不过这个值会当重传syn/ack的时候(这里要注意是这个syn/ack第一次重传的时候才会减一)自动减
- 一,也就是重传了SYN/ACK的request_sock不在是新的request_sock,在SYN/ACK定时器时有用*/
- int clock_hand;
- /*每次SYN-ACK定时器超时时,我们需要遍历SYN队列哈希表,但表太大了,所以每次都只遍历部分哈希表,而每次遍历完,将哈希索引值放在clock_hand这里,下次遍历时直接从clock_hand开始,而不用从头开始*/
- u32 hash_rnd;
- ///这个值表示了当前的syn_backlog(半开连接队列)的最大值
- u32 nr_table_entries;
- ///半连接队列.
- struct request_sock *syn_table[0];
- };
request_sock的结构就不在这里贴出来了,我们只要知道每一个SYN请求都会新建一个request_sock结构,并将它加入到listen_sock的syn_table哈希表中,然后接收端会发送一个SYN/ACK段给SYN请求端,当SYN请求端将3次握手的最后一个ACK发送给接收端后,并且接收端判断ACK正确,则将request_sock从syn_table哈希表中删除,将request_sock加入到request_sock_queue的rskq_accept_head和rskq_accept_tail队列中,最后的accept系统调用不过是判断accept队列是否存在完成3次请求的request_sock,从这个队列中将request_sock结构释放,然后在BSD层新建一个socket结构,并将它和接收端新建的子sock结构关联起来。
我们可以想到,listen系统调用必然要分配一个listen_sock结构,其实也正如此,inet_listen系统调用最终会调用inet_csk_listen_start函数,
它的主要工作是新分配一个listen socket,将它加入到inet_connection_sock的icsk_accept_queue域的listen_opt中.然后对当前使用端口进行判断.最终返回,
- 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);
- ///新分配一个listen_sock,并让request_sock_queue->listen_opt指向它
- int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
- if (rc != 0)
- return rc;
- ///先将这两个ack_backlog赋值为0.
- sk->sk_max_ack_backlog = 0;
- sk->sk_ack_backlog = 0;
- inet_csk_delack_init(sk);
- /* There is race window here: we announce ourselves listening,
- * but this transition is still not validated by get_port().
- * It is OK, because this socket enters to hash table only
- * after validation is complete.
- */
- ///设置状态.
- sk->sk_state = TCP_LISTEN;
- ///get_port上面已经分析过了.这里之所以还要再次判断一下端口,是为了防止多线程,也就是另一个线程在我们调用listen之前改变了这个端口的信息.
- if (!sk->sk_prot->get_port(sk, inet->num)) {
- //端口可用的情况,将端口值付给sport,并加入到inet_hashinfo(上面已经分析过)的listening_hash hash链表中.
- inet->sport = htons(inet->num);
- sk_dst_reset(sk);
- ///这里调用__inet_hash实现的.
- sk->sk_prot->hash(sk);
- return 0;
- }
- ///不可用,则返回错误.
- sk->sk_state = TCP_CLOSE;
- __reqsk_queue_destroy(&icsk->icsk_accept_queue);
- return -EADDRINUSE;
- }
inet_accept系统调用最终会调用inet_csk_accept,inet_csk_accept调用reqsk_queue_get_child从accept队列中取一个request_sock,得到request_sock对应的sock,然后将request_scok删除,这里的目的主要是为了得到request_soc的对应的sock,还在BSD层生成相应的sock结构,reqsk_queue_get_child代码如下(2.6.32内核):
static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
struct sock *parent)
{
struct request_sock *req = reqsk_queue_remove(queue);
struct sock *child = req->sk;
WARN_ON(child == NULL);
sk_acceptq_removed(parent);
__reqsk_free(req);
return child;
}