[TOC]
过程
1 | 以下的分别使用 tcp 报文帧中的 标志位, 序列号位, 确认号位 |
链接建立后,接下来Client端发送的数据包将从J + 1开始,Server端发送的数据包将从K + 1开始,这里要说明的是:建立链接时,Client端宣称自己的初始序列号是J,Server端宣称自己的初始序列号是K,但是建立连接后的数据包却各自中初始序列号+1开始,这是因为SYN请求本身需要占用一个序列号
TCP 的定义
很多人尝试回答或者思考这个问题的时候其实关注点都放在了三次握手中的三次上面,这确实很重要,但是如果重新审视这个问题,我们对于『什么是连接』真的清楚?只有知道连接的定义,我们才能去尝试回答为什么 TCP 建立连接需要三次握手。
The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.
RFC 793 - Transmission Control Protocol 文档中非常清楚地定义了 TCP 中的连接是什么,我们简单总结一下:用于保证可靠性和流控制机制的信息,包括 Socket、序列号以及窗口大小叫做连接。
所以,建立 TCP 连接就是通信的双方需要对上述的三种信息达成共识,连接中的一对 Socket 是由互联网地址标志符和端口组成的,窗口大小主要用来做流控制,最后的序列号是用来追踪通信发起方发送的数据包序号,接收方可以通过序列号向发送方确认某个数据包的成功接收。
为什么需要三次握手
这篇文章主要会从以下几个方面介绍为什么我们需要通过三次握手才可以初始化 Sockets、窗口大小、初始序列号并建立 TCP 连接:
- 通过三次握手才能阻止重复历史连接的初始化;
- 通过三次握手才能对通信双方的初始序列号进行初始化;
- 讨论其他次数握手建立连接的可能性;
历史连接
最大的问题是发起创建连接的一方A在发出syn之后没有收到ack(网络原因等),它就会再发出一个syn,那么接受方B其实收到了两个 syn并对两个syn 发出了确认,那么实际上其中一个syn已经是历史连接了。TCP 选择使用三次握手来建立连接并在连接引入了 RST 这一控制消息,接收方当收到请求时会将发送方发来的 SEQ+1 发送给对方,这时由发送方来判断当前连接是否是历史连接:
- 如果当前连接是历史连接,即 SEQ 过期或者超时,那么发送方就会直接发送 RST 控制消息中止这一次连接;
- 如果当前连接不是历史连接,那么发送方就会发送 ACK 控制消息,通信双方就会成功建立连接;
初始化序列号
另一个使用三次握手的重要的原因就是通信双方都需要获得一个用于发送信息的初始化序列号,作为一个可靠的传输层协议,TCP 需要在不稳定的网络环境中构建一个可靠的传输层,网络的不确定性可能会导致数据包的缺失和顺序颠倒等问题,常见的问题可能包括:
- 数据包被发送方多次发送造成数据的重复;
- 数据包在传输的过程中被路由或者其他节点丢失;
- 数据包到达接收方可能无法按照发送顺序;
为了解决上述这些可能存在的问题,TCP 协议要求发送方在数据包中加入『序列号』字段,有了数据包对应的序列号,我们就可以:
- 接收方可以通过序列号对重复的数据包进行去重;
- 发送方会在对应数据包未被 ACK 时进行重复发送;
- 接收方可以根据数据包的序列号对它们进行重新排序;
除此之外,网络作为一个分布式的系统,其中并不存在一个用于计数的全局时钟,而 TCP 可以通过不同的机制来初始化序列号,作为 TCP 连接的接收方我们无法判断对方传来的初始化序列号是否过期,所以我们需要交由对方来判断,TCP 连接的发起方可以通过保存发出的序列号判断连接是否过期,如果让接收方来保存并判断序列号却是不现实的,这也再一次强化了我们在上一节中提出的观点 —— 避免历史错连接的初始化。
TCP 控制位
控制位
ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。
PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。
RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。
SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。
数据传输中的序列号及确认号
- 序号(Sequence Number)
- 也称为序列号,长度为32位,序号用来标识从TCP发送端向接入端发送的数据字节流进行编号,可以理解成对字节流的计数。例如一个报文段的序号为 55555,此报文段数据部分共有 11 字节,则下一个报文段的序号为 55566。
- 确认号(ack Number)
- 指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。比如 client 发送的确认号是 22222,那么 server回复的 序号就是 22222。
- 确认号还有一个更重要的功能就是对消息的确认,它等于请求的 序列号 + 数据包的长度(表示收到了这个序列号的这么多数据)
1 | 在 tcp 的数据传输过程中,一次数据请求,至少需要发送三个tcp报文,分别是 请求报文,响应报文,收到响应后的ack报文。 |
让我们来抓包看看
注意wireshark 入口不要选 wlan, 要选 loopback traffic capture
解决抓不到包的问题
今天将自己的电脑既作为客户端又作为服务端进行一个程序的测试,想着用WireShark来抓包分析一下问题,但由于WireShark只能抓取经过电脑网卡的包,由于我是使用localhost
或者127.0.0.1
进行测试的,流量是不经过电脑网卡的,所以WireShark无法抓包,一番查找之下找到了解决方法。
1 | route print |
mac
注意wireshark入口不要选wifi,要选择 loopback:lo0
1 | # 查看本机ip |
wireshark 简单使用
1 | # 过滤指定ip和端口, 这里的10.23.101.135 是我机器的端口 |
tcp 包分析
tcp 握手和挥手包分析
测试代码 go-mod/netx/tcp_demo/client.go, testConn
tcp 连接后立即发送一个请求包的分析
测试代码 go-mod/netx/tcp_demo/client.go, testSendOne
这里可以看到, 三次握手建立连接造成的后果就是,HTTP 请求最少必须在一个 RTT(从客户端到服务器一个往返的时间)后才能发送。最少的请求就是握手的最后一个 ack 包和请求包合并。
tcp 连接后睡眠一秒,然后两次请求的分析
测试代码 go-mod/netx/tcp_demo/client.go, testSendTwo
参考
最重要的参考: 为什么 TCP建立连接需要三次握手