TCP协议详解

前言

小到基于应用层作网络开发,大到生活中无处不在的网络。咱们在享受这个便利的时候,没有人会关心它如此牢固的底层基石是如何搭建的。而这些基石中很重要的一环就是tcp协议。翻看一下“三次握手”和“四次挥手”,本觉得这就是tcp了,其实否则。它仅仅解决了链接和关闭的问题,传输的问题才是tcp协议更重要,更难,更复杂的问题。回头看tcp协议的原理,会发现它为了承诺上层数据传输的“可靠”,不知要应对多少网络中复杂多变的状况。简单直白列举一下:html

  • 怎么保证数据都是可靠呢?---链接确认!关闭确认!收到数据确认!各类确认!!
  • 由于网络或其余缘由,对方收不到数据怎么办?--超时重试
  • 网络状况变幻无穷,超时时间怎么肯定?--根据RTT动态计算
  • 反反复复,不厌其烦的重试,致使网络拥塞怎么办?---慢启动,拥塞避免,快速重传,快速恢复
  • 发送速度和接收速度不匹配怎么办?--滑动窗口
  • 滑动窗口滑的过程当中,他一直告诉我处理不过来了,不让传数据了怎么办?--ZWP
  • 滑动窗口滑的过程当中,他处理得慢,就理所固然的每次让我发不多的数据,致使网络利用率很低怎么办?---Nagle

其中任何一个小环节,都凝聚了无数的算法,咱们没有能力理解各个算法的实现,可是须要了解下tcp实现者的思路历程。linux

梳理完全部内容,大概能够知道:算法

tcp提供哪些机制保证了数据传输的可靠性?

tcp链接的“三次握手”和关闭的“四次挥手”流程是怎么样的?

tcp链接和关闭过程当中,状态是如何变化的?

tcp头部有哪些字段,分别用来作什么的?

tcp的滑动窗口协议是什么?

超时重传的机制是什么?

如何避免传输拥塞?

一. 概述

1. tcp链接的特色

  • 提供面向链接的,可靠的字节流服务
  • 为上层应用层提供服务,不关心具体传输的内容是什么,也不知道是二进制流,仍是ascii字符。

2. tcp的可靠性如何保证

  • 分块传送:数据被分割成最合适的数据块(UDP的数据报长度不变)
  • 等待确认:经过定时器等待接收端发送确认请求,收不到确认则重发
  • 确认回复:收到确认后发送确认回复(不是当即发送,一般推迟几分之一秒)
  • 数据校验:保持首部和数据的校验和,检测数据传输过程有无变化
  • 乱序排序:接收端能重排序数据,以正确的顺序交给应用端
  • 重复丢弃:接收端能丢弃重复的数据包
  • 流量缓冲:两端有固定大小的缓冲区(滑动窗口),防止速度不匹配丢数据

3. tcp的首部格式

3.1 宏观位置

  • 从应用层->传输层->网络层->链路层,每通过一次都会在报文中增长相应的首部。参考以前的文章http协议
  • TCP数据被封装在IP数据报中

3.2 首部格式

  • tcp首部数据一般包含20个字节(不包括任选字段)
  • 第1-2两个字节:源端口号
  • 第3-4两个字节:目的端口号

    源端口号+ip首部中的源ip地址+目的端口号+ip首部中的目的ip地址,惟一的肯定了一个tcp链接。对应编码级别的socket。shell

  • 第5-8四个字节:32位序号。tcp提供全双工服务,两端都有各自的序号。编号:解决网络包乱序的问题

    序号如何生成:不能是固定写死的,不然断网重连时序号重复使用会乱套。tcp基于时钟生成一个序号,每4微秒加一,到2^32-1时又从0开始缓存

  • 第9-12四个字节:32位确认序列号。上次成功收到数据字节序号加1,ack为1才有效。确认号:解决丢包的问题
  • 第13位字节:首部长度。由于任选字段长度可变
  • 后面6bite:保留
  • 随后6bite:标识位。控制各类状态
  • 第15-16两个字节:窗口大小。接收端指望接收的字节数。解决流量控制的问题
  • 第17-18两个字节:校验和。由发送端计算和存储,由接收端校验。解决数据正确性问题
  • 第19-20两个字节:紧急指针

3.3 标识位说明

  • URG:为1时,表示紧急指针有效
  • ACK:确认标识,链接创建成功后,总为1。为1时确认号有效
  • PSH:接收方应尽快把这个报文交给应用层
  • RST:复位标识,重建链接
  • SYN:创建新链接时,该位为0
  • FIN:关闭链接标识

3.4 tcp选项格式

  • 每一个选项开始是1字节kind字段,说明选项的类型
  • kind为0和1的选项,只占一个字节
  • 其余kind后有一字节len,表示该选项总长度(包括kind和len)
  • kind为11,12,13表示tcp事务

3.5 MSS 最长报文大小

  • 最多见的可选字段
  • MSS只能出如今SYN时传过来(第一次握手和第二次握手时)
  • 指明本端能接收的最大长度的报文段
  • 创建链接时,双方都要发送MSS
  • 若是不发送,默认为536字节

二. 链接的创建与释放

1. 链接创建的“三次握手”

1.1 三次握手流程

  • 客户端发送SYN,代表要向服务器创建链接。同时带上序列号ISN
  • 服务器返回ACK(序号为客户端序列号+1)做为确认。同时发送SYN做为应答(SYN的序列号为服务端惟一的序号)
  • 客户端发送ACK确认收到回复(序列号为服务端序列号+1)

1.2 为何是三次握手

  • tcp链接是全双工的,数据在两个方向上能同时传递。
  • 因此要确保双方,同时能发数据和收数据
  • 第一次握手:证实了发送方能发数据
  • 第二次握手:ack确保了接收方能收数据,syn确保了接收方能发数据
  • 第三次握手:确保了发送方能收数据
  • 其实是四个维度的信息交换,不过中间两步合并为一次握手了。
  • 四次握手浪费,两次握手不能保证“双方同时具有收发功能”

2. 链接关闭的“四次挥手”

2.1 为何是四次挥手

  • 由于tcp链接是全双工的,数据在两个方向上能同时传递。
  • 同时tcp支持半关闭(发送一方结束发送还能接收数据的功能)。
  • 所以每一个方向都要单独关闭,且收到关系通知须要发送确认回复

2.2 为何要支持半关闭

  • 客户端须要通知服务端,它的数据已经传输完毕
  • 同时仍要接收来自服务端的数据
  • 使用半关闭的单链接效率要比使用两个tcp链接更好

2.3 四次握手流程

  • 主动关闭的一方发送FIN,表示要单方面关闭数据的传输
  • 服务端收到FIN后,发送一个ACK做为确认(序列号为收到的序列号+1)
  • 等服务器数据传输完毕,也发送一个FIN标识,表示关闭这个方向的数据传输
  • 客户端回复ACK以确认回复

3. 链接和关闭对应的状态

3.1 状态说明

  • 服务端等待客户端链接时,处于Listen监听状态
  • 客户端主动打开请求,发送SYN时处于SYN_SENT发送状态
  • 客户端收到syn和ack,并回复ack时,处与Established状态等待发送报文
  • 服务端收到ack确认后,也处于Established状态等待发送报文
  • 客户端发送fin后,处于fin_wait_1状态
  • 服务端收到fin并发送ack时,处于close_wait状态
  • 客户端收到ack确认后,处于fin_wait_2状态
  • 服务端发送fin后,处于last_ack状态
  • 客户端收到fin后发送ack,处于time_wait状态
  • 服务端收到ack后,处于closed状态

3.2 time_wait状态

  • 也称为2MSL等待状态,MSL=Maximum Segment LifetIme,报文段最大生存时间,根据不一样的tcp实现自行设定。经常使用值为30s,1min,2min。linux通常为30s。
  • 主动关闭的一方发送最后一个ack所处的状态
  • 这个状态必须维持2MSL等待时间

3.2.1 为何须要这么作?

  • 设想一个场景,最后这个ack丢失了,接收方没有收到
  • 这时候接收方会从新发送fin给发送方
  • 这个等待时间就是为了防止这种状况发生,让发送方从新发送ack
  • 总结:预留足够的时间给接收端收ack。同时保证,这个链接不会和后续的链接乱套(有些路由器会缓存数据包)

3.2.2 这么作的后果?

  • 在这2MSL等待时间内,该链接(socket,ip+port)将不能被使用
  • 不少时候linux上报too many open files,说端口不够用了,就须要检查一些代码里面是否是建立大量的socket链接,而这些socket链接并非关闭后就立马释放的
  • 客户端链接服务器的时候,通常不指定客户端的端口。由于客户端关闭而后立马启动,按照理论来讲是会提示端口被占用。一样的道理,主动关闭服务器,2MSL时间内立马启动是会报端口被占用的错误
  • 多并发的短链接状况下,会出现大量的Time_wait状态。这两个参数能够解决问题,可是它违背了tcp协议,是有风险的。参数为:tcp_tw_reuse和tcp_tw_recycle
  • 若是是服务端开发,可设置keep-alive,让客户端主动关闭链接解决这个问题

4. 复位报文段

一个报文段从源地址发往目的地址,只要出现错误,都会发出复位的报文段,首部字段的RST是用于“复位”的。这些错误包括如下状况服务器

  • 端口没有在监听
  • 异常停止:经过发送RST而不是fin来停止链接

5. 同时打开

  • 两个应用程序同时执行主动打开,称为“同时打开“
  • 这种状况极少发生
  • 两端同时发送SYN,同时进入SYN_SENT状态
  • 打开一条链接而不是两条
  • 要进行四次报文交换过程,“四次握手”

6. 同时关闭

  • 双方同时执行主动关闭
  • 进行四次报文交换
  • 状态和正常关闭不同

7. 服务器对于并发请求的处理

  • 正等待链接的一端有一个固定长度的队列(长度叫作“积压值”,大多数状况长度为5)
  • 该队列中的链接为:已经完成了三次握手,但尚未被应用层接收(应用层须要等待最后一个ack收到后才知道这个链接)
  • 应用层接收请求的链接,将从该队列中移除
  • 当新的请求到来时,先判断队列状况来决定是否接收这个链接
  • 积压值的含义:tcp监听的端点已经被tcp接收,可是等待应用层接收的最大值。与系统容许的最大链接数,服务器接收的最大并发数无关

三. 数据的传输

1. tcp传输的数据分类

  • 成块数据传输:量大,报文段经常满
  • 交互数据传输:量小,报文段为微小分组,大量微小分组,在广域网传输会增长拥堵的出现
  • tcp处理的数据包括两类,有不一样的特色,须要不一样的传输技术

2. 交互数据的传输技术

2.1 经受时延的确认

  • 概念:tcp收到数据时,并不立马发送ack确认,而是稍后发送
  • 目的:将ack与须要沿该方向发送的数据一块儿发送,以减小开销
  • 特色:接收方没必要确认每个收到的分组,ACk是累计的,它表示接收方已经正确收到了一直到确认序号-1的全部字节
  • 延时时间:绝大多数为200ms。不能超过500ms

2.2 Nagle算法

  • 解决什么问题:微小分组致使在广域网出现的拥堵问题
  • 核心:减小了经过广域网传输的小分组数目
  • 原理:要求一个tcp链接上最多只能有一个未被确认的未完成的分组,该分组的确认到达以前,不能发送其余分组。tcp收集这些分组,确认到来以前以一个分组的形式发出去
  • 优势:自适应。确认到达的快,数据发送越快。确认慢,发送更少的组。
  • 使用注意:局域网不多使用该算法。且有些特殊场景须要禁用该算法

3. 成块数据的传输

  • 主要使用滑动窗口协议

四. 滑动窗口协议

1. 概述

  • 解决了什么问题:发送方和接收方速率不匹配时,保证可靠传输和包乱序的问题
  • 机制:接收方根据目前缓冲区大小,通知发送方目前能接收的最大值。发送方根据接收方的处理能力来发送数据。经过这种协调机制,防止接收端处理不过来。
  • 窗口大小:接收方发给发送端的这个值称为窗口大小

2. tcp缓冲区的数据结构

  • 接收端:
    • LastByteRead: 缓冲区读取到的位置
    • NextByteExpected:收到的连续包的最后一个位置
    • LastByteRcvd:收到的包的最后一个位置
    • 中间空白区:数据没有到达
  • 发送端:
    • LastByteAcked: 被接收端ack的位置,表示成功发送确认
    • LastByteSent:发出去了,尚未收到成功确认的Ack
    • LastByteWritten:上层应用正在写的地方

3. 滑动窗口示意图

3.1 初始时示意图

  • 黑框表示滑动窗口
  • #1表示收到ack确认的数据
  • #2表示还没收到ack的数据
  • #3表示在窗口中尚未发出的(接收方还有空间)
  • #4窗口之外的数据(接收方没空间)

3.2 滑动过程示意图

  • 收到36的ack,并发出46-51的字节

4. 拥塞窗口

  • 解决什么问题:发送方发送速度过快,致使中转路由器拥堵的问题
  • 机制:发送方增长一个拥塞窗口(cwnd),每次受到ack,窗口值加1。发送时,取拥塞窗口和接收方发来的窗口大小取最小值发送
  • 起到发送方流量控制的做用

5. 滑动窗口会引起的问题

5.1 零窗口

  • 如何发生: 接收端处理速度慢,发送端发送速度快。窗口大小慢慢被调为0
  • 如何解决:ZWP技术。发送zwp包给接收方,让接收方ack他的窗口大小。

5.2 糊涂窗口综合征

  • 如何发生:接收方太忙,取不完数据,致使发送方愈来愈小。最后只让发送方传几字节的数据。
  • 缺点:数据比tcp和ip头小太多,网络利用率过低。
  • 如何解决:避免对小的窗口大小作响应。
    • 发送端:前面说到的Nagle算法。
    • 接收端:窗口大小小于某个值,直接ack(0),阻止发送数据。窗口变大后再发。

五. 超时与重传

1. 概述

  • tcp提供可靠的运输层,使用的方法是确认机制。
  • 可是数据和确认都有可能丢失
  • tcp经过在发送时设置定时器解决这种问题
  • 定时器时间到了还没收到确认,就重传该数据

2. tcp管理的定时器类型

  • 重传定时器:等待收到确认
  • 坚持定时器:使窗口大小信息保持不断流动
  • 保活定时器:检测空闲链接崩溃或重启
  • 2MSL定时器:检测time_wait状态

3. 超时重传机制

3.1 背景

  • 接收端给发送端的Ack确认只会确认最后一个连续的包
  • 好比发送1,2,3,4,5共五份数据,接收端收到1,2,因而回ack3,而后收到4(还没收到3),此时tcp不会跳过3直接确认4,不然发送端觉得3也收到了。这时你能想到的方法是什么呢?tcp又是怎么处理的呢?

3.1 被动等待的超时重传策略

  • 直观的方法是:接收方不作任何处理,等待发送方超时,而后重传。
    • 缺点:发送端不知道该重发3,仍是重发3,4,5
  • 若是发送方若是只发送3:节省宽度,可是慢
  • 若是发送方若是发送3,4,5:快,可是浪费宽带
  • 总之,都在被动等待超时,超时可能很长。因此tcp不采用此方法

3.2 主动的快速重传机制

3.2.1 概述

  • 名称为:Fast Retransmit
  • 不以实际驱动,而以数据驱动重传

3.2.2 实现原理

  • 若是包没有送达,就一直ack最后那个可能被丢的包
  • 发送方连续收到3相同的ack,就重传。不用等待超时
  • 图中发生1,2,3,4,5数据
  • 数据1到达,发生ack2
  • 数据2由于某些缘由没有送到
  • 后续收到3的时候,接收端并非ack4,也不是等待。而是主动ack2
  • 收到4,5同理,一直主动ack2
  • 客户端收到三次ack2,就重传2
  • 2收到后,结合以前收到的3,4,5,直接ack6

3.2.3 快速重传的利弊

  • 解决了被动等待timeout的问题
  • 没法解决重传以前的一个,仍是全部的问题。
  • 上面的例子中是重传2,仍是重传2,3,4,5。由于并不清楚ack2是谁传回来的

3.3 SACK方法

3.3.1 概述

  • 为了解决快速重传的缺点,一种更好的SACK重传策略被提出
  • 基于快速重传,同时在tcp头里加了一个SACK的东西
  • 解决了什么问题:客户端应该发送哪些超时包的问题

3.3.2 实现原理

  • SACK记录一个数值范围,表示哪些数据收到了
  • linux2.4后默认打开该功能,以前版本须要配置tcp-sack参数
  • SACK只是一种辅助的方式,发送方不能彻底依赖SACK。主要仍是依赖ACK和timout

3.3.3 Duplicate SACK(D-SACK)

  • 使用SACK标识的范围,还能够知道告知发送方,有哪些数据被重复接收了
  • 可让发送方知道:是发出去的包丢了,仍是回来的ack包丢了

4. 超时时间的肯定

4.1 背景

  • 路由器和网络流量均会变化
  • 因此超时时间确定不能设置为一个固定值
  • 超时长:重发慢,效率低,性能差
  • 超时短:并无丢就重发,致使网络拥塞,致使更多超时和更多重发
  • tcp会追踪这些变化,并相应的动态改变超时时间(RTO)

4.2 如何动态改变

  • 每次重传的时间间隔为上次的一倍,直到最大间隔为64s,称为“指数退避”
  • 首次重传到最后放弃重传的时间间隔通常为9min
  • 依赖以往的往返时间计算(RTT)动态的计算

4.3 往返时间(RTT)的计算方法

  • 并非简单的ack时间和发送时间的差值。由于有重传,网络阻塞等各类变化的因素。
  • 而是经过采样屡次数值,而后作估算
  • tcp使用的方法有:
    • 被平滑的RTT估计器
    • 被平滑的均值误差估计器

4.4. 重传时间的具体计算

  • 计算往返时间(RTT),保存测量结果
  • 经过测量结果维护一个被平滑的RTT估计器和被平滑的均值误差估计器
  • 根据这两个估计器计算下一次重传时间

5. 超时重传引起的问题-拥塞

5.1 为何重传会引起拥塞

  • 当网络延迟忽然增长时,tcp会重传数据
  • 可是过多的重传会致使网络负担加剧,从而致使更大的延时和丢包,进入恶性循环
  • 也就是tcp的拥塞问题

5.2 解决拥塞-拥塞控制的算法

  • 慢启动:下降分组进入网络的传输速率
  • 拥塞避免:处理丢失分组的算法
  • 快速重传
  • 快速恢复

六. 其余定时器

1. 坚持定时器

1.1 坚持定时器存在的意义

  • 当窗口大小为0时,接收方会发送一个没有数据,只有窗口大小的ack
  • 可是,若是这个ack丢失了会出现什么问题?双方可能由于等待而停止链接
  • 坚持定时器周期性的向接收方查询窗口是否被增大。这些发出的报文段称为窗口探查

1.2 坚持定时器启动时机

  • 发送方被通告接收方窗口大小为0时

1.3 与超时重传的相同和不一样

  • 相同:一样的重传时间间隔
  • 不一样:窗口探查从不放弃发送,直到窗口被打开或者进程被关闭。而超时重传到必定时间就放弃发送

2. 保活定时器

2.1 保活定时器存在的意义

  • 当tcp上没有数据传输时,服务器如何检测到客户端是否还存活

参考

相关文章
相关标签/搜索