深入理解TCP三次握手

一、概述

  在本篇文章中我们主要研究TCP协议如何进行连接的建立。我们知道如果应用层要使用TCP进行通信,首先要进行连接的建立,在这个过程中需要进行所谓的"三次握手"。三次握手的示意图如下所示:

                      

  在进行网络通信的时候,客户端(client)和服务器端(server)都是通过socket这层操作系统提供的抽象进行编程,那么他们是如何通过socket来建立连接的呢?注意我们只研究TCP协议。下图展示了通过socket客户端和服务器端如何进行通信。

                        

  可以看到如果通过socket进行网络通信,在客户端调用connect的时候,会和服务器端建立连接。那么我们主要的研究内容就是connect这个系统调用。

二、__sys_connect

  上次的实验告诉我们,socketcall(int call, unsigned long *args是我们调用socket相关系统调用的入口函数,这个函数的call参数决定具体调用的哪个函数,一些主要函数的具体的对应关系如下所示:

1 __sys_socket     ---- call = 1 
2 __sys_bind       ---- call = 2 
3 __sys_connect    ---- call = 3 
4 __sys_listen     ---- call = 4 
5 __sys_accept     ---- call = 5 

  当客户端调用系统调用connect连接服务端的时候,经过socket系统调用入口函数的分发,最终调用的是__sys_connect()函数,下面来分析这个函数。

  

 1 SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)
 2 {
 3     struct socket *sock;
 4     struct sockaddr_storage address;
 5     int err, fput_needed;
 6  
 7     //根据fd,找到对应的socket。11     sock = sockfd_lookup_light(fd, &err, &fput_needed);
12     if (!sock)
13         goto out;
14  
15 16     err = move_addr_to_kernel(uservaddr, addrlen, &address);
17     if (err < 0)
18         goto out_put;
19  
20     err = security_socket_connect(sock, (struct sockaddr *)&address, addrlen);
21     if (err)
22         goto out_put;
23  
24     // 调用sock的连接函数27     err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags);
28  
29 out_put:
30     fput_light(sock->file, fput_needed);
31  
32 out:
33     return err;
34 }

上述代码比较重要的是调用了 sockfd_lookup_light()和 sock->ops->connect()函数。首先sockfd_lookup_light()找到具体的socket实例。socket实例结构体如下:

  

 1 struct socket {
 2   socket_state state;
 3   unsigned long flags;
 4   const struct proto_ops * ops;
 5   struct fasync_struct * fasync_list;
 6   struct file * file;
 7   struct sock * sk;
 8   wait_queue_head_t wait;
 9   short type;
10 };  

其中的ops是具体协议的操作函数指针。里面包含了对应协议的操作函数。在sock->ops->connect()处打上断点,进入其中看一下。

  可以看到他去了实现IPV4协议的底层函数中去。调用的是inet_stream_connect()。

1 int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
2 {
3     int err;
4  
5     lock_sock(sock->sk);
6     err = __inet_stream_connect(sock, uaddr,addr_len, flags);
7     release_sock(sock->sk);
8     return err;
9 }

其中调用了__inet_stream_connect函数。

  1 int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
  2               int addr_len, int flags, int is_sendmsg)
  3 {
  4     struct sock *sk = sock->sk;
  5     int err;
  6     long timeo;
  7 
  8     /*
  9      * uaddr can be NULL and addr_len can be 0 if:
 10      * sk is a TCP fastopen active socket and
 11      * TCP_FASTOPEN_CONNECT sockopt is set and
 12      * we already have a valid cookie for this socket.
 13      * In this case, user can call write() after connect().
 14      * write() will invoke tcp_sendmsg_fastopen() which calls
 15      * __inet_stream_connect().
 16      */
 17     if (uaddr) {
 18         if (addr_len < sizeof(uaddr->sa_family))
 19             return -EINVAL;
 20 
 21         if (uaddr->sa_family == AF_UNSPEC) {
 22             err = sk->sk_prot->disconnect(sk, flags);
 23             sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
 24             goto out;
 25         }
 26     }
 27 
 28     switch (sock->state) {
 29     default:
 30         err = -EINVAL;
 31         goto out;
 32     case SS_CONNECTED:
 33         err = -EISCONN;
 34         goto out;
 35     case SS_CONNECTING:
 36         if (inet_sk(sk)->defer_connect)
 37             err = is_sendmsg ? -EINPROGRESS : -EISCONN;
 38         else
 39             err = -EALREADY;
 40         /* Fall out of switch with err, set for this state */
 41         break;
 42     case SS_UNCONNECTED:    //没有建立连接
 43         err = -EISCONN;
 44         if (sk->sk_state != TCP_CLOSE)
 45             goto out;
 46 
 47         if (BPF_CGROUP_PRE_CONNECT_ENABLED(sk)) {
 48             err = sk->sk_prot->pre_connect(sk, uaddr, addr_len);
 49             if (err)
 50                 goto out;
 51         }
 52 
 53         err = sk->sk_prot->connect(sk, uaddr, addr_len);  //这一行发送一个报文,这是TCP连接的第一步
 54         if (err < 0)
 55             goto out;
 56 
 57         sock->state = SS_CONNECTING;
 58 
 59         if (!err && inet_sk(sk)->defer_connect)
 60             goto out;
 61 
 62         /* Just entered SS_CONNECTING state; the only
 63          * difference is that return value in non-blocking
 64          * case is EINPROGRESS, rather than EALREADY.
 65          */
 66         err = -EINPROGRESS;
 67         break;
 68     }
 69 
 70     timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
 71 
 72     if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {  //客户端发出请求连接后,等待服务端的响应。
 73         int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
 74                 tcp_sk(sk)->fastopen_req &&
 75                 tcp_sk(sk)->fastopen_req->data ? 1 : 0;
 76 
 77         /* Error code is set above */
 78         if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))  //等待第二次连接
 79             goto out;
 80 
 81         err = sock_intr_errno(timeo);
 82         if (signal_pending(current))
 83             goto out;
 84     }
 85 
 86     /* Connection was closed by RST, timeout, ICMP error
 87      * or another process disconnected us.
 88      */
 89     if (sk->sk_state == TCP_CLOSE)
 90         goto sock_error;
 91 
 92     /* sk->sk_err may be not zero now, if RECVERR was ordered by user
 93      * and error was received after socket entered established state.
 94      * Hence, it is handled normally after connect() return successfully.
 95      */
 96 
 97     sock->state = SS_CONNECTED;    //连接建立成功
 98     err = 0;
 99 out:
100     return err;
101 
102 sock_error:
103     err = sock_error(sk) ? : -ECONNABORTED;
104     sock->state = SS_UNCONNECTED;
105     if (sk->sk_prot->disconnect(sk, flags))
106         sock->state = SS_DISCONNECTING;
107     goto out;
108 }

上述的代码其实也很直接,检查socket的状态,如果没有建立连接,则发送一个syn报文,这是三次握手的第一次握手。然后等待服务器端的回应,若是服务器端的回应到达,则完成连接,将socket的状态改为已连接。至此connect系统调用已经完成。

  

参考自 https://www.geeksforgeeks.org/tcp-3-way-handshake-process/

    https://www.geeksforgeeks.org/socket-programming-cc/

    https://blog.csdn.net/zhangskd/article/details/45508569

猜你喜欢

转载自www.cnblogs.com/luhaipeng/p/12103903.html