深入理解TCP协议的三次握手,分析源码并跟踪握手过程

1.TCP三次握手建立连接

在TCP中,面向连接的传输需要经过三个阶段:连接建立、数据传输和连接终止。

三次握手建立连接

在我们的例子中,一个称为客户的应用程序希望使用TCP作为运输层协议来和另一个称为服务器的应用程序建立连接。

这个过程从服务器开始。服务器程序告诉它的TCP自己已准备好接受连接。这个请求称为被动打开请求。虽然服务器的TCP已准备好接受来自世界上任何一个机器的连接,但是它自己并不能完成这个连接。

客户程序发出的请求称为主动打开。打算与某个开放的服务器进行连接的客户告诉它的TCP,自己需要连接到某个特定的服务器上。TCP现在可以开始进行如下图所示的三向握手过程。

每个报文段的首部字段值都是完整的,并且可能还有一些可选字段也有相应的数值,不过,为了方便我们理解每个阶段,只画出了其中很少几个字段。图中显示了序号、确认号、控制标志(只有那些置1的)以及有关的窗口大小。这个阶段的三个步骤如下所示。

1.客户发送第一个报文段(SYN报文段),在这个报文段中只有SYN标志置为1。这个报文段的作用是同步序号。在我们的例子中,客户选择了一个随机数作为第一个序号,并把这个序号发送给服务器。这个序号称为初始序号(ISN)。 请注意,这个报文段中不包括确认号,也没有定义窗口大小。只有当一个报文段中包含了确认时,定义窗口大小才有意义。这个报文段还可以包含一些选项。请注意,SYN报文段是一个控制报文段,它不携带任何数据。但是,它消耗了一个序号。当数据传送开始时,序号就应当加1。我们可以说,SYN报文段不包含真正的数据,但是我们可以想象它包含了一个虚字节。

 SYN报文段不携带任何数据,但是它要消耗一个序号。

2.服务器发送第二个报文段,即SYN + ACK报文段,其中的两个标志(SYN和ACK)置为1。这个报文段有两个目的。首先,它是另一个方向上通信的SYN报文段。服务器使用这个报文段来同步它的初始序号,以便从服务器向客户发送字节。其次,服务器还通过ACK标志来确认已收到来自客户端的SYN报文段,同时给出期望从客户端收到的下一个序号。因为这个报文段包含了确认,所以它还需要定义接收窗口大小,即rwnd (由客户端使用)。

SYN+ACK报文段不携带数据,但要消耗一个序号。

3.客户发送第三个报文段。这仅仅是一一个ACK报文段。它使用ACK标志和确认号:字段来确认收到了第二个报文段。请注意,这个报文段的序号和SYN报文段使用的序号一样,也就是说,这个ACK报文段不消耗任何序号。客户还必须定义服务器的窗口大小。在某些实现中,连接阶段的第三个报文段可以携带客户的第一一个数据块。在这种情况下,第三个报文段必须有一个新的序号来表示数据中的第一个字节的编号。通常,第三个报文段不携带数据,因而不消耗序号。

ACK报文段如果不携带数据就不消耗序号。

2.TCP协议三次握手相关源代码

TCP的三次握手从用户程序的角度看就是客户端connect和服务端accept建立起连接时背后的完成的工作,在内核socket接口层这两个socket API函数对应着sys_connect和sys_accept函数,进一步对应着sock->opt->connect和sock->opt->accept两个函数指针,在TCP协议中这两个函数指针对应着tcp_v4_connect函数和inet_csk_accept函数。

tcp_v4_connect函数

客户端调用tcp_v4_connect函数发起一个TCP连接。tcp_v4_connect函数设置了 TCP_SYN_SENT,并调用了 tcp_connect(sk)函数来实际构造SYN并发送出去。

tcp_connect函数的作用为具体负责构造一个携带SYN标志位的TCP头并发送出去,同时还设置了计时器超时重发。

140/* This will initiate an outgoing connection. */
141int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
142{
...
171    rt = ip_route_connect(fl4, nexthop, inet->inet_saddr,
172                  RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
173                  IPPROTO_TCP,
174                  orig_sport, orig_dport, sk);
...
215    /* Socket identity is still unknown (sport may be zero).
216     * However we set state to SYN-SENT and not releasing socket
217     * lock select source port, enter ourselves into the hash tables and
218     * complete initialization after this.
219     */
220    tcp_set_state(sk, TCP_SYN_SENT);//设置TCP_SYN_SENT
...
227    rt = ip_route_newports(fl4, rt, orig_sport, orig_dport,
228                   inet->inet_sport, inet->inet_dport, sk);
...
246    err = tcp_connect(sk);//实际构造SYN报文段,并发送SYN报文段
...
264}
265EXPORT_SYMBOL(tcp_v4_connect);

inet_csk_accept函数

服务端调用inet_csk_accept函数,从队列中取出一个连接请求。

如果队列为空则通过inet_csk_wait_for_connect阻塞住等待客户端的连接,inet_csk_wait_for_connec无限for循环,一旦出现连接请求则跳出循环。

289/*
290 * This will accept the next outstanding connection.
291 */
292struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
293{
294    struct inet_connection_sock *icsk = inet_csk(sk);
295    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
296    struct sock *newsk;
297    struct request_sock *req;
298    int error;
299
300    lock_sock(sk);
301
302    /* We need to make sure that this socket is listening,
303     * and that it has something pending.
304     */
305    error = -EINVAL;
306    if (sk->sk_state != TCP_LISTEN)
307        goto out_err;
308
309    /* Find already established connection */
310    if (reqsk_queue_empty(queue)) {
311        long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
...
318        error = inet_csk_wait_for_connect(sk, timeo);//队列为空无限for循环等待
319        if (error)
320            goto out_err;
321    }
322    req = reqsk_queue_remove(queue);
323    newsk = req->sk;
324
325    sk_acceptq_removed(sk);
326    if (sk->sk_protocol == IPPROTO_TCP && queue->fastopenq != NULL) {
327        spin_lock_bh(&queue->fastopenq->lock);
328        if (tcp_rsk(req)->listener) {
329            /* We are still waiting for the final ACK from 3WHS
330             * so can‘t free req now. Instead, we set req->sk to
331             * NULL to signify that the child socket is taken
332             * so reqsk_fastopen_remove() will free the req
333             * when 3WHS finishes (or is aborted).
334             */
335            req->sk = NULL;
336            req = NULL;
337        }
...
344    return newsk;
...
350}

3.跟踪调试代码验证握手过程

给tcp_v4_connect函数和inet_csk_accept函数,还有__sys_socket,__sys_bind,__sys_listen,__sys_connect打上断点,追踪代码执行过程

追踪TCP通信的代码发现,服务器端先执行__sys_socket函数创建套接字,接着调用__sys_bind函数绑定套接字和服务器的地址,然后调用__sys_connect监听,接着会调用__sys_accept4,进入inet_csk_accept函数,此时还未有客户端请求连接,所以队列为空,进入inet_csk_wait_for_connect的for循环,直到客户端请求连接。

下面是对客户端的追踪,客户端先调用__sys_socket创建套接字,然后调用__sys_connect请求建立连接,其中tcp_v4_connect是真正实现连接的函数,此函数设置构造SYN,并发出去。

服务器端调用inet_csk_accept,结束for循环,从队列中取出连接请求,建立连接。

在设置断点追踪的同时,抓包验证三次握手的具体过程。可以发现,客户端调用__sys_connect将连接请求SYN报文段发出,是第一次握手;之后服务器调用__sys_accpt4响应连接请求,完成后两次握手过程。从调用__sys_connect到__sys_accept4函数响应连接请求并返回之间就是三次握手的时间。

原文地址:https://www.cnblogs.com/qfdzztt/p/12099198.html

时间: 2024-08-05 14:51:15

深入理解TCP协议的三次握手,分析源码并跟踪握手过程的相关文章

通俗大白话来理解TCP协议的三次握手和四次分手

通俗理解: 但是为什么一定要进行三次握手来保证连接是双工的呢,一次不行么?两次不行么?我们举一个现实生活中两个人进行语言沟通的例子来模拟三次握手. 引用网上的一些通俗易懂的例子,虽然不太正确,后面会指出,但是不妨碍我们理解,大体就是这么个理解法. 第一次对话: 老婆让甲出去打酱油,半路碰到一个朋友乙,甲问了一句:哥们你吃饭了么? 结果乙带着耳机听歌呢,根本没听到,没反应.甲心里想:跟你说话也没个音,不跟你说了,沟通失败.说明乙接受不到甲传过来的信息的情况下沟通肯定是失败的. 如果乙听到了甲说的话

深入理解TCP协议:三次握手详解

1.什么是三次握手? TCP协议建立连接时,需要三次发送数据包: 第一次:客户机向服务器端请求建立连接 第二次:服务器收到客户机的请求,发出响应 第三次:客户机收到响应 认为连接建立成功 详细过程: 名词解释: SYN - 标志位 只有第一次和第二次为1,第三次和其他任何情况都是0 ACK - 标志位 只有第一次不为1,第二,三次和其他任何情况都是1 Sequence Number 顺序号,初始值为随机数 Acknowledgment Number 确认号,下一次对收到的数据顺序号的期望 第一次

理解TCP为什么需要进行三次握手(白话)

首先简单介绍一下TCP三次握手     在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态: 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此

TCP协议的三次握手+四次断开

TCP协议的三次握手 1.TCP/IP协议概述 TCP/IP协议(Transmission Control Protocol/Internet Protocol)叫做传输控制/网际协议,又叫网络通讯协议,这个协议是Internet国际互联网络的基础.TCP/IP是网络中使用的基本的通信协议.虽然从名字上看TCP/IP包括两个协议,传输控制协议(TCP)和网际协议(IP),但TCP/IP实际上是一组协议,它包括上百个各种功能的协议,如:远程登录.文件传输和电子邮件等,而TCP协议和IP协议是保证数

TCP协议的三次握手与四次挥手过程图解

建立TCP需要三次握手才能建立,而断开连接则需要四次握手.整个过程如下图所示: 先来看看如何建立连接的. 首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源.Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了. 那如何断开连接呢?简单的过程如下: [注意]中断连接端可以是Client端,也可以是Server端. 假设Client端发起中断连接请求,也就是发送FIN报文.Server端接到FIN报文后,

深入理解TCP协议及其源代码

深入理解TCP协议及其源代码 前言 在前面实验我们分别实现了Socket 通信工具,探讨了Socket API.Socket 调用原理等.但是还没有针对某一实例进行讲解,在本实验我们将针对TCP协议进行详细分析,期待在Linux内核进行分析TCP原理. 1.Tcp基本原理 TCP是一种面向连接.可靠.基于字节流的传输协议,位于TCP/IP模型的传输层. 面向连接:不同于UDP,TCP协议需要通信双方确定彼此已经建立连接后才可以进行数据传输: 可靠:连接建立的双方在进行通信时,TCP保证了不会存在

深入理解TCP协议及其源代码——send和recv背后数据的收发过程

TCP数据发送和接收的原理 TCP连接的建立过程 TCP Socket的连接的过程是服务端先通过socket()函数创建一个socket对象,生成一个socket文件描述符,然后通过bind()函数将生成的socket绑定到要监听的地址和端口上面.绑定好了之后,使用listen()函数来监听相应的端口.而客户端是在通过socket()函数创建一个socket对象之后,通过connect()函数向被服务端监听的socket发起一个连接请求,即发起一次TCP连接的三次握手.接下来就可以就可以通过TC

TCP协议详解即实例分析

 TCP协议详解 3.1 TCP服务的特点 TCP协议相对于UDP协议的特点是面向连接.字节流和可靠传输. 使用TCP协议通信的双方必须先建立链接,然后才能开始数据的读写.双方都必须为该链接分配必要的内核资源,以还礼链接状态和连接上数据的传输.TCP链接是全双工的,即双方的数据读写可以通过一个连接进行.完成数据交换之后,通信双方都必须断开连接以释放系统资源. TCP协议的这种连接是一对一的,所以基于广播和多播(目标是多个主机地址)的应用程序不能使用TCP服务.而无连接协议UDP则非常适合于广

协议的注册与维护——ndpi源码分析

在前面的文章中,我们对ndpi中的example做了源码分析.这一次我们将尽可能深入的了解ndpi内部的结构和运作.我们将带着下面三个目的(问题)去阅读ndpi的源代码. 1.ndpi内部是怎么样注册和维护需要检测的协议呢? 2.ndpi在初始化的过程中,做了怎么样的工作? 3.ndpi在底层的实现中具体又是使用怎样的数据结构? 注:这里限于篇幅,本文章指针对使用中的初始化部分进行源码分析.主体的分析函数和具体的各个协议将在后面的文中陆续介绍.如果有不正确或者理解不到位的地方,欢迎大家一起讨论.