一:摘要概述
系列第一文TCP(一) -- 初识TCP中描述了TCP是一个面向连接的传输层协议,这也是TCP协议保证可靠性的重要一环。客户端与服务端建立连接的方式就是通过三次握手,三次握手的过程中将会交换大量数据信息。本文的目的就是详细解释TCP三次握手的过程、状态变更以及交换的初始数据信息。再次重申文章是阅读张师傅的小册总结,感兴趣的朋友可以购买,真的物超所值
二:协议标志
序号 | 标签种类 | 标签含义 | 备注 |
---|---|---|---|
1 | SYN | 请求连接数据包标志 | 三次握手时 |
2 | ACK | 确认数据包 | 数据接收方收到数据包后确认通信的数据包 |
3 | FIN | 断开数据包 | 四次挥手时传递 |
4 | RST | 强制断开 | 某些不合法操作时强制断开连接返回数据包 |
5 | PSH | 传输层别缓存,立即将数据交给应用层 |
三:连接过程
-
- 客户端发送SYN包到服务端,Hello兄弟我想创建一个连接
-
- 服务端收到客户端SYN包回复SYN+ACK,兄弟我收到连接请求可以连接
-
- 客户端回复服务端ACK包,好的,连接创建成功接下来将发送数据
四:状态变更
如第三节示例图所示:
- 服务端启动应用监听某个端口时就会处于
LISTEN
状态 - 客户端发起连接发送SYN包客户端处于
SYN-SENT
状态 - 服务端收到SYN包回复SYN+ACK后处于
SYN-RECV
状态 - 客户端收到SYN+ACK回复ACK后处于
ESTAB
状态 - 服务端收到ACK包后处于
ESTAB
状态
五: Sequence Number
连接建立的过程中可以看到有0、1信息的交换,这个信息到底是什么?有什么作用?
5.1 作用详解
TCP协议是一个可靠的协议,如果数据包丢了那么会进行重传尝试,但是数据包有很多,发送方怎么确认哪一段数据包丢失?依赖的就是Sequence Number,可以理解为该属性就是数据包的编码,方便TCP协议管理数据
5.2 初始值定义
很多资料上亦或是本文上述绘制的过程图中都将Sequence Number的初始值定义为0,现实中的该初始值真为0么?必然这个概念是错误的,也就是为了方便描述计数,所以将其初始值定义0。WireShark抓包工具中显示0是因为其自动帮我们做了处理,可以通过设置:Edit(编辑E) -- Preferences(首选项P) -- Protocols -- TCP
恢复原始值显示
实际上Sequence Number的初始值是一个随时间递增的数值,有着自己的生成算法。使用tcpdump抓包显示结果如下:
5.3 固定初始值
这时候就会有人问为什么Sequence Numer的初始值不能设置为一个固定值?有以下两个原因值得深思:
- TCP连接四元组件:src host/port + dst host/port,这些连接信息十分容易获取,如果固定初始值就使得第三方十分容易构造一个在窗口允许范围内的RST数据包关闭连接,这样导致的后果可想而知
- 后面会讲到SO_REUSEADDR参数,端口复用的情况下如果Sequence Number固定值开始,那么有可能造成新旧连接的数据包一致,就会导致服务端无法判断这个数据包到底是什么时候的数据包
六:ACK确认码
三次握手创建连接时候发现每次对方发送SYN包后都需要ACK进行确认,不仅如此,后续包括数据传输、四次挥手过程都需要ACK确认,这样才能保证数据接收方收到了传输的数据。ACK确认的值到底是多少?其实也比较简单,ACK 数值 = Sequence Number + 数据包大小,表示这个范围内的数据已经接收到,下次传递数据的时候请使用ACK数值作为你的Sequence Numer传递
七:三次握手重试
三次握手的过程其实就是客户端与服务端三次数据包传递交互的过程,既然涉及到数据包的传递那么就有可能因为网络波动等原因导致的数据包丢失。这时候发送数据包的一方就会根据情况尝试重试
7.1 SYN重试
天有不测风云,人有祸兮旦福。网络波动导致客户端发送的SYN包服务端未收到从而没有回复SYN+ACK,那么这时候客户端会如何处理?明显客户端会进行重试,也就是当客户端在一定时间内未收到服务端的ACK确认那么就会重新发送SYN包,示例如下
重试多少次?这个数值由服务器的tcp_syn_retries
决定,查看该数值命令如下:
[root@bogon ~]# cat /proc/sys/net/ipv4/tcp_syn_retries
复制代码
packetdrill构建SYN_SENT状态连接使用如下脚本:
+0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 connect(3, ..., ...) = -1
复制代码
7.2 SYN + ACK重试
服务端接收到客户端传送的SYN包之后会回复SYN+ACK,这时候如果该数据包丢失的话客户端不会回复ACK,自然当时限达到的情况下服务端需要重新发送SYN + ACK。重试的次数由参数tcp_synack_retries
空值,查看该数值命令如下:
[root@bogon ~]# cat /proc/sys/net/ipv4/tcp_synack_retries
复制代码
packetdrill构建SYN_RECV状态连接使用如下脚本:
+0 < S 0:0(0) win 65535 <mss 100>
+0 > S. 0:0(0) ack 1 <...>
复制代码