bind的实现:

先来介绍几个地址结构.

struct sockaddr 其实相当于一个基类的地址结构,其他的结构都能够直接转到sockaddr.举个例子比如当sa_family为PF_INET时,sa_data就包含了端口号和ip地址(in_addr结构).

Java代码  
  1. struct sockaddr {
  2. sa_family_t sa_family;  /* address family, AF_xxx   */
  3. char        sa_data[14];    /* 14 bytes of protocol address */
  4. };

接下来就是sockaddr_in ,它表示了所有的ipv4的地址结构.可以看到他也就相当于sockaddr 的一个子类.

Java代码  
  1. struct sockaddr_in {
  2. sa_family_t       sin_family; /* Address family       */
  3. __be16        sin_port;   /* Port number          */
  4. struct in_addr    sin_addr;   /* Internet address     */
  5. /* Pad to size of `struct sockaddr'. */
  6. unsigned char     __pad[__SOCK_SIZE__ - sizeof(short int) -
  7. sizeof(unsigned short int) - sizeof(struct in_addr)];
  8. };

这里还有一个内核比较新的地质结构sockaddr_storage,他可以容纳所有类型的套接口结构,比如ipv4,ipv6..可以看到它是强制对齐的,相比于sockaddr.

Java代码  
  1. struct __kernel_sockaddr_storage {
  2. unsigned short  ss_family;      /* address family */
  3. ///每个协议实现自己的地址结构.
  4. char        __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
  5. /* space to achieve desired size, */
  6. /* _SS_MAXSIZE value minus size of ss_family */
  7. } __attribute__ ((aligned(_K_SS_ALIGNSIZE)));   /* force desired alignment */

接下来看几个和bind相关的数据结构:

第一个是inet_hashinfo,它主要用来管理 tcp的bind hash bucket(在tcp的初始化函数中会将tcp_hashinfo初始化.然后在tcp_prot中会将tcp_hashinfo付给结构体h,然后相应的我们就可以通过sock中的sock_common域来存取这个值).后面我们会分析这个流程.

Java代码  
  1. struct inet_hashinfo {
  2. /* This is for sockets with full identity only.  Sockets here will
  3. * always be without wildcards and will have the following invariant:
  4. *
  5. *          TCP_ESTABLISHED <= sk->sk_state < TCP_CLOSE
  6. *
  7. * TIME_WAIT sockets use a separate chain (twchain).
  8. */
  9. ///下面会分析这个结构.
  10. struct inet_ehash_bucket    *ehash;
  11. rwlock_t            *ehash_locks;
  12. unsigned int            ehash_size;
  13. unsigned int            ehash_locks_mask;
  14. /* Ok, let's try this, I give up, we do need a local binding
  15. * TCP hash as well as the others for fast bind/connect.
  16. */
  17. ///表示所有的已经在使用的端口号的信息.这里bhash也就是一个hash链表,而链表的元素是inet_bind_bucket,紧接着我们会分析这个结构.
  18. struct inet_bind_hashbucket *bhash;
  19. unsigned int            bhash_size;
  20. /* Note : 4 bytes padding on 64 bit arches */
  21. /* All sockets in TCP_LISTEN state will be in here.  This is the only
  22. * table where wildcard'd TCP sockets can exist.  Hash function here
  23. * is just local port number.
  24. */
  25. ///listening_hash表示所有的处于listen状态的socket.
  26. struct hlist_head       listening_hash[INET_LHTABLE_SIZE];
  27. /* All the above members are written once at bootup and
  28. * never written again _or_ are predominantly read-access.
  29. *
  30. * Now align to a new cache line as all the following members
  31. * are often dirty.
  32. */
  33. rwlock_t            lhash_lock ____cacheline_aligned;
  34. atomic_t            lhash_users;
  35. wait_queue_head_t       lhash_wait;
  36. struct kmem_cache           *bind_bucket_cachep;
  37. };

struct inet_ehash_bucket管理所有的tcp状态在TCP_ESTABLISHED和TCP_CLOSE之间的socket.这里要注意,twchain表示处于TIME_WAIT的socket.

Java代码  
  1. struct inet_ehash_bucket {
  2. struct hlist_head chain;
  3. struct hlist_head twchain;
  4. };

inet_bind_bucket结构就是每个使用的端口的信息,最终会把它链接到bhash链表中.

Java代码  
  1. struct inet_bind_bucket {
  2. struct net      *ib_net;
  3. ///端口号
  4. unsigned short      port;
  5. ///表示这个端口是否能够被重复使用.
  6. signed short        fastreuse;
  7. ///指向下一个端口的inet_bind_bucket 结构.
  8. struct hlist_node   node;
  9. ///也就是使用这个端口的socket链表
  10. struct hlist_head   owners;
  11. };

最后一个结构是tcp_hashinfo他在 tcp_init中被初始化,而tcp_init是在inet_init中被初始化的.然后tcp_hashinfo会被赋值给tcp_proto和sock的sk_prot域.

Java代码  
  1. struct inet_hashinfo __cacheline_aligned tcp_hashinfo = {
  2. .lhash_lock  = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock),
  3. .lhash_users = ATOMIC_INIT(0),
  4. .lhash_wait  = __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.lhash_wait),
  5. };

然后来看bind的实现,bind对应的系统调用是sys_bind:

Java代码  
  1. asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
  2. {
  3. struct socket *sock;
  4. struct sockaddr_storage address;
  5. int err, fput_needed;
  6. ///通过fd查找相应的socket,如果不存在则返回错误.
  7. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  8. if (sock) {
  9. ///用户空间和内核的地址拷贝.
  10. err = move_addr_to_kernel(umyaddr, addrlen, (struct sockaddr *)&address);
  11. if (err >= 0) {
  12. err = security_socket_bind(sock,
  13. (struct sockaddr *)&address,
  14. addrlen);
  15. if (!err)
  16. ///调用inet_bind方法.
  17. err = sock->ops->bind(sock,
  18. (struct sockaddr *)
  19. &address, addrlen);
  20. }
  21. ///将socket对应的file结构的引用计数.
  22. fput_light(sock->file, fput_needed);
  23. }
  24. return err;
  25. }

sockfd_lookup_light主要是查找fd对应的socket

Java代码  
  1. static struct socket *sockfd_lookup_light(int fd, int *err, int *fput_needed)
  2. {
  3. struct file *file;
  4. struct socket *sock;
  5. *err = -EBADF;
  6. ///通过fd得到对应的file结构
  7. file = fget_light(fd, fput_needed);
  8. if (file) {
  9. ///我们在sock_map_fd通过sock_attach_fd中已经把file的private域赋值为socket,因此这里就直接返回socket.
  10. sock = sock_from_file(file, err);
  11. if (sock)
  12. return sock;
  13. fput_light(file, *fput_needed);
  14. }
  15. return NULL;
  16. }

然后来看inet_bind的实现.

Java代码  
  1. int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
  2. {
  3. ///取得绑定地址.以及相关的socket和inet_sock.
  4. struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
  5. struct sock *sk = sock->sk;
  6. struct inet_sock *inet = inet_sk(sk);
  7. unsigned short snum;
  8. int chk_addr_ret;
  9. int err;
  10. /* If the socket has its own bind function then use it. (RAW) */
  11. if (sk->sk_prot->bind) {
  12. err = sk->sk_prot->bind(sk, uaddr, addr_len);
  13. goto out;
  14. }
  15. err = -EINVAL;
  16. if (addr_len < sizeof(struct sockaddr_in))
  17. goto out;
  18. ///得到地址类型,比如广播地址之类的.
  19. chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
  20. err = -EADDRNOTAVAIL;
  21. ///主要是判断绑定的地址不是本地时的一些条件判断.
  22. if (!sysctl_ip_nonlocal_bind &&
  23. !inet->freebind &&
  24. addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
  25. chk_addr_ret != RTN_LOCAL &&
  26. chk_addr_ret != RTN_MULTICAST &&
  27. chk_addr_ret != RTN_BROADCAST)
  28. goto out;
  29. ///得到端口号.
  30. snum = ntohs(addr->sin_port);
  31. err = -EACCES;
  32. ///主要是端口号小于prot_sock(1024)必须得有root权限.如果没有则退出.capable就是用来判断权限的.
  33. if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
  34. goto out;
  35. /*      We keep a pair of addresses. rcv_saddr is the one
  36. *      used by hash lookups, and saddr is used for transmit.
  37. *
  38. *      In the BSD API these are the same except where it
  39. *      would be illegal to use them (multicast/broadcast) in
  40. *      which case the sending device address is used.
  41. */
  42. lock_sock(sk);
  43. /* Check these errors (active socket, double bind). */
  44. err = -EINVAL;
  45. ///检测状态是否为close.如果是close状态,说明这个socket前面已经bind过了.而num只有当raw socket时才会不为0
  46. if (sk->sk_state != TCP_CLOSE || inet->num)
  47. goto out_release_sock;
  48. ///设置相应的地址.rcv_saddr是通过hash查找的源地址,而saddr是ip层使用的源地址(ip头的源地址).
  49. inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
  50. ///如果是多播或者广播,设置saddr.
  51. if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
  52. inet->saddr = 0;  /* Use device */
  53. ///这里get_port用来发现我们绑定的端口,是否被允许使用.而get_port在tcp中,被实例化为inet_csk_get_port,接近着我们会分析它的实现.
  54. if (sk->sk_prot->get_port(sk, snum)) {
  55. inet->saddr = inet->rcv_saddr = 0;
  56. err = -EADDRINUSE;
  57. goto out_release_sock;
  58. }
  59. ///这两个锁不太理解.不知道谁能解释下.
  60. if (inet->rcv_saddr)
  61. sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
  62. if (snum)
  63. sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
  64. ///设置源端口
  65. inet->sport = htons(inet->num);
  66. ///目的地址和目的端口,暂时设为0
  67. inet->daddr = 0;
  68. inet->dport = 0;
  69. sk_dst_reset(sk);
  70. err = 0;
  71. out_release_sock:
  72. release_sock(sk);
  73. out:
  74. return err;
  75. }

这里我先来介绍下inet_csk_get_port的流程.

当绑定的port为0时,这时也就是说需要kernel来分配一个新的port. 
1 首先得到系统的port范围.

2  随机分配一个port.

3 从bhash中得到当前随机分配的端口的链表(也就是inet_bind_bucket链表).

4 遍历这个链表(链表为空的话,也说明这个port没有被使用),如果这个端口已经被使用,则将端口号加一,继续循环,直到找到当前没有被使用的port,也就是没有在bhash中存在的port.

5 新建一个inet_bind_bucket,并插入到bhash中.

当指定port时.

1 从bhash中根据hash值(port计算的)取得当前指定端口对应的inet_bind_bucket结构.

2 如果bhash中存在,则说明,这个端口已经在使用,因此需要判断这个端口是否允许被reuse.

3 如果不存在,则步骤和上面的第5部一样.

Java代码  
  1. int inet_csk_get_port(struct sock *sk, unsigned short snum)
  2. {
  3. struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
  4. struct inet_bind_hashbucket *head;
  5. struct hlist_node *node;
  6. struct inet_bind_bucket *tb;
  7. int ret;
  8. struct net *net = sock_net(sk);
  9. local_bh_disable();
  10. if (!snum) {
  11. ///端口为0,也就是需要内核来分配端口.
  12. int remaining, rover, low, high;
  13. ///得到端口范围.
  14. inet_get_local_port_range(&low, &high);
  15. remaining = (high - low) + 1;
  16. rover = net_random() % remaining + low;
  17. ///循环来得到一个当前没有使用的端口.
  18. do {
  19. ///通过端口为key,来得到相应的inet_bind_bucket
  20. head = &hashinfo->bhash[inet_bhashfn(net, rover,
  21. hashinfo->bhash_size)];
  22. spin_lock(&head->lock);
  23. inet_bind_bucket_for_each(tb, node, &head->chain)
  24. if (tb->ib_net == net && tb->port == rover)
  25. ///说明这个端口已被使用,因此需要将端口加1,重新查找.
  26. goto next;
  27. break;
  28. next:
  29. spin_unlock(&head->lock);
  30. ///如果端口大于最大值,则将它赋值为最小值(这是因为我们这个端口是随机值,因此有可能很多端口就被跳过了),重新查找.
  31. if (++rover > high)
  32. rover = low;
  33. } while (--remaining > 0);
  34. /* Exhausted local port range during search?  It is not
  35. * possible for us to be holding one of the bind hash
  36. * locks if this test triggers, because if 'remaining'
  37. * drops to zero, we broke out of the do/while loop at
  38. * the top level, not from the 'break;' statement.
  39. */
  40. ret = 1;
  41. if (remaining <= 0)
  42. goto fail;
  43. ///将要分配的端口号.
  44. snum = rover;
  45. } else {
  46. ///指定端口号的情况.和上面的方法差不多,只不过只需要一次.
  47. head = &hashinfo->bhash[inet_bhashfn(net, snum,
  48. hashinfo->bhash_size)];
  49. spin_lock(&head->lock);
  50. inet_bind_bucket_for_each(tb, node, &head->chain)
  51. if (tb->ib_net == net && tb->port == snum)
  52. goto tb_found;
  53. }
  54. tb = NULL;
  55. goto tb_not_found;
  56. tb_found:
  57. ///用来处理端口号已经被使用的情况.他被使用的socket不为空的情况.
  58. if (!hlist_empty(&tb->owners)) {
  59. ///fastreuse大于0说明其他的socket允许另外的socket也使用这个端口,而reuse表示当前的端口也允许和其他的端口分享这个port.并且socket的状态必须是TCP_LISTEN,才能做这个判断.
  60. if (tb->fastreuse > 0 &&
  61. sk->sk_reuse && sk->sk_state != TCP_LISTEN) {
  62. goto success;
  63. } else {
  64. ret = 1;
  65. ///如果出错,调用inet_csk_bind_conflict.主要是有可能一些使用这个端口的socket,有可能使用不同的ip地址.此时,我们是可以使用这个端口的.
  66. if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb))
  67. goto fail_unlock;
  68. }
  69. }
  70. tb_not_found:
  71. ret = 1;
  72. ///重新分配一个inet_bind_bucket,并链接到bhash.
  73. if (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep,
  74. net, head, snum)) == NULL)
  75. goto fail_unlock;
  76. if (hlist_empty(&tb->owners)) {
  77. ///设置当前端口的fastreuse,这个域也只能是处于listen的socket才能设置.
  78. if (sk->sk_reuse && sk->sk_state != TCP_LISTEN)
  79. tb->fastreuse = 1;
  80. else
  81. tb->fastreuse = 0;
  82. } else if (tb->fastreuse &&
  83. (!sk->sk_reuse || sk->sk_state == TCP_LISTEN))
  84. tb->fastreuse = 0;
  85. success:
  86. ///将这个socket加到这个端口的ower中.
  87. if (!inet_csk(sk)->icsk_bind_hash)
  88. inet_bind_hash(sk, tb, snum);
  89. WARN_ON(inet_csk(sk)->icsk_bind_hash != tb);
  90. ret = 0;
  91. fail_unlock:
  92. spin_unlock(&head->lock);
  93. fail:
  94. local_bh_enable();
  95. return ret;
  96. }

在看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的时候就直接从这个队列中读取了.

Java代码  
  1. struct request_sock_queue {
  2. ///一个指向头,一个指向结尾.
  3. struct request_sock *rskq_accept_head;
  4. struct request_sock *rskq_accept_tail;
  5. rwlock_t        syn_wait_lock;
  6. u8          rskq_defer_accept;
  7. /* 3 bytes hole, try to pack */
  8. ///相应的listen_socket结构.
  9. struct listen_sock  *listen_opt;
  10. };

listen_sock 表示一个处于listening状态的socket.

Java代码  
  1. struct listen_sock {
  2. ///log_2 of maximal queued SYNs/REQUESTs ,这里不太理解这个域的作用.
  3. u8          max_qlen_log;
  4. /* 3 bytes hole, try to use */
  5. ///当前的半连接队列的长度.
  6. int         qlen;
  7. ///也是指当前的半开连接队列长度,不过这个值会当重传syn/ack的时候(这里要注意是这个syn/ack第一次重传的时候才会减一)自动减一.
  8. int         qlen_young;
  9. int         clock_hand;
  10. u32         hash_rnd;
  11. ///这个值表示了当前的syn_backlog(半开连接队列)的最大值
  12. u32         nr_table_entries;
  13. ///半连接队列.
  14. struct request_sock *syn_table[0];
  15. };

最后来看下request_sock,它保存了tcp双方传输所必需的一些域,比如窗口大小,对端速率,对端数据包序列号等等这些值.

Java代码  
  1. struct request_sock {
  2. struct request_sock     *dl_next; /* Must be first member! */
  3. ///mss值.
  4. u16             mss;
  5. u8              retrans;
  6. u8              cookie_ts; /* syncookie: encode tcpopts in timestamp */
  7. /* The following two fields can be easily recomputed I think -AK */
  8. u32             window_clamp; /* window clamp at creation time */
  9. ///窗口大小.
  10. u32             rcv_wnd;      /* rcv_wnd offered first time */
  11. u32             ts_recent;
  12. unsigned long           expires;
  13. ///这个域包含了发送ack的操作集合.
  14. const struct request_sock_ops   *rsk_ops;
  15. struct sock         *sk;
  16. u32             secid;
  17. u32             peer_secid;
  18. };

listen的对应的系统调用是sys_listen,它首先通过sockfd_lookup_light查找到相应的socket,然后调用inet_listen,大体流程和bind差不多,只不过中间调用的是inet_listen罢了.

这里还有一个概念那就是backlog,在linux中,backlog的大小指的是已完成连接队列的大小.而不是和半连接队列之和.而半开连接的大小一般是和backlog差不多大小.

而半开连接队列的最大长度是根据backlog计算的,我们后面会介绍这个.

因此我们直接来看inet_listen的实现,这个函数主要是进行一些合法性判断,然后调用inet_csk_listen_start来对相关域进行处理:

Java代码  
  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. ///判断状态(非连接状态)以及socket类型.
  9. if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
  10. goto out;
  11. old_state = sk->sk_state;
  12. ///状态必须为close或者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. ///非listen状态,需要我们处理.
  19. if (old_state != TCP_LISTEN) {
  20. err = inet_csk_listen_start(sk, backlog);
  21. if (err)
  22. goto out;
  23. }
  24. ///将backlog赋值给sk_max_ack_backlog,也就是完全连接队列最大值.
  25. sk->sk_max_ack_backlog = backlog;
  26. err = 0;
  27. out:
  28. release_sock(sk);
  29. return err;
  30. }

然后来看inet_csk_listen_start的实现.

它的主要工作是新分配一个listen socket,将它加入到inet_connection_sock的icsk_accept_queue域的listen_opt中.然后对当前使用端口进行判断.最终返回:

Java代码  
  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. ///新分配一个listen socket.
  6. int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
  7. if (rc != 0)
  8. return rc;
  9. ///先将这两个ack_backlog赋值为0.
  10. sk->sk_max_ack_backlog = 0;
  11. sk->sk_ack_backlog = 0;
  12. inet_csk_delack_init(sk);
  13. /* There is race window here: we announce ourselves listening,
  14. * but this transition is still not validated by get_port().
  15. * It is OK, because this socket enters to hash table only
  16. * after validation is complete.
  17. */
  18. ///设置状态.
  19. sk->sk_state = TCP_LISTEN;
  20. ///get_port上面已经分析过了.这里之所以还要再次判断一下端口,是为了防止多线程,也就是另一个线程在我们调用listen之前改变了这个端口的信息.
  21. if (!sk->sk_prot->get_port(sk, inet->num)) {
  22. //端口可用的情况,将端口值付给sport,并加入到inet_hashinfo(上面已经分析过)的listening_hash hash链表中.
  23. inet->sport = htons(inet->num);
  24. sk_dst_reset(sk);
  25. ///这里调用__inet_hash实现的.
  26. sk->sk_prot->hash(sk);
  27. return 0;
  28. }
  29. ///不可用,则返回错误.
  30. sk->sk_state = TCP_CLOSE;
  31. __reqsk_queue_destroy(&icsk->icsk_accept_queue);
  32. return -EADDRINUSE;
  33. }

最后我们来看下reqsk_queue_alloc的实现:

Java代码  
  1. ///半开连接的最大长度.
  2. int sysctl_max_syn_backlog = 256;
  3. int reqsk_queue_alloc(struct request_sock_queue *queue,
  4. unsigned int nr_table_entries)
  5. {
  6. size_t lopt_size = sizeof(struct listen_sock);
  7. struct listen_sock *lopt;
  8. ///在当前的nr_table_entries(也就是listen传进来的backlog)和sysctl_max_syn_backlog取一个较小的值.
  9. nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
  10. ///也就是说nr_table_entries不能小于8.
  11. nr_table_entries = max_t(u32, nr_table_entries, 8);
  12. ///其实也就是使nr_table_entries更接近于2的次幂
  13. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
  14. ///最终所要分配的listen_sock 的大小.
  15. lopt_size += nr_table_entries * sizeof(struct request_sock *);
  16. if (lopt_size > PAGE_SIZE)
  17. lopt = __vmalloc(lopt_size,
  18. GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
  19. PAGE_KERNEL);
  20. else
  21. lopt = kzalloc(lopt_size, GFP_KERNEL);
  22. if (lopt == NULL)
  23. return -ENOMEM;
  24. ///计算max_qlen_log的值,他最小要为3,最大为对nr_table_entries求以2为低的log..
  25. for (lopt->max_qlen_log = 3;
  26. (1 << lopt->max_qlen_log) < nr_table_entries;
  27. lopt->max_qlen_log++);
  28. get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
  29. rwlock_init(&queue->syn_wait_lock);
  30. queue->rskq_accept_head = NULL;
  31. ///给nr_table_entries赋值.
  32. lopt->nr_table_entries = nr_table_entries;
  33. write_lock_bh(&queue->syn_wait_lock);
  34. ///将listen_socket赋值给queue->listen_opt
  35. queue->listen_opt = lopt;
  36. write_unlock_bh(&queue->syn_wait_lock);
  37. return 0;
  38. }
首先来看下内核如何处理3次握手的半连接队列和accept队列(其实也就是server端的三次握手的状态变换).而半连接队列和accept队列在内核如何表示,我们上次已经介绍过了,这里就不介绍了.

首先我们知道当3层的数据包到达之后会调用4层的协议handle,tcp的话就是tcp_v4_rcv.如何调用可以看我前面的blog:

而在tcp_v4_rcv中,则最终会调用tcp_v4_do_rcv来处理输入数据包.在看tcp_v4_do_rcv之前,我们先来看在tcp_v4_rcv中,内核如何通过4元组(目的,源端口和地址)来查找对应得sock对象.

在分析之前,我们要知道,当一对tcp连接3次握手完毕后,内核将会重新new一个socket,这个socket中的大部分域都是与主socket相同的.而把这个新的socket的状态设置为established,而主socket的状态依旧为listen状态.

而通过前面的blog分析,我们也知道在inet_hashinfo中将处于listening状态的socket和处于TCP_ESTABLISHED与TCP_CLOSE之间的状态的socket是分开的,一个是ehash,一个是listening_hash.因此通过对应的4元组查找socket也是分开在这两个hash链表中操作的.

内核是通过调用__inet_lookup来查找socket的:

Java代码  
  1. ///在tcp_v4_rcv中的代码片段.
  2. sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,
  3. th->source, iph->daddr, th->dest, inet_iif(skb));
  4. static inline struct sock *__inet_lookup(struct net *net,
  5. struct inet_hashinfo *hashinfo,
  6. const __be32 saddr, const __be16 sport,
  7. const __be32 daddr, const __be16 dport,
  8. const int dif)
  9. {
  10. u16 hnum = ntohs(dport);
  11. struct sock *sk = __inet_lookup_established(net, hashinfo,
  12. saddr, sport, daddr, hnum, dif);
  13. return sk ? : __inet_lookup_listener(net, hashinfo, daddr, hnum, dif);
  14. }

tcp_hashinfo我们前面也已经分析过了,包含了所有tcp所用到的hash信息,比如socket,port等等.这里的查找其实就是在tcp_hashinfo中(其实是它的域ehash或者listening_hash)查找相应的socket.

我们可以看到内核在这里进行了两次查找,首先是在established状态的socket中查找,处于established状态,说明3次握手已经完成,因此这个socket可以通过简单的4元组hash在hashinfo的ehash中查找.

而当在__inet_lookup_established中没有找到时,则将会__inet_lookup_listener中查找.也就是在处于listening状态的socket中查找(这里主要是通过daddr也就是目的地址来进行匹配).

当找到对应的socket以后就会进入数据包的处理,也就是进入tcp_v4_do_rcv函数.

Java代码  
  1. int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
  2. {
  3. struct sock *rsk;
  4. ..................................................
  5. ///如果为TCP_ESTABLISHED状态,则进入相关处理
  6. if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
  7. TCP_CHECK_TIMER(sk);
  8. if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
  9. rsk = sk;
  10. goto reset;
  11. }
  12. TCP_CHECK_TIMER(sk);
  13. return 0;
  14. }
  15. ///进行包头的合法性校验.
  16. if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
  17. goto csum_err;
  18. ///进入TCP_LISTEN状态.
  19. if (sk->sk_state == TCP_LISTEN) {
  20. struct sock *nsk = tcp_v4_hnd_req(sk, skb);
  21. if (!nsk)
  22. goto discard;
  23. if (nsk != sk) {
  24. if (tcp_child_process(sk, nsk, skb)) {
  25. rsk = nsk;
  26. goto reset;
  27. }
  28. return 0;
  29. }
  30. }
  31. TCP_CHECK_TIMER(sk);
  32. ///进入其他状态的处理.除了ESTABLISHED和TIME_WAIT状态.
  33. if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
  34. rsk = sk;
  35. goto reset;
  36. }
  37. TCP_CHECK_TIMER(sk);
  38. return 0;
  39. ......................................................................
  40. }

可以看到当进来之后,会通过判断socket的不同状态来进入不同的处理.这里其实就分了3种状态,TCP_ESTABLISHED,TCP_LISTEN和剩余的的状态.

我们这里先不分析TCP_ESTABLISHED.

我们先来看当第一个syn分解到达后,内核会做怎么样处理.首先它会进入tcp_v4_hnd_req函数,这个函数我们后面会处理,这里只需要知道当为第一个syn分节时,它会返回当前socket.因此此时nsk == sk,所以我们进入tcp_rcv_state_process函数,这个函数处理除了ESTABLISHED和TIME_WAIT状态之外的所有状态.

我们这里只看他的listen状态处理,后面的话也是遇到一个状态,我们看一个状态的处理:

Java代码  
  1. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
  2. struct tcphdr *th, unsigned len)
  3. {
  4. struct tcp_sock *tp = tcp_sk(sk);
  5. ///取得对应的inet_connection_sock .
  6. struct inet_connection_sock *icsk = inet_csk(sk);
  7. int queued = 0;
  8. tp->rx_opt.saw_tstamp = 0;
  9. switch (sk->sk_state) {
  10. case TCP_LISTEN:
  11. ///当为ack分节,则返回1,而对应内核会发送一个rst给对端.
  12. if (th->ack)
  13. return 1;
  14. ///如果是rst,则忽略这个分组.
  15. if (th->rst)
  16. goto discard;
  17. ///是syn分组,因此调用对应的虚函数conn_request,而这个函数在tcpv4中被初始化为tcp_v4_conn_request.
  18. if (th->syn) {
  19. if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
  20. return 1;
  21. kfree_skb(skb);
  22. return 0;
  23. }
  24. goto discard;
  25. ............................................................
  26. }

可以看到最终会调用tcp_v4_conn_request来处理syn分组,我们接下来就来看这个函数的实现.

先来看几个相关的函数,第一个是reqsk_queue_is_full,他来判断半连接队列是否已满.其实实现很简单,就是判断qlen和max_qlen_log的大小:

Java代码  
  1. static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
  2. {
  3. return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
  4. }

第二个是sk_acceptq_is_full,它用来判断accept队列是否已满.这个也是很简单,比较当前的队列大小sk_ack_backlog与最大的队列大小sk_max_ack_backlog.

Java代码  
  1. static inline int sk_acceptq_is_full(struct sock *sk)
  2. {
  3. return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
  4. }

最后一个是tcp_openreq_init,它用来新建一个inet_request_sock,我们知道每次一个syn到达后,我们都会新建一个inet_request_sock,并加入到半连接队列.

Java代码  
  1. static inline void tcp_openreq_init(struct request_sock *req,
  2. struct tcp_options_received *rx_opt,
  3. struct sk_buff *skb)
  4. {
  5. struct inet_request_sock *ireq = inet_rsk(req);
  6. req->rcv_wnd = 0;        /* So that tcp_send_synack() knows! */
  7. req->cookie_ts = 0;
  8. tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq;
  9. req->mss = rx_opt->mss_clamp;
  10. req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;
  11. ireq->tstamp_ok = rx_opt->tstamp_ok;
  12. ireq->sack_ok = rx_opt->sack_ok;
  13. ireq->snd_wscale = rx_opt->snd_wscale;
  14. ireq->wscale_ok = rx_opt->wscale_ok;
  15. ireq->acked = 0;
  16. ireq->ecn_ok = 0;
  17. ireq->rmt_port = tcp_hdr(skb)->source;
  18. }

接下来来看tcp_v4_conn_request的实现,

Java代码  
  1. int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
  2. {
  3. struct inet_request_sock *ireq;
  4. struct tcp_options_received tmp_opt;
  5. struct request_sock *req;
  6. __be32 saddr = ip_hdr(skb)->saddr;
  7. __be32 daddr = ip_hdr(skb)->daddr;
  8. ///这个名字实在是无语,when具体表示什么不太理解,只是知道它是用来计算rtt的.
  9. __u32 isn = TCP_SKB_CB(skb)->when;
  10. struct dst_entry *dst = NULL;
  11. #ifdef CONFIG_SYN_COOKIES
  12. int want_cookie = 0;
  13. #else
  14. #define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
  15. #endif
  16. ///如果是广播或者多播,则丢掉这个包.
  17. if (skb->rtable->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
  18. goto drop;
  19. ///判断半连接队列是否已经满掉.如果满掉并且处于非timewait状态,则丢掉这个包(如果设置了SYN Cookie则会继续进行,因为SYN Cookie不需要新分配半连接队列,详细的SYN Cookie请google)
  20. if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
  21. #ifdef CONFIG_SYN_COOKIES
  22. if (sysctl_tcp_syncookies) {
  23. want_cookie = 1;
  24. } else
  25. #endif
  26. goto drop;
  27. }
  28. ///如果accept队列已满,并且qlen_young大于一就丢掉这个包,这里qlen_young大于一表示在syn队列中已经有足够多的(这里不包括重传的syn)请求了.
  29. if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
  30. goto drop;
  31. req = inet_reqsk_alloc(&tcp_request_sock_ops);
  32. if (!req)
  33. goto drop;
  34. ...................................................
  35. ///对tmp_opt进行初始化,而tcp_options_received中包含了tcp的一些选项信息(比如mss,窗口扩大因子等等)
  36. tcp_clear_options(&tmp_opt);
  37. tmp_opt.mss_clamp = 536;
  38. tmp_opt.user_mss  = tcp_sk(sk)->rx_opt.user_mss;
  39. ///对对端的tcp_options_received进行解析,并对本端得tcp_options_received进行初始化.
  40. tcp_parse_options(skb, &tmp_opt, 0);
  41. .......................................................
  42. ///这里对新的req进行初始化.
  43. tcp_openreq_init(req, &tmp_opt, skb);
  44. ...............................................
  45. ///这里将tcp_options_received保存到req中.
  46. ireq->opt = tcp_v4_save_options(sk, skb);
  47. if (!want_cookie)
  48. TCP_ECN_create_request(req, tcp_hdr(skb));
  49. if (want_cookie) {
  50. #ifdef CONFIG_SYN_COOKIES
  51. syn_flood_warning(skb);
  52. req->cookie_ts = tmp_opt.tstamp_ok;
  53. #endif
  54. isn = cookie_v4_init_sequence(sk, skb, &req->mss);
  55. }else if (!isn) {
  56. .............................................
  57. ///计算当前一个合适的isn,并返回.
  58. isn = tcp_v4_init_sequence(skb);
  59. }
  60. ///赋值发送给对端的isn
  61. tcp_rsk(req)->snt_isn = isn;
  62. ///发送syn和ack(如果设置了want_cookie则不会将这个req链接到半连接队列中.
  63. if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
  64. goto drop_and_free;
  65. ///将这个req链接到半连接队列中.
  66. inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
  67. return 0;
  68. drop_and_release:
  69. dst_release(dst);
  70. drop_and_free:
  71. reqsk_free(req);
  72. drop:
  73. return 0;
  74. }

而tcp_v4_hnd_req的主要工作是在半连接队列中看是否存在当前的socket,如果存在则说明这个有可能是最终的ack包,因此将会做一系列的合法性校验(比如重传,rst,syn等等),最终确定这个是ack后会调用对应的新建socket的虚函数syn_recv_sock.

Java代码  
  1. static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
  2. {
  3. struct tcphdr *th = tcp_hdr(skb);
  4. const struct iphdr *iph = ip_hdr(skb);
  5. struct sock *nsk;
  6. struct request_sock **prev;
  7. ///通过socket,查找对应request_sock
  8. struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
  9. iph->saddr, iph->daddr);
  10. if (req)
  11. ///如果存在则进入req的相关处理.
  12. return tcp_check_req(sk, skb, req, prev);
  13. ///不存在,则通过inet_lookup_established查找.这是因为有可能当我们进入这个函数之前,socket的状态被改变了,也就是这个socket的状态已经不是listen了.
  14. nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,
  15. th->source, iph->daddr, th->dest, inet_iif(skb));
  16. if (nsk) {
  17. if (nsk->sk_state != TCP_TIME_WAIT) {
  18. ///非tw状态返回新的socket.
  19. bh_lock_sock(nsk);
  20. return nsk;
  21. }
  22. ///如果是timewait状态则返回空.
  23. inet_twsk_put(inet_twsk(nsk));
  24. return NULL;
  25. }
  26. #ifdef CONFIG_SYN_COOKIES
  27. if (!th->rst && !th->syn && th->ack)
  28. sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
  29. #endif
  30. return sk;
  31. }

tcp_check_req最主要工作就是调用虚函数,新建一个socket,并返回.

先来看几个相关的函数,第一个是inet_csk_reqsk_queue_unlink,它主要用来从半连接队列unlink掉一个元素.:

Java代码  
  1. static inline void inet_csk_reqsk_queue_unlink(struct sock *sk,
  2. struct request_sock *req,
  3. struct request_sock **prev)
  4. {
  5. reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);
  6. }
  7. static inline void reqsk_queue_unlink(struct request_sock_queue *queue,
  8. struct request_sock *req,
  9. struct request_sock **prev_req)
  10. {
  11. write_lock(&queue->syn_wait_lock);
  12. ///处理链表.
  13. *prev_req = req->dl_next;
  14. write_unlock(&queue->syn_wait_lock);
  15. }

第二个是inet_csk_reqsk_queue_removed,它主要用来修改对应的qlen和qlen_young的值.

Java代码  
  1. static inline void inet_csk_reqsk_queue_removed(struct sock *sk,
  2. struct request_sock *req)
  3. {
  4. if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)
  5. inet_csk_delete_keepalive_timer(sk);
  6. }
  7. static inline int reqsk_queue_removed(struct request_sock_queue *queue,
  8. struct request_sock *req)
  9. {
  10. struct listen_sock *lopt = queue->listen_opt;
  11. ///如果重传数为0则说明没有重传过,因此qlen_young跟着也减一.
  12. if (req->retrans == 0)
  13. --lopt->qlen_young;
  14. return --lopt->qlen;
  15. }

最后是inet_csk_reqsk_queue_add,它用来把新的req加入到accept队列中.

Java代码  
  1. static inline void inet_csk_reqsk_queue_add(struct sock *sk,
  2. struct request_sock *req,
  3. struct sock *child)
  4. {
  5. reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
  6. }
  7. static inline void reqsk_queue_add(struct request_sock_queue *queue,
  8. struct request_sock *req,
  9. struct sock *parent,
  10. struct sock *child)
  11. {
  12. req->sk = child;
  13. sk_acceptq_added(parent);
  14. ///可以看到刚好就是request_sock_queue的rskq_accept_head与rskq_accept_tail保存accept队列.
  15. if (queue->rskq_accept_head == NULL)
  16. queue->rskq_accept_head = req;
  17. else
  18. queue->rskq_accept_tail->dl_next = req;
  19. queue->rskq_accept_tail = req;
  20. req->dl_next = NULL;
  21. }

然后再来看tcp_check_req的实现.

Java代码  
  1. struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
  2. struct request_sock *req,
  3. struct request_sock **prev)
  4. {
  5. const struct tcphdr *th = tcp_hdr(skb);
  6. __be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
  7. int paws_reject = 0;
  8. struct tcp_options_received tmp_opt;
  9. struct sock *child;
  10. tmp_opt.saw_tstamp = 0;
  11. ......................................
  12. ///如果只有rst和syn域则发送一个rst给对端.
  13. if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
  14. TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
  15. goto embryonic_reset;
  16. }
  17. ///如果是重传的syn,则重新发送syn和ack分组.
  18. if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&
  19. flg == TCP_FLAG_SYN &&
  20. !paws_reject) {
  21. req->rsk_ops->rtx_syn_ack(sk, req);
  22. return NULL;
  23. }
  24. ..........................................
  25. ///确定有设置ack分节.
  26. if (!(flg & TCP_FLAG_ACK))
  27. return NULL;
  28. ///这里主要处理TCP_DEFER_ACCEPT被设置的情况,如果它被设置,则丢掉这个包.(这是因为TCP_DEFER_ACCEPT会等待数据真正发过来才处理的,而不是最后一个ack发过来就处理)
  29. if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
  30. TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {
  31. inet_rsk(req)->acked = 1;
  32. return NULL;
  33. }
  34. ///可以创建一个新的socket了.返回一个包含新创建的socket的request结构.
  35. child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
  36. if (child == NULL)
  37. goto listen_overflow;
  38. ..................................
  39. #endif
  40. ///创建成功,则在request_sock_queue的listen_opt中unlink掉这个req.也就是从半连接队列中删除这个req.
  41. inet_csk_reqsk_queue_unlink(sk, req, prev);
  42. ///修改对应的 qlen和qlen_young的值.
  43. inet_csk_reqsk_queue_removed(sk, req);
  44. ///最后加入到accept队列中.这里注意最终是将新的socket赋值给对应的req.
  45. inet_csk_reqsk_queue_add(sk, req, child);
  46. return child;
  47. listen_overflow:
  48. if (!sysctl_tcp_abort_on_overflow) {
  49. inet_rsk(req)->acked = 1;
  50. return NULL;
  51. }
  52. embryonic_reset:
  53. NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
  54. if (!(flg & TCP_FLAG_RST))
  55. req->rsk_ops->send_reset(sk, skb);
  56. inet_csk_reqsk_queue_drop(sk, req, prev);
  57. return NULL;
  58. }

最后我们来看内核如何创建一个新的socket,tcp 协议使用tcp_v4_syn_recv_sock来实现,它做的其实很简单就是新建一个socket,并且设置状态为TCP_SYN_RECV(在inet_csk_clone中),父socket继续处于listen状态,然后对新的socket进行一些赋值,然后对一些定时器进行初始化.这里定时器我们全部都略过了,以后会专门来分析tcp中的定时器.

最后从tcp_v4_hnd_req中返回,判断是否与父socket相等,然后调用tcp_child_process函数:

这个函数主要是完成最终的三次握手,将子socket设置为TCP_ESTABLISHED然后根据条件唤醒被accept阻塞的主socket:

Java代码  
  1. int tcp_child_process(struct sock *parent, struct sock *child,
  2. struct sk_buff *skb)
  3. {
  4. int ret = 0;
  5. int state = child->sk_state;
  6. if (!sock_owned_by_user(child)) {
  7. ///完成最终的三次握手.
  8. ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),
  9. skb->len);
  10. /* Wakeup parent, send SIGIO */
  11. if (state == TCP_SYN_RECV && child->sk_state != state)
  12. ///唤醒阻塞的主socket.
  13. parent->sk_data_ready(parent, 0);
  14. } else {
  15. /* Alas, it is possible again, because we do lookup
  16. * in main socket hash table and lock on listening
  17. * socket does not protect us more.
  18. */
  19. sk_add_backlog(child, skb);
  20. }
  21. bh_unlock_sock(child);
  22. sock_put(child);
  23. return ret;
  24. }

最后来分析下在tcp_rcv_state_process中的处理当前的TCP_SYN_RECV状态,它主要是为将要到来的数据传输做一些准备,设置一些相关域.:

Java代码  
  1. case TCP_SYN_RECV:
  2. if (acceptable) {
  3. tp->copied_seq = tp->rcv_nxt;
  4. smp_mb();
  5. ///设置状态为TCP_ESTABLISHED.
  6. tcp_set_state(sk, TCP_ESTABLISHED);
  7. sk->sk_state_change(sk);
  8. ///这里的wake应该是针对epoll这类的
  9. if (sk->sk_socket)
  10. sk_wake_async(sk,
  11. SOCK_WAKE_IO, POLL_OUT);
  12. ///设置期望接收的isn号,也就是第一个字节的序列和窗口大小.
  13. tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
  14. tp->snd_wnd = ntohs(th->window) <<
  15. tp->rx_opt.snd_wscale;
  16. tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,
  17. TCP_SKB_CB(skb)->seq);
  18. .........................................................................
  19. break;

先来看下accept的实现.

其实accept的作用很简单,就是从accept队列中取出三次握手完成的socket,并将它关联到vfs上(其实操作和调用sys_socket时新建一个socket类似).然后返回.这里还有个要注意的,如果这个传递给accept的socket是非阻塞的话,就算accept队列为空,也会直接返回,而是阻塞的话就会休眠掉,等待accept队列有数据后唤醒他.

接下来我们就来看它的实现,accept对应的系统调用是 sys_accept,而他则会调用do_accept,因此我们直接来看do_accept:

Java代码  
  1. long do_accept(int fd, struct sockaddr __user *upeer_sockaddr,
  2. int __user *upeer_addrlen, int flags)
  3. {
  4. struct socket *sock, *newsock;
  5. struct file *newfile;
  6. int err, len, newfd, fput_needed;
  7. struct sockaddr_storage address;
  8. .............................................
  9. ///这个函数前面已经分析过了,也就是通过fd,得到相应的socket.
  10. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  11. if (!sock)
  12. goto out;
  13. err = -ENFILE;
  14. ///新建一个socket,也就是这个函数将要返回的socket.这里注意我们得到的是一个socket,而不是sock.下面会解释为什么这么做.
  15. if (!(newsock = sock_alloc()))
  16. goto out_put;
  17. newsock->type = sock->type;
  18. newsock->ops = sock->ops;
  19. /*
  20. * We don't need try_module_get here, as the listening socket (sock)
  21. * has the protocol module (sock->ops->owner) held.
  22. */
  23. __module_get(newsock->ops->owner);
  24. ///找到一个新的可用的文件句柄,以及file结构.是为了与刚才新建的socket关联起来.
  25. newfd = sock_alloc_fd(&newfile, flags & O_CLOEXEC);
  26. if (unlikely(newfd < 0)) {
  27. err = newfd;
  28. sock_release(newsock);
  29. goto out_put;
  30. }
  31. ///将新的socket和file关联起来.(这里所做的和我们第一篇所分析的信件socket的步骤是一样的,不理解的,可以去看我前面的blog
  32. err = sock_attach_fd(newsock, newfile, flags & O_NONBLOCK);
  33. if (err < 0)
  34. goto out_fd_simple;
  35. err = security_socket_accept(sock, newsock);
  36. if (err)
  37. goto out_fd;
  38. ///调用inet_accept
  39. err = sock->ops->accept(sock, newsock, sock->file->f_flags);
  40. if (err < 0)
  41. goto out_fd;
  42. ///这里也就是取得accept到的句柄的源地址.也就是填充传递进来的upeer_sockaddr.
  43. if (upeer_sockaddr) {
  44. if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
  45. &len, 2) < 0) {
  46. err = -ECONNABORTED;
  47. goto out_fd;
  48. }
  49. err = move_addr_to_user((struct sockaddr *)&address,
  50. len, upeer_sockaddr, upeer_addrlen);
  51. if (err < 0)
  52. goto out_fd;
  53. }
  54. /* File flags are not inherited via accept() unlike another OSes. */
  55. ///最终将新的file结构和fd关联起来,其实也就是最终将这个fd关联到当前进程的files中.
  56. fd_install(newfd, newfile);
  57. err = newfd;
  58. security_socket_post_accept(sock, newsock);
  59. out_put:
  60. ///文件描述符的引用计数加一.
  61. fput_light(sock->file, fput_needed);
  62. out:
  63. ///返回句柄.
  64. return err;
  65. .......................................
  66. }

可以看到流程很简单,最终的实现都集中在inet_accept中了.而inet_accept主要做的就是

1 调用inet_csk_accept来进行对accept队列的操作.它会返回取得的sock.

2 将从inet_csk_accept返回的sock链接到传递进来的(也就是在do_accept中new的socket)中.这里就知道我们上面为什么只需要new一个socket而不是sock了.因为sock我们是直接从accept队列中取得的.

3 设置新的socket的状态为SS_CONNECTED.

Java代码  
  1. int inet_accept(struct socket *sock, struct socket *newsock, int flags)
  2. {
  3. struct sock *sk1 = sock->sk;
  4. int err = -EINVAL;
  5. ///调用inet_csk_accept.
  6. struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
  7. if (!sk2)
  8. goto do_err;
  9. lock_sock(sk2);
  10. ///测试tcp连接的状态.
  11. WARN_ON(!((1 << sk2->sk_state) &
  12. (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)));
  13. ///将返回的sock链接到socket.
  14. sock_graft(sk2, newsock);
  15. ///设置状态.
  16. newsock->state = SS_CONNECTED;
  17. err = 0;
  18. release_sock(sk2);
  19. do_err:
  20. return err;
  21. }

inet_csk_accept就是从accept队列中取出sock然后返回.

在看他的源码之前先来看几个相关函数的实现:

首先是reqsk_queue_empty,他用来判断accept队列是否为空:

Java代码  
  1. static inline int reqsk_queue_empty(struct request_sock_queue *queue)
  2. {
  3. return queue->rskq_accept_head == NULL;
  4. }

然后是reqsk_queue_get_child,他主要是从accept队列中得到一个sock:

Java代码  
  1. static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
  2. struct sock *parent)
  3. {
  4. ///首先从accept队列中remove这个socket并返回.
  5. struct request_sock *req = reqsk_queue_remove(queue);
  6. ///取得socket.
  7. struct sock *child = req->sk;
  8. WARN_ON(child == NULL);
  9. ///这里主要是将sk_ack_backlog减一,也就是accept当前的数目减一.
  10. sk_acceptq_removed(parent);
  11. __reqsk_free(req);
  12. return child;
  13. }

这里还有一个inet_csk_wait_for_connect,它是用来在accept队列为空的情况下,休眠掉一段时间 (这里每个socket都有一个等待队列的(等待队列的用法请google,我这里就不阐述了).这里是每个调用的进程都会声明一个wait队列,然后将它连接到主的socket的等待队列链表中,然后休眠,等到唤醒.

Java代码  
  1. static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
  2. {
  3. struct inet_connection_sock *icsk = inet_csk(sk);
  4. ///定义一个waitqueue.
  5. DEFINE_WAIT(wait);
  6. int err;
  7. ..................................................
  8. for (;;) {
  9. ///这里也就是把当前的进程的等待队列挂入sk中的sk_sleep队列,sk也就是主的那个socket.
  10. prepare_to_wait_exclusive(sk->sk_sleep, &wait,
  11. TASK_INTERRUPTIBLE);
  12. release_sock(sk);
  13. ///再次判断是否为空.
  14. if (reqsk_queue_empty(&icsk->icsk_accept_queue))
  15. ///这个函数里面会休眠timeo时间(调用schedule让出cpu),或者被当accept队列有数据时唤醒(我们前面也有介绍这个)主的等待队列链表.,
  16. timeo = schedule_timeout(timeo);
  17. lock_sock(sk);
  18. err = 0;
  19. ///非空则跳出.
  20. if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
  21. break;
  22. err = -EINVAL;
  23. if (sk->sk_state != TCP_LISTEN)
  24. break;
  25. err = sock_intr_errno(timeo);
  26. if (signal_pending(current))
  27. break;
  28. ///设置错误号.
  29. err = -EAGAIN;
  30. ///时间为0则直接退出.
  31. if (!timeo)
  32. break;
  33. }
  34. ///这里也就会从sk_sleep中remove掉当前的wait队列.
  35. finish_wait(sk->sk_sleep, &wait);
  36. return err;
  37. }

然后来看inet_csk_accept的源码,这里有个阻塞和非阻塞的问题.非阻塞的话会直接返回的,就算accept队列为空.这个时侯设置errno为-EAGAIN.

Java代码  
  1. struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
  2. {
  3. struct inet_connection_sock *icsk = inet_csk(sk);
  4. struct sock *newsk;
  5. int error;
  6. lock_sock(sk);
  7. /* We need to make sure that this socket is listening,
  8. * and that it has something pending.
  9. */
  10. error = -EINVAL;
  11. ///sk也就是主socket,他的状态我们前面也讲过会一直是TCP_LISTEN.
  12. if (sk->sk_state != TCP_LISTEN)
  13. goto out_err;
  14. ///然后判断accept队列是否为空
  15. if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
  16. ///如果是O_NONBLOCK,则返回0,此时下面的inet_csk_wait_for_connect也就会立即返回.
  17. long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
  18. /* If this is a non blocking socket don't sleep */
  19. error = -EAGAIN;
  20. if (!timeo)
  21. goto out_err;
  22. ///休眠或者立即返回.
  23. error = inet_csk_wait_for_connect(sk, timeo);
  24. if (error)
  25. goto out_err;
  26. }
  27. ///得到sock并从accept队列中remove.
  28. newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
  29. WARN_ON(newsk->sk_state == TCP_SYN_RECV);
  30. out:
  31. release_sock(sk);
  32. return newsk;
  33. out_err:
  34. newsk = NULL;
  35. *err = error;
  36. goto out;
  37. }

最后来大概分析下connect的实现.它的具体流程是:

1 由fd得到socket,并且将地址复制到内核空间

2 调用inet_stream_connect进行主要的处理.

这里要注意connect也有个阻塞和非阻塞的区别,阻塞的话调用inet_wait_for_connect休眠,等待握手完成,否则直接返回.

Java代码  
  1. asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
  2. int addrlen)
  3. {
  4. struct socket *sock;
  5. struct sockaddr_storage address;
  6. int err, fput_needed;
  7. ///得到socket.
  8. sock = sockfd_lookup_light(fd, &err, &fput_needed);
  9. if (!sock)
  10. goto out;
  11. ///拷贝地址.
  12. err = move_addr_to_kernel(uservaddr, addrlen, (struct sockaddr *)&address);
  13. if (err < 0)
  14. goto out_put;
  15. err =
  16. security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
  17. if (err)
  18. goto out_put;
  19. ///调用处理函数.
  20. err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen,
  21. sock->file->f_flags);
  22. out_put:
  23. fput_light(sock->file, fput_needed);
  24. out:
  25. return err;
  26. }

然后来看inet_stream_connect,他的主要工作是:

1 判断socket的状态.只有当为SS_UNCONNECTED也就是非连接状态时才调用tcp_v4_connect来进行连接处理.

2 判断tcp的状态sk_state只能为TCPF_SYN_SENT或者TCPF_SYN_RECV,才进入相关处理.

3 如果状态合适并且socket为阻塞模式则调用inet_wait_for_connect进入休眠等待握手完成,否则直接返回,并设置错误号为EINPROGRESS.

Java代码  
  1. int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
  2. int addr_len, int flags)
  3. {
  4. struct sock *sk = sock->sk;
  5. int err;
  6. long timeo;
  7. lock_sock(sk);
  8. ............................................
  9. switch (sock->state) {
  10. default:
  11. err = -EINVAL;
  12. goto out;
  13. case SS_CONNECTED:
  14. err = -EISCONN;
  15. goto out;
  16. case SS_CONNECTING:
  17. err = -EALREADY;
  18. /* Fall out of switch with err, set for this state */
  19. break;
  20. case SS_UNCONNECTED:
  21. err = -EISCONN;
  22. if (sk->sk_state != TCP_CLOSE)
  23. goto out;
  24. ///调用tcp_v4_connect来处理连接.主要是发送syn.
  25. err = sk->sk_prot->connect(sk, uaddr, addr_len);
  26. if (err < 0)
  27. goto out;
  28. ///设置状态.
  29. sock->state = SS_CONNECTING;
  30. ///设置错误号.
  31. err = -EINPROGRESS;
  32. break;
  33. }
  34. ///和上面的处理一样,如果非阻塞返回0,否则返回timeo.
  35. timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
  36. if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
  37. ///如果非阻塞则直接返回.否则进入休眠等待三次握手完成并唤醒他.(这个函数和上面的inet_csk_wait_for_connect函数实现很类似,因此这里就不分析了)
  38. if (!timeo || !inet_wait_for_connect(sk, timeo))
  39. goto out;
  40. err = sock_intr_errno(timeo);
  41. if (signal_pending(current))
  42. goto out;
  43. }
  44. /* Connection was closed by RST, timeout, ICMP error
  45. * or another process disconnected us.
  46. */
  47. if (sk->sk_state == TCP_CLOSE)
  48. goto sock_error;
  49. ///设置socket状态.为已连接.
  50. sock->state = SS_CONNECTED;
  51. err = 0;
  52. out:
  53. release_sock(sk);
  54. return err;
  55. sock_error:
  56. err = sock_error(sk) ? : -ECONNABORTED;
  57. sock->state = SS_UNCONNECTED;
  58. if (sk->sk_prot->disconnect(sk, flags))
  59. sock->state = SS_DISCONNECTING;
  60. goto out;
  61. }

tcp_v4_connect的源码就不分析了,我这里只大概的介绍下他的流程:

1 判断地址的一些合法性.

2 调用ip_route_connect来查找出去的路由(包括查找临时端口等等).

3 设置sock的状态为TCP_SYN_SENT,并调用inet_hash_connect来查找一个临时端口(也就是我们出去的端口),并加入到对应的hash链表(具体操作和get_port很相似).

4 调用tcp_connect来完成最终的操作.这个函数主要用来初始化将要发送的syn包(包括窗口大小isn等等),然后将这个sk_buffer加入到socket的写队列.最终调用tcp_transmit_skb传输到3层.再往下的操作就可以看我前面的blog了.

最后来看下3次握手的客户端的状态变化,还是看tcp_rcv_state_process函数,这里我们进来的socket假设就是TCP_SYN_SENT状态,也就是在等待syn和ack分节:

Java代码  
  1. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
  2. struct tcphdr *th, unsigned len)
  3. {
  4. ..........................................
  5. switch (sk->sk_state) {
  6. case TCP_CLOSE:
  7. goto discard;
  8. case TCP_LISTEN:
  9. ..................................
  10. case TCP_SYN_SENT:
  11. ///进入对应的状态机处理函数.
  12. queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
  13. if (queued >= 0)
  14. return queued;
  15. /* Do step6 onward by hand. */
  16. tcp_urg(sk, skb, th);
  17. __kfree_skb(skb);
  18. tcp_data_snd_check(sk);
  19. return 0;
  20. }

然后来看tcp_rcv_synsent_state_process中的状态变化:

Java代码  
  1. static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
  2. struct tcphdr *th, unsigned len)
  3. {
  4. ..................
  5. if (th->ack) {
  6. ....................................
  7. ///如果是rst分节,则进行相关处理,
  8. if (th->rst) {
  9. tcp_reset(sk);
  10. goto discard;
  11. }
  12. ///如果过来的ack分节没有syn分节则直接丢掉这个包,然后返回.
  13. if (!th->syn)
  14. goto discard_and_undo;
  15. ..................................................
  16. ///如果校验都通过则设置状态为TCP_ESTABLISHED,下面就会发送最后一个ack分节.
  17. tcp_set_state(sk, TCP_ESTABLISHED);
  18. .......................................
  19. }
  20. ....................................................
  21. if (th->syn) {
  22. ///如果只有syn分节,则此时设置状态为TCP_SYN_RECV.
  23. tcp_set_state(sk, TCP_SYN_RECV);
  24. ...................................
  25. ///发送ack分节给对方.
  26. tcp_send_synack(sk);
  27. goto discard;
  28. #endif
  29. }
  30. ...................
  31. }

这里如果只接受到syn,则三次握手还没完成,我们还在等待最后一个ack,因此此时有数据报的话,会再次落入tcp_rcv_state_process函数:

Java代码  
  1. if (th->ack) {
  2. ///是否这个ack可被接受.
  3. int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);
  4. switch (sk->sk_state) {
  5. case TCP_SYN_RECV:
  6. if (acceptable) {
  7. tp->copied_seq = tp->rcv_nxt;
  8. smp_mb();
  9. ///设置为TCP_ESTABLISHED,三次握手完成.
  10. tcp_set_state(sk, TCP_ESTABLISHED);
  11. sk->sk_state_change(sk);
  12. ///唤醒休眠在connect的队列.
  13. if (sk->sk_socket)
  14. sk_wake_async(sk,
  15. SOCK_WAKE_IO, POLL_OUT);
  16. ........................................
  17. } else {
  18. return 1;
  19. }
  20. break;

tcp connection setup的实现相关推荐

  1. fatal: unable to access ‘‘: TCP connection reset by peer

    fatal: unable to access '': TCP connection reset by peer 3.20有用按照 只对github.com git config --global h ...

  2. IDEA部署Tomcat报错[RMI TCP Connection(6)-127.0.0.1]

    > 严重 [RMI TCP Connection(6)-127.0.0.1] > org.apache.catalina.core.StandardContext.startInterna ...

  3. linux上TCP connection timeout的原因查找

    linux上TCP connection timeout的原因查找 好久没有写文章了, 今天解决了一个网络连接超时的问题, 记录以备查看. 最近在线上nginx经常出现输出connection tim ...

  4. java rmi tcp_Tomcat启动失败报错[RMI TCP Connection(4)-127.0.0.1] [RMI TCP Connection(3)-127.0.0.1]...

    19-Feb-2019 10:04:43.069 警告 [RMI TCP Connection(4)-127.0.0.1] org.apache.tomcat.util.descriptor.web. ...

  5. 报:严重 [RMI TCP Connection(3)-127.0.0.1]

    报:严重 [RMI TCP Connection(3)-127.0.0.1] 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.tomcat.util.m ...

  6. 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core.ContainerBase.addChildInternal Contain

    tomcat 启动报错: 20-Aug-2019 11:23:42.807 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.core. ...

  7. tomcat启动报错 06-Dec-2021 13:57:50.855 严重 [RMI TCP Connection(5)-127.0.0.1]

    06-Dec-2021 13:57:50.855 严重 [RMI TCP Connection(5)-127.0.0.1] org.apache.catalina.core.StandardConte ...

  8. 严重 [RMI TCP Connection(3)-127.0.0.1]

    遇到这个问题网上搜了半天发现还是没解决,最后发现是maven库的问题 1.这个问题一般是maven库的原因,自己下载配置Repository仓库,也可以使用IDEA自动下载一个库.下面我是使用IDEA ...

  9. Tomcat出现警告:[RMI TCP Connection(3)-127.0.0.1] org.apache.tomcat.util.descriptor.web.WebXml.setVersion

    解决方案 tomcat.JDK.web.xml 对应关系,向下兼容 web.xml--version2.2--JDK1.1--Tomcat3.3 web.xml--version2.3--JDK1.3 ...

最新文章

  1. An Edge-Guided Image Interpolation Algorithm via Directional Filtering and Data Fusion【翻译】
  2. Spring Security3.1登陆验证
  3. 【数据挖掘笔记三】数据预处理
  4. mac包安装kafka
  5. iPhone开发各种图标大小
  6. Sitecore安全性第1部分:自定义角色和权限
  7. Linux内存管理机制研究
  8. mysql+rsyslog+loganalyzer的部署方案
  9. pdf在线翻译_如何免费快速地翻译pdf英文文档,并保留很好的格式?
  10. 用VC++制作MSN、QQ 的消息提示窗口
  11. java hbase 例子,java操作hbase例子
  12. 理解numpy dot函数
  13. 智鼎在线测评是测什么_人才测评工具和人才测评方法
  14. ESP8266自动下载电路分析
  15. mysql自动全量更新表_MySQL数据库自动全量备份脚本
  16. Swiper参数说明(swiper参数配置)
  17. c语言二维数组输入数据,c语言编写程序,把下面的数据输入到一个二维数组中:...
  18. 谷歌股价跌的越多,我们买的越多
  19. 深度学习之计算机视觉方向的知识结构
  20. Delphi 位运算符 shl shr or xor and not

热门文章

  1. 致力云安全服务模式 安全狗获近5000万融资
  2. 非常漂亮的Flash纯脚本生成饼图
  3. linux下挂载U盘过程
  4. 前端开发模式--MV*
  5. 2018-2019-1 20165226 《信息安全系统设计基础》第8周学习总结
  6. 大容量类 Redis 存储的场景补充-pika
  7. 分享10个实用的超绚CSS3按钮设计
  8. ajax实现文件上传
  9. golang map 排序
  10. linux c 遍历目录 及 目录下文件