vlambda博客
学习文章列表

一文弄懂TCP协议如何保证数据传输的可靠性

工作了几年,不管是面试还是实际开发场景,与TCP协议打交道的场景屡见不鲜,网上对于TCP协议的解读性博文也不胜枚举。但是作为一个不满足面向复制粘贴的程序猿还是有必要自己再总结下,也算自己的知识积累吧。废话不多,直接面向主题吧。



TCP协议解决了什么难题??

首先得了解TCP是在OSI7层模型中的传输层工作,而且也知道工程师们对网络模型施行分层的目的是在于希望每一层所在的协议们各自分工,统一接口交互。那么,TCP在传输层需要做哪些事情呢?


保证消息的可靠性

我觉得这个答案是最标准的,而且TCP协议内其他特性几乎都是为了实现这个目标而存在的,比如:

  • 以数据流模式传输(需要理清楚数据包与流的关系)

  • 以连接为基础

  • 创建连接的三次握手

  • 消息支持分段发送

  • 重传机制

  • 流量控制(基于策略方式,可针对消息特性选择不同传输策略)

  • 拥塞控制

  • 滑动窗口

  • 校验机制

上面这些特性不排除同时满足或优化了其他方面的功能,但绝对都与保证消息可靠性这一大方向有关。


          数据单元

 

在围绕TCP协议保证消息可靠性讲解之前个人认为有必要先熟悉数据单元的知识。

数据单元是网络信息传输的基本单位。一般网络连接不允许传送任意大小的数据包,而是采用分组技术将一个数据分成若干个很小的数据包,并给每个小数据包加上一些关于此数据包的属性信息

数据单元分为

  • 服务数据单元 (Service Data Unit)

  • 协议数据单元(Protocol Data Unit PDU)

服务数据单元是指在分层网络下同层之间使用相同协议进行数据传输的计量单位。比如同在OSI七层网络模型下的数据链路层就是使用 数据帧(Frame) 作为双方数据传输的计量单位。

协议数据单元是指同一设备下发起或者接收网络信号后,在分层网络下的不同层次之间的数据传输计量单位(比如传输层与网络层通过各自接口进行数据互通);

说白话,数据单元就是在网络信息传输领域里对传输对象的一个计量单位。且需要注意的是同层网络模型下不同协议的数据单元格式是不相同。

          数据流

 

OK,有了上面的数据单元基础再来看这一小结就会容易理解很多。

以数据流模式传输是TCP协议数据传输的关键,也是后续消息分段发送、滑动窗口这些机制的基础。我个人感觉需要理解TCP协议脑海里必须有数据流概念。

举个例子能更加形象的凸显出数据流模式的样子:

你家盖了新房子需要沙子,你老爸托朋友从市场拖了一车的沙子到了家门口。这时候需要卸货,你想了个卸货办法。就是在车上用桶子将沙子填满后再提下来,然后提到家里存储沙子的地方,再继续从车上将沙子填满到桶子里,这么循环劳作,直至沙子全部卸货完毕。


上面例子里面的沙子可以表示为数据,流模式就是你拿着填满沙子的桶子从货车搬运至家里放置沙子的卸货点,桶子就是数据缓冲区,即你单次最多只能运输这么多沙子(对应TCP分段传输,每次分段传输的大小有限制,且实际上每次传输的大小都是经过复杂的运算之后得出的结果)。可以看出来这种流模式特点是具有关联性、持续性 。


          TCP协议以连接为基础

 TCP连接使客户端、服务端双方实现有状态的通讯, 这里的状态依赖于TCP连接的数据包。
我们知道以数据流模式传输的TCP会将数据进行分段传输。在分段传输的数据报文内分为报文体和报文头,报文体承载的是分段后的数据本身。报文头则存储TCP连接的属性,通过下图可以更加清晰的了解TCP报文头包含的连接属性:

一文弄懂TCP协议如何保证数据传输的可靠性

上图中URG、ACK、PSH、RST、SYN、FIN是组合在TCP报文头的一个长度6位的标识(Flags),每个标识位使的二进制值0和1表示。再次查看上图会发现除数据、选项两行外,其他每一行的大小总和都是32位。


接下来就是对上面的TCP报文头内的字段做详细简介,可以说弄明白报文头内各项字段的含义也算熟悉TCP是如何工作的了。

源端口号、目的端口号

序号

我们知道TCP是采取分段发送数据的,假设我们应用程序目的是传输1GB大小的文件,客户端 创建TCP连接后操作系统内核会分配一个初始序号ISN(Initial Sequence Number),内核通过算法保证序号的唯一性。在连接创建后,每次交互的数据报文头都会携带唯一的序列号,双方通过序列号进行确认,序列号是贯穿整个连接的。


下图通过抓包工具Wireshark抓取 请求段建立连接时的TCP数据包

一文弄懂TCP协议如何保证数据传输的可靠性

上图的Sequence number 值为 242009331 。上面为0的为相对序号,是工具方便我们追踪TCP分段传输的次序而生成出来的,简而言之就是提升可读性用的。

SYN

SYN表示建立连接,如上图Flags的值展开后发现Syn的值为1,代表了这是一条用客户端向服务端发起的连接的请求

ACK (Acknowledgment number)

Acknowledgment number用于对接收的数据进行确认的确认号。注意这里的接收与连接角色无关,不管是客户端还是服务端,都会接收对方的数据报文。通过该标识符可以在返回的报文中对当前接收的报文进行确认。

如下图就是目标机器在接收到上图客户端发起的连接创建请求后的反馈报文,从报文的ACK标识以及SYN标识可以确认服务端已收到客户端发起的连接请求。

一文弄懂TCP协议如何保证数据传输的可靠性

留意下上图的相对序号,它还是0,但是实际序号值却与发起连接创建请求的实际序号不同了。还有上面Flags的值为0x012,是一个16进制值,展开后可以查看各个Flags包含的标识位的值。

这里需要注意Flags标识位也存在一个ACK,这个是单纯的标识值,它的值只会是0或1。与上面的32位的ACK不同。 这里Flags的二进制值是010010转换为16进制就是12。


URG

URG是紧急标识,可以理解为告诉接收方报文包含了紧急数据。这个实际上用的很少,因为接收方在收到带有URG标识的报文后会优先处理,可能会造成资源调度上的混乱。

PSH

PSH值为1是指发送方要求接收方获取到数据后尽快将该报文段反馈给应用层(默认是先存储在接收方的接收缓存)。可以通俗理解为这一条消息不需要再依赖等待接收其他请求后再一并处理,你接收方直接接收到后转交给你的上层应用去处理即可。
PSH为1的前提是在TCP经过三次握手后才可以携带此标识符向接收方发起请求。

如下图:

一文弄懂TCP协议如何保证数据传输的可靠性


可以看到上图带有PSH标识的请求是在上面的三次握手之后发起的,如果感觉上面图片不直观我再上传一张Wireshark提供的TCP流量图

一文弄懂TCP协议如何保证数据传输的可靠性

通过流量图可以很直观的看出来,在我目标机器发送ACK确认消息请求过后,又发了一条带有PSH、ACK标识的请求到目标机器193.112.163.43上去了。


RST

TCP使用该标识符进行连接“复位”(可以理解为连接失败),出现连接失效、无法连接等异常情况时候该标识符会被写入1。比如客户端向一个没有开启端口监听的服务端发起创建连接请求则会被返回一个带有RST标识的报文。

FIN

FIN用于标识连接的断开状态,众所周知TCP断开连接需要经历四次挥手阶段。

如下图

一文弄懂TCP协议如何保证数据传输的可靠性

TCP需要四次挥手释放连接是因为它是双工通讯机制,一方发起关闭连接请求后只是代表它不再主动
发送数据报文了,但是它依旧可以接收从另一方传输的报文,且对接收的报文允许执行反馈。


上面图片会有客户端、服务端不同时期的连接状态。连接状态从通讯中变更到关闭都就根据四次挥手的过程而来的。至于三次握手其实在上面的PSH标识说明那里已经能够通过第二张TCP流量图看出来了,这里不再细说。

TCP的滑动窗口


滑动窗口是流量控制的核心,发送端根据滑动窗口和序号标识计算当前可以一次(以分段发送为参考)发送多少数据。

滑动窗口概念图如:

一文弄懂TCP协议如何保证数据传输的可靠性

滑动窗口有三个状态,分别是合拢、收缩、张开


窗口合拢表示窗口的可发送数据下标从左向右滑动,将数据进行打包发送。合拢窗口表示窗口内可发送数据减少,已发送未确认数据增加。

窗口收缩和合拢持对立方向,意味着窗口的可发送数据下标从右向左滑动,收缩窗口表示窗口内可发送数据增多,通常是数据发送后接收方确认超时的表现。

窗口张开表示滑动窗口整体向右边移动,通常是在接收方确认数据接收后,滑动窗口释放已确认数据同时整体向右移动。


关于Wireshark的流量图查看


使用Wireshark菜单栏的【统计】-->【流量图】可以很方便的对指定网络连接的请求顺序以及数据包信息进行很直观的查看。如下图就是过滤到上面TCP抓包的流量图信息:

一文弄懂TCP协议如何保证数据传输的可靠性

小结



 

不积跬步,无以至千里,不积小流,无以成江海。

一文弄懂TCP协议如何保证数据传输的可靠性
扫码关注我
关注我,一起探索IT技术的乐趣