标注: 看《TCP/IP协议卷一》记的笔记
处理差错的两种主要方法
- 差错校正码
- 数据重传
使用序列号解决分组重复的问题
停止个等待协议的吞吐量性能低,因此允许多个分组被注入网络(滑动窗口)
滑动窗口
- 发送方
- 记录哪些分组可被释放,哪些分组正在等待ACK,哪些分组不能被发送
- 接收方
- 哪些分组已经被接受和确认,哪些分组是下一步期望的,哪些分组即使被接收也会因为内存限制而被丢弃。
流量控制
- 为了处理当接收方相对发送方太慢而产生的问题
- 基于速率流量控制
- 给发送方指定某个速率
- 基于窗口流量控制
- 窗口大小随时间改变,接收方通知发送方使用多大窗口
拥塞控制
- 保护发送方和接收方中间的网络
- 发送方减低速度以不至于压垮与接收方之间的网络
TCP
-
可靠性
- 面向连接的字节流服务
- 校验和
- 超时重传
- 累积确认
- 双工服务
- 序列号
- 丢弃重复记录杂乱次序
-
连接的建立和结束
- 为什么三次握手
- 问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的
- 1、2、4次握手的弊端
- 为什么四次挥手
- 本质的原因是tcp是全双公的,要实现可靠的连接关闭,A发出结束报文FIN,收到B确认后A知道自己没有数据需要发送了,B知道A不再发送数据了,自己也不会接收数据了,但是此时A还是可以接收数据,B也可以发送数据;当B发出FIN报文的时候此时两边才会真正的断开连接,读写分开。
- 每个链接都有不同的初始序列号
- TCP状态转换图
- 重置报文段
- 当一个连接请求到达本地却没有相关进程在目的端口监听就会产生一个重置报文段
- 通过发送一个重置报文段终止一条链接
- 半开链接中服务器响应一个重置报文段
- TIME_WAIT状态不对重置报文段做出反应
- 连接队列
- 使用并行服务器的原因
- 一个并行的服务器会为每一个客户端分配一个新的进程或线程,这样负责侦听的服务器能够始终准备处理下一个到来的连接请求
- 被用于应用程序之前新的连接可能会处于两种状态(不同的连接队列)
- 连接尚未完成但是已经接收到SYN(处于SYN_RCVD状态)
- 连接已经完成三次握手并且处于ESTABLISHED状态
- 通过队列的大小控制
- 当连接到达,会检查系统范围的参数,如果处于SYN_RCVD状态的连接数超过这一阈值就会拒绝连接
- 每一个处于侦听状态下的节点都拥有一个固定长度的连接队列(其中的连接已经被TCP完全接受),应用程序会对这一连接做限制(backlog)
- 如果侦听节点的队列中有空间分配给新的连接,TCP回应答SYN并完成连接
- 如果队列没有足够的空间分配给新的连接,TCP将会延迟对SYN做出响应
- 在不发送重置报文段的情况下,如果服务器无法抽时间接受被TCP接受却超出队列上限的连接,客户端的主动打开操作会超时。
- 使用并行服务器的原因
- 为什么三次握手
-
RTT和重传超时
- 保证数据传输的准确性,TCP会重传其认为丢失的包
- 处理包丢失
-
超时重传(重传次数和放弃当前连接的时间)
- TCP需要为重传计时器设置超时值,指示发送数据后等待ACK的时间
- TCP需要动态设置RTO(重传超时)
- 原因:
- 小于RTT导致不必要的重复数据,大于RTT导致网络利用率低
- 步骤
- 根据一段时间的RTT样本值建立估计值
- 基于估计值设置RTO
- 原因:
- 每一个TCP连接的发送端不断设定和取消一个重传计时器,没有数据丢失就不会出现超时
- 降低当前的数据发送率
- 基于拥塞控制机制减小发送窗口的大小
- 每当一个重传报文段再次被重传时,增大RTO的退避因子
- 基于计时器的重传导致网络利用率低(因为RTO的设置通常大于2倍的RTT)
-
快速重传
- 基于接受端的反馈信息引发重传
- TCP发送端等待一定数目的重复ACK后重传可能丢失的数据分组,不必等到重传计时器超时
- 失序数据到达时重复ACK应立即响应不能延时发送(使发送端尽早得知有失序报文段)
-
带选择确认的重传
-
RTT较大,丢包严重的环境下SACK的优势明显
-
利用SACK可以获知多个空缺
-
不采用SACK时,接收到有效ACK前至多只能重传一个报文段,采用SACK时,ACK可包含额外信息使发送端在每个RTT内可以填补多个空缺
-
接收端通过TCP头部累计ACK号字段描述其接收到的数据来提供SACK功能
-
使用SACK能更快的实现空缺填补(一个RTT内可以获知多个空缺)以及减少不必要的重传
-
每个ACK中只能保护3个SACK块
-
SACK接收端
- 连接建立期间接收到SACK许可选项即可生成SACK,丢包时就会生成
- 第一个SACK块内包含的是最近接收到的报文段序列号范围,最新一个块除了包含最近接收到的报文段序列号范围还需要重复之前的SACK块
- 多个块重复信息的目的是防止SACK丢失提供一些备份,因为SACK 会丢失并且不会被重传
-
SACK发送端
- 当接受到SACK或者重复ACK时。SACK发送端执行重传,发送端不仅记录接收到的累积ACK信息还要记录SACK信息避免重传正确接收到的数据
- SACK不能在接收到一个SACK后立即清空其重传缓存的数据 只有当接收端的普通ACK大于其最大序列号的时候才清除
- 当发送端启动基于定时器的重传时忽略SACK信息
-
-
伪超时与重传
- 伪重传
- DSACK
- 在SACK块中告知当前接收端收到的重复报文序列号
- 伪超时(RTT>RTO)
- 处理伪超时的方法
- 检测算法
- 判断某个超时或重传是否真实
- 响应算法
- 用于撤销或减轻该伪超时带来的影响
- 检测算法
- 处理伪超时的方法
- DSACK
- 伪重传
-
- 包失序
- 将重复阈值设置较小就可处理绝大部分情况
- 包重复
- 利用SACK和DSACK解决
-
数据传输
-
窗口管理和流量控制
- 交互式TCP连接
- 需要在客户端和服务器之间传输用户输入信息
- 延时ACK
- 实现
- 当有响应数据发送的时候,ACK会随着数据一块发送
- 如果没有响应数据,ACK就会有一个延迟,以等待是否有响应数 据一块发送(内核会启动一个定时器,定时器到了即便没有响应数据也会发送ACK)
- 如果在等待发送ACK期间,第二个数据又到了,这时候就要立即发送ACK
- 特别是交互式流量传输中服务器需要对客户端每个按键返回响应
- 相对高延迟的广域网 中需要
- 减少ACK传输数目,一定程度上减轻网络负载,缺点是可能会造成RTT过大
- 实现
- Nagle算法
- 当一个TCP连接中有在传数据(已发送未确认),小的(小于SMSS)报文段就不能备发送,直到所有的在传数据都收到ACK,TCP需要收集这些小数据整合到一个报文段发送。
- 优点:避免网络中充斥着许多小数据块,降低网络负载,减少网络拥塞,提高网络吞吐
- 缺点:客户端的延迟会增加,实时性降低,不适合延时要求尽量小的场景;且对于大文件传输这种场景,会降低传输速度。
- Nagle算法比较适用于发送方发送大批量的小数据,并且接收方作出及时回应的场合,这样可以降低包的传输个数
- 当你的应用不是连续请求+应答的模型的时候,而是需要实时的单项的发送数据并及时获取响应,就不太适合Nagle算法,明显有delay的
- Nagle算法与Delay ACK机制有共存的情况下会有一些非常糟糕的状况, 避免write-wirte-read的这种写法就可以避免掉问题,比如write一块去写,一次写成功,就一个包发过去了,就没有等待delay的过程
- 网络带宽资源不再像过去那么紧张,一般使用TCP_NODELAY关闭nagle算法
- 滑动窗口
- 每个TCP报文段(除了连接建立之初的包交换)都包含一个有效的序列号字段,ACK号或确认字段以及一个窗口大小字段
- tcp每一端都可收发数据,收发数据量是通过一组窗口结构维护的,每个TCP链接的两端都维护一个发送窗口接口和接收窗口结构
- 发送端窗口记录了已确认,在传以及还未传的数据的序列号,提供窗口的大小由接收端返回的ACK中的窗口大小控制
- 接收端窗口记录了已接收并确认的数据,以及能够接收的最大序列号,保证接收数据的准确性
- TCP是通过接收端的通告窗口来实现流量控制的
- 通告窗口为0时,阻止发送方继续发送直到窗口大小恢复为非零值
- 接收端重新获得可用空间时,会给发送端发送一个窗口更新告知其继续发送数据。
- 防止如果有个窗口更新的ack丢失(不包含数据丢失不会重传),双方死锁的等待情况,发送端会发送窗口探测(包含一个字节的数据,可采用可靠传输即丢失重传)强制要求接收端返回ACK。
- 糊涂窗口综合征
- 影响:
- 交换数据段的大小不是全长的而是一些较小的数据段由于每个报文段的有用数据相对头部信息比例较小,因此耗费资源更多,传输效率更低。
- TCP两端都可能导致
- 接收端的通告窗口较小(没有等到窗口变大才通告)
- 发送端发送的数据段较小(没有等待将其他数据组合成更大的一个报文段)
- 避免SWS,遵循以下规则
- 接受端
- 不应通告小的窗口值
- 在窗口可增长至一个全长的报文段或者接受端缓存空间的一半之前,不能通告比当前窗口更大的窗口值
- 不应通告小的窗口值
- 发送端
- 不应发送小的报文段,需由Nagle算法控制何时发送
- 全长的报文段可发送
- 数据段长度>=接收端通告过的最大窗口值的一半时可发送
- 满足以下任一可发送
- 该连接禁用Nagle算法
- 某一ACK不是目前期盼的(没有未经确认的在传数据)
- 不应发送小的报文段,需由Nagle算法控制何时发送
- 接受端
- 影响:
- 交互式TCP连接
-
拥塞控制算法
- 拥塞
- 路由器因无法处理高速率到达的流量而被迫丢弃数据信息的现象
- TCP推断产生拥塞:发生丢包现象
- 防止网络因为大规模的通信负载而瘫痪
- 发送端的发送速率=min (cwnd,awnd)
- TCP发送方的拥塞控制操作是由ack的接受来驱动或控制的
- 算法
- 基于丢包的拥塞控制算法
- 慢启动
- 执行条件
- 链接建立之初
- 由超时判定丢包发生
- 目的
- 使tcp在用拥塞避免探寻更多可用带宽之前得到cwnd的值,以及帮助tcp建立ack时钟。
- 在传输初始阶段,由于未知网络传输能力,需要缓慢探测可用传输资源,防止短时间内大量数据注入导致拥塞
- 使tcp在用拥塞避免探寻更多可用带宽之前得到cwnd的值,以及帮助tcp建立ack时钟。
- 与一开始就允许最大可用速率发送相比,显得缓慢
- 窗口随时间呈指数增长
- 执行条件
- 拥塞避免
- 一旦确认慢启动阈值tcp就会进入拥塞避免阶段
- 目的
- 为了得到更多传输资源而不影响其他连接传输
- 慢启动阶段,cwnd会快速增长,帮助建立一个慢启动阈值,一旦达到阈值,就意味有更多可用传输资源,若立即占用这些资源,会使共享路由器队列的其他连接严重丢包和重传,导致整个网络性能不稳定
- 为了得到更多传输资源而不影响其他连接传输
- 窗口随时间线性增长
- 如何决定使用哪种算法
- cwnd<慢启动阈值,使用慢启动算法
- cwnd>慢启动阈值,使用拥塞避免算法
- 相等时,任何一种算法都可以
- 区别
- 新的ACK到达时,cwnd怎样增长
- 慢启动阈值
- 不固定,随时间变化
- 目的
- 在没有丢包发生的情况下,记住上一次最好的操作窗口估计值
- 慢启动和拥塞避免是通过在发送方设置一个拥塞窗口来实现对其操作的控制
- 慢启动
- 二进制增长拥塞控制(BIC,UBIC)
- BIC-TCP认为TCP拥塞窗口调整的本质就是找到最适合当前网络的一个发送窗口,为了找到这个窗口值,TCP采取的方式是(拥塞避免阶段)每RTT加1,缓慢上升,丢包时下降一半,接着再来慢慢上升。BIC-TCP认为其实这就是一个搜索的过程,而TCP的搜索方式类似于逐个遍历搜索方法,可以认为这个值是在1和一个比较大的数(large_window)之间,既然在这个区间内需要搜索一个最佳值,那么显然最好的方式就是二分搜索思想
- BIC算法的目的在于即使在拥塞窗口非常大的情况下也能满足线性RTT公平性(较小的RTT在共享传输路径上能获得更大的带宽),指连接得到的带宽与RTT成反比
- 使用两种算法:
- 二分搜索增大
- 最小窗口是最近一个在完整RTT中没有出现丢包的窗口大小,最大窗口是最近一次出现丢包的窗口大小,预期窗口位于两值之间,使用二分搜索技术选择两值的中间点作为试验窗口然后进行递归,如果依然丢包设置为最大窗口,不丢包设置为最小的窗口,重复直到最大最小的窗口差值小于预先设置的阈值
- 当中间点与当前窗口大小之间差值大于一个特定值的时候,调用加法增大算法,当检测到丢包现象窗口会使用乘法系数减小,当窗口增大时首先使用加法增大算法,之后一旦确认加法正增量小于S时转为使用二分搜索增大算法
- 是为充分利用高速网络没有不必要的延时的优点而设计
- CUBIC
- 改进了BIC的一些增长过快的不足,对窗口增长机制简化,不像BIC使用阈值决定何时调用加法增大算法和二分搜索增大算法,而是使用一个高阶的多项式控制窗口增大
- 在BIC-TCP的窗口调整中会出现一个凹和凸的增长曲线,CUBIC使用了一个三次函数,在三次函数曲线中同样存在一个凹和凸的部分,该曲线形状和BIC-TCP的曲线图十分相似,于是该部分取代BIC-TCP的增长曲线
- CUBIC中最关键的点在于它的窗口增长函数仅仅取决于连续的两次拥塞事件的时间间隔值,从而窗口增长完全独立于网络的时延RTT,使得连接之间保持良好的RRTT公平性
- 基于延迟的拥塞控制算法
- 存在原因
- 没有显式拥塞控制的情况下,判断网络中的主机是否发生拥塞
- 原理
- 不断增大的RTT
- 算法
- Vegas算法
- 优点
- 更好的利用率
- 更少的自诱导丢包率
- 更快的收敛性
- RTT更公平和稳定
- 存在原因
- 复合TCP (CTCP)
- 通过将基于延迟的方法和基于丢包的方法相结合解决问题
- W=min (cwnd+dwnd,awnd)
- dwnd延迟窗口,基于Vegas算法,只在拥塞避免阶段非零值。
- 积极队列管理
- tcp只能在丢包后做出反应,如果路由器可以事先积极管理它们的等待队列,就可以改善情况。
- 将路由器和交换机的状态传输给端系统来实现
- 显式拥塞通知
- 对经过路由器的包进行标记,当数据包被接收时,接收端向发送端返回一个AcK来通知拥塞状况(因为发送端需要这些信息降低发送速率)
- 在IP层操作
- 基于丢包的拥塞控制算法
- 拥塞
-
TCP保活机制
- 目的
- 客户端和服务器需要了解什么时候终止进程或者与对方断开连接
- 虽然应用进程之间没有任何数据交换,但仍需要通过链接保持一个最小的数据流
- 实现
- 由一个保活计时器实现,当计时器被激发,链接一端将发送一个保活探测报文,另一端接收报文同时发送一个ACK响应。
- 目的
-
TCP粘包问题
- 当应用层协议使用TCP协议传输数据时,TCP协议可能会将应用层发送的数据分成多个包依次发送,而数据的接收方收到的数据段可能有多个『应用层数据包』组成,所以当应用层从TCP缓冲区中读取数据时发现粘连的数据包时,需要对收到的数据进行拆分
- TCP协议中的粘包是如何发生的:
- TCP协议是面向字节流的协议,它可能会组合或者拆分应用层协议的数据;
- 应用层协议的没有定义消息的边界导致数据的接收方无法拼接数据
- TCP协议是基于字节流的协议,其本身没有数据包的概念,不会按照数据包发送数据
- 实现消息边界的方法
- 基于长度(HTTP协议的消息边界就是基于长度实现的)
- 一种是使用固定长度,所有的应用层消息都使用统一的大小
- 另一种方式是使用不固定长度,但是需要在应用层协议的协议头中增加表示负载长度的字段,这样接收方才可以从字节流中分离出不同的消息
- 基于终结符
- 基于长度(HTTP协议的消息边界就是基于长度实现的)
- TCP协议粘包问题是因为应用层协议开发者的错误设计导致的,他们忽略了TCP协议数据传输的核心机制 — 基于字节流,其本身不包含消息、数据包等概念,所有数据的传输都是流式的,需要应用层协议自己设计消息的边界
- UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题
-
TCP与UDP区别
- 面向字节流vs面向报文
- 面向连接vs无连接
- TCP有三次握手的连接过程,UDP适合消息的多播发布,从单个点向多个点传输消息
- 可靠性
- TCP利用握手, ACK和重传的机制,提供了可靠性保证,而UDP可能丢失,不知道到底有没有接收
- 有序性:
- TCP利用序列号保证了消息包的的顺序交付,到达可能无序,但TCP会排序
- 速度:
- TCP 速度比较慢,因为要创建连接,保证消息的可靠性和有序性等,需要做额外的很多事情,UDP 更适合对速度比较敏感的应用,比如在线视频媒体,电视广播,多人在线游戏等
- 重量级
- TCP重量级,UDP轻量级,体现在元数据的头大小是,TCP20字节,UDP8字节
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP用于在传输层有必要实现可靠传输的情况;UDP主要用于那些对高速传输和实时性有较高要求的通信或广播通信
-
UDP使用场景
- TCP对应的是可靠性要求高的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议
- UDP对应的则是可靠性要求低、传输流量大,传输速度快的应用,UDP更适合对速度比较敏感的应用,比如在线视频媒体,电视广播,多人在线游戏等。
- UDP常用于以下几个方面:
- 1.包总量较少的通信(DNS、SNMP等);
- 2.视频、音频等多媒体通信(即时通信);
- 3.限定于 LAN 等特定网络中的应用通信;
- 4.广播通信(广播、多播)
- UDP的优势
- 网速的提升给UDP提供了可靠地保障,丢包率很低,使用应用层重传就能保证一定的可靠性
- TCP为了实现可靠性建立的一套复杂的机制很难改造,使用TCP,如果发生丢包,会将后续的包缓存起来等前面的包重传结束后继续发送,会有较大的延时。基于UDP对实时性要求严格的情况,使用自定义重传能把丢包的延迟降到最低
-
TIME_WAIT状态
-
存在理由
- 允许老的重复报文分组在网络中消逝
- 有足够的时间使迟到的报文段被丢弃,防止延迟的数据段被其他使用相同源地址、源端口、目的地址以及目的端口的TCP连接收到
- 保证TCP全双工连接的正确关闭
- TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到Ack,就会触发被动端重发Fin,一来一去正好2个MSL
- 要是主动关闭方不维护2MSL状态,那么主动关闭将会不得不响应一个RST报文段,而服务器将会把它解释为一个错误,导致TCP连接没有办法完成全双工的关闭,而进入半关闭状态
- 允许老的重复报文分组在网络中消逝
-
2MSL
- 一个MSL确保主动关闭方最后的ACK可以到达对端
- 一个MSL确保被动关闭方重发的FIN能被主动方收到
-
-
为什么TCP建立连接需要三次握手
-
什么是连接:
- 用于保证可靠性和流控制机制的信息,包括Socket、序列号以及窗口大小(流量控制)叫做连接
- 建立TCP连接就是通信的双方需要对上述的三种信息达成共识
-
TCP连接使用三次握手的原因
- 为了阻止历史的重复连接初始化造成的混乱问题,防止使用TCP协议通信的双方建立了错误的连接
- 假设在网络状况复杂或者较差的网络中,发送方连续发送多次建立连接的请求,如果TCP建立连接只能通信两次,那么接收方只能选择接受或者拒绝发送方发起的请求,它并不清楚这一次请求是不是由于网络拥堵而早早过期的连接。所以,TCP选择使用三次握手来建立连接并在连接引入了RST 这一控制消息,接收方当收到请求时会将发送方发来的SEQ+1发送给对方,这时由发送方来判断当前连接是否是历史连接:
- 如果当前连接是历史连接(SEQ过期或者超时),那么发送方就会直接发送RST控制消息中止这一次连接;
- 如果不是历史连接,那么发送方就会发送ACK控制消息,通信双方就会成功建立连接;
- 使用三次握手和RST控制消息将是否建立连接的最终控制权交给了发送方,因为只有发送方有足够的上下文来判断当前连接是否是错误的或者过期的
- 假设在网络状况复杂或者较差的网络中,发送方连续发送多次建立连接的请求,如果TCP建立连接只能通信两次,那么接收方只能选择接受或者拒绝发送方发起的请求,它并不清楚这一次请求是不是由于网络拥堵而早早过期的连接。所以,TCP选择使用三次握手来建立连接并在连接引入了RST 这一控制消息,接收方当收到请求时会将发送方发来的SEQ+1发送给对方,这时由发送方来判断当前连接是否是历史连接:
- 序列号在TCP连接中有着非常重要的作用,初始序列号作为TCP连接的一部分也需要在三次握手期间进行初始化
- 为了阻止历史的重复连接初始化造成的混乱问题,防止使用TCP协议通信的双方建立了错误的连接
-
究其根源,是网络可不可达导致的问题。从发送端发出连接请求到发送端收到消息确认,在客户端看来确实是证明了网络可达并且连接已经建立,但是在接收端看来,单单收到连接请求并不能证明什么,因为这个连接请求有可能是旧的延迟的连接请求,此时客户端可能早就下线了。所以从接收端的角度来看,其也需要确认网络可达(或者说接收端在线),所以客户端收到消息确认后还得再回复一下接收端,这样一来一回就实现了双向证明,所以需要四次中间两次可以合成一次,所以是3次
-
本质是通信是不可靠的但是数据传输需要可靠的
-
TCP协议性能问题
- 导致 TCP 性能问题的三个重要原因:
- TCP 的拥塞控制在发生丢包时会进行退让,减少能够发送的数据段数量,但是丢包并不一定意味着网络拥塞,更多的可能是网络状况较差;
- TCP 的三次握手带来了额外开销,这些开销不只包括需要传输更多的数据,还增加了首次传输数据的网络延迟;
- TCP 的重传机制在数据包丢失时可能会重新传输已经成功接收的数据段,造成带宽的浪费;
- 解决TCP的性能问题,方案:
- 使用 UDP 构建性能更加优异、更灵活的传输协议,例如:QUIC19 等;
- 通过不同的手段优化 TCP 协议的性能,例如:选择性 ACK(Selective ACK, SACK),TCP 快开启(TCP Fast Open, TFO);
UDP通信
- UDP是面向报文的,应用层发送的报文有多大,UDP就原样发送,既不组合也不拆分,UDP是无连接的,传输数据之前不需要建立连接,时延小,而且UDP没有重传,拥塞控制等机制传输速度快,但也就传输不可靠
UDP头部为什么只有8字节
- UDP 协议利用下层的 IP 协议提供基本的数据传输能力,它的作用就是引入端口号的概念让同一主机可以同时提供对外多个服务,由于不保证可靠性,所以协议本身只占用 8 个字节
- UDP和TCP可以使用同样的端口