文章目录

  • UDP
    • 概念
    • 格式
    • UDP如何实现可靠传输
    • 基于UDP的应用层知名协议
  • TCP
    • 概念
    • 格式
    • 保证TCP可靠性的八种机制
      • 确认应答、延时应答与捎带应答
      • 超时重传
      • 滑动窗口
        • 滑动窗口协议
        • 后退n协议
        • 选择重传协议
      • 流量控制
      • 拥塞控制
        • 发送窗口、接收窗口、拥塞窗口
        • 快速重传和快速恢复
    • 连接管理机制
      • 三次握手
        • 连接超时
      • 四次挥手
        • TIME_WAIT
      • 保活机制
    • TCP的数据类型
      • 交互/成块数据流
      • 带外数据
    • TCP粘包
    • 基于TCP的应用层知名协议

UDP

概念

UDP协议,即用户数据报协议。为应用层提供 不可靠、无连接、基于数据报的服务。

  • 不可靠: 不保证数据从发送端正确地传送到目的端,也无须为应用层数据保存副本,出现问题时(丢包,错序达到等) UDP协议 只是简单地通知应用程序发送失败。因此,使用 UDP协议 的应用程序通常需要自己处理数据确认、超时重传等逻辑。
  • 无连接: 不需要建立连接也可以发送数据。通信双方每次发送数据都需要指定接收端的地址。
  • 基于数据报的服务: 每个 UDP数据报 都有一个长度,接收端必须以该长度为最小单位将其所有内容一次性读出,否则数据将被截断。

格式

  • 16位源端端口/目的端口: 表示源端/对端的端口号,源端端口有时候可以不设置(不关心通信的端口),可以设置为 0
  • 16位数据报长度: 标志UDP首部与发送数据的长度之和,大小为 216,即 64K65535
  • 16位校验和: 用于检验 接收的数据发送的数据 是否一致,不一致则丢弃 。校验方法:二进制反码求和,即对报文从头开始的每个字节进行取反相加,高出16位则截断高位,与低16位相加,得到校验和。

面向数据报:

  • 数据报长度字段只有 16 位,报头要占 8 个字节,所以数据报的长度不能大于 64K- 8
    UDP 给应用层传下来的报文添加首部后就会直接转交给网络层,所以对于较大的数据,需要我们自己在应用层进行分包和包序管理。
  • UDP 在报头中定义了数据长度,所以传输的时候都是整条收发。
    所以接收时缓冲区必须要足够大,如果缓冲区小于一条数据的大小会接收失败,因为 UDP 不会交付 半条数据

UDP如何实现可靠传输

依靠UDP本身是无法实现可靠传输的,因为无法保证数据 有序且到达 ,所以可以参考TCP的可靠性机制,在应用层也为其引入类似逻辑:

  • 引入序列号,保证数据有序。
  • 确认应答机制,保证对端能够收到数据。
  • 引入超时重传机制,保证数据不会丢失。

基于UDP的应用层知名协议

  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

TCP

概念

TCP协议,即传输控制协议。为应用层提供 可靠的、面向连接、基于流的服务。

  • 可靠: 通过超时重传、数据确认等方式来确保数据报被正确地发送至目的端。
  • 面向连接: 双方都必须先分配必要的内核资源以建立全双工的连接,才能开始数据的读写。
  • 基于流: 基于流的数据没有边界(长度)限制。

格式

  • 16位源端端口/目的端口: 表示源端/目的端的端口号,源端端口有时候可以不设置(不关心通信的端口),可以设置为0。
  • 32位序号: 序号是指发送数据的位置。每发送一次数据,就累加一次该数据字节数的大小。序号不会从 01 开始,而是在建立连接时由计算机生成的随机数作为其初始值(ISN,初始序号值),通过 SYN包 传给接收端主机。后续报文中序号值将被设置为 ISN+报文携带数据的第一个字节在整个字节流中的偏移 。如:后续 某个TCP报文段 传送的数据是字节流中的 第 1025~2048 字节 ,那么该报文段的序号值为 ISN+1025
  • 32位确认序号: 对发送端发来的TCP报文段的响应,其值是 收到的TCP报文段的序号+1 。而发送端接收到这个确认序号以后可以认为在确认序号以前的数据都已经被正常接收。TCP通过序号和确认序号来实现包序管理,确保TCP数据是有序交付的。
  • 4位数据偏移(首部长度): 标识该 TCP 头部有多少个 32bit(4字节)。因为 4位 最大能表示 15(24 -1)个,所以 TCP头部最长是60字节。
  • 6位保留位: 保留为今后使用,一般设置为 0
  • 6位标志位: 有六种标志位,用来描述本报文的性质:
    1. URG(Urgent Flag): 该位为 1 时,表示包中有需要紧急处理的数据。对于需要紧急处理的数据,会在后面的紧急指针中再进行解释。
    2. ACK(Acknowledge Flag): 该位为 1 时,确认应答的字段变为有效。携带ACK标志的称为 确认报文段
    3. PSH(Push Flag): 该位为 1 时,表示接收端应该立刻从 TCP接收缓冲区中读走数据,传输给上层的应用。为 0 时,则不需要立即读取而是先进行缓存。
    4. RST(ResetFlag): 该位为 1 时,要求接收方重新建立连接。携带RST标志的称为 复位报文段 。下面讨论产生 复位报文段 的三种情况:
      • 当客户端访问一个不存在的端口时,服务器就可以返回一个RST设置为 1 的包(报文中 接受通告窗口【seq】大小为 0 ,因此不能被回应),告诉客户端关闭连接或者重新连接。
      • 可用于 异常终止 连接,不发送 结束报文段 而直接发送 复位报文段 ,发送端所有排队等待发送的数据都将被丢弃。
      • 服务器(或客户端)关闭或者异常终止了连接 ,而客户端(或服务器)由于断网、重启等原因 没有接收到发来的结束报文段 ,恢复正常后客户端(或服务器)还 维持着原来的连接 。客户端(或服务器)的这种状态称为 半打开状态,处于这种状态的连接叫 半打开连接 。如果客户端(或服务器)往 半打开连接 写入数据,对方会回应一个 复位报文段
    5. SYN(Synchronize Flag): SYN为 1 表示希望建立连接,并在其序列号的字段进行序列号初始值的设定。携带SYN标志的称为 同步报文段
    6. FIN(Finish Flag): 该位为 1 时,表示通知对方本端今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为 1 的TCP段。每个主机又对对方的FIN包进行确认应答以后就可以断开连接。不过,主机收到FIN设置为 1 的TCP端以后不必马上回复一个FIN包,而是可以等到缓冲区中所有数据都已成功发送而被自动删除之后再发。携带FIN标志的称为 结束报文段
  • 16位窗口大小: 是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方 本端的TCP接收缓冲区 还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。用于实现滑动窗口机制,来进行流量控制。
  • 16位校验和: 由发送端填充,用于检验接收的数据(TCP头部+数据部分)与发送的数据是否一致,不一致则丢弃 。校验方法CRC算法:二进制反码求和,即对报文从头开始的每个字节进行取反相加,高出16位则截断高位,与低16位相加,得到校验和。是TCP可靠传输的一个重要保障。
  • 16位紧急指针: 是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据(带外数据)的下一个字节的序号。
  • 0-40字节选项: 选项字段用于提高TCP的传输性能,主要协商和描述一些信息。因为TCP首部大小最高为60字节,而前面必须的有20字节,所以选项的大小可以为0-40字节。
kind(1字节) length(1字节) info(n字节)

kind 说明选项类型;length (如果有的话)指定该选项长度 ;info (如果有的话)指选项的具体信息。

  • kind=0是选项表结束选项。
  • kind=1是空操作(nop)选项。 没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。
  • kind=2是最大报文段长度选项。 TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为 (MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是 1460(1500-40)字节
  • kind=3是窗口扩大因子选项。 TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小是用 16位 表示的,故最大为 65535字节 ,但实际上TCP模块允许的接收通告窗口大小远不止这个数。假设TCP头部中的 接收通告窗口大小是N窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是 N*2M ,或者说 N左移M位 。注意,M的取值范围是0~14 。我们可以通过修改 proc/sys/net/ipv4/tcp_window_scaling 内核变量来启用或关闭窗口扩大因子选项。和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。 但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。
  • kind=4是选择性确认(Selective Acknowledgment,SACK)选项。 TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP模块只重新发送丢失的TCP报文段,不用发送未被确认的TCP报文段之后所有报文段。我们可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。
  • kind=5是SACK实际工作的选项。 该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个 4字节 的序号。其中 块左边沿 表示 不连续块的第一个数据的序号 ,而 块右边沿 则表示 不连续块的最后一个数据的序号的下一个序号 。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用 8字节 ,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
  • kind=8是时间戳选项。 该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。 我们可以通过修改 /proc/sys/net/ipv4/tcp_timestamps 内核变量来启用或关闭时间戳选项。

保证TCP可靠性的八种机制

为了保证可靠传输,TCP提出了8种机制:

  1. 确认应答机制
  2. 延时应答机制
  3. 捎带应答机制
  4. 超时重传机制
  5. 滑动窗口机制
  6. 流量控制机制
  7. 拥塞控制机制
  8. 快速重传机制

确认应答、延时应答与捎带应答

确认应答

TCP中,确认应答机制(ACK报文) 是保证数据可靠传输的核心机制:

  • TCP将每个字节的数据都进行了编号,即为序列号。
  • 每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发(应答)。
  • 而且此时给请求和应答都对应带上编号,既能保证数据传输没有歧义,也不会浪费太多的空间和宽带。
  • TCP规定,除了第一次握手时的SYN包之外,其他TCP报文段必须携带 确认报文

延时应答

基于确认应答机制,但接收端收到发送端发来的数据后并 不立即返回ACK应答

  • 立即返回ACK应答的话,这时候的接收缓冲区中的数据还没能够处理,缓存区的剩余大小就是窗口大小。
  • 但实际上应用程序可能很快就会读走接收缓冲区中的内容,因此我们 延迟一会 ,等待缓存区中数据被处理,那么剩余的缓存区就会大些。

是不是所有的包都可以延时应答?

  1. 数量限制:每隔 N(默认N=2) 个包就应答一次
  2. 时间限制:超过最大延时时间就应答一次(默认为 200ms

捎带应答

在延时应答的基础上,接受方和发送方都是 一发一收,所以,我们在发送数据的时候,将ACK以搭顺风车的方式发送给对方。


超时重传

实际网络中会有丢包的可能,TCP模块为每个TCP报文段都维护一个重传定时器 ,该定时器在TCP报文段第一次发送时启动。如果 超时时间内 未收到接收方的 确认报文段 ,TCP模块将 重传丢失的TCP报文段重置定时器

两种原因导致的超时重传

假设 AB 发送 TCP报文段 ,造成TCP报文段超时重传有两种原因:

  1. A发给B的TCP报文段丢了。
  2. B发给A的ACK报文段丢了。
  • 如果是第一种,那对双方没什么影响,重发就行。
  • 但是如果第二种情况,那么 B 就会收到 已经确认过的TCP报文 ,那么 B 需要能够识别出这是之前重复的包,我回应的ACK报文丢了?还是新的一次请求,需要新的ACK报文?这时候可以利用序列号,由于重发的TCP报文序列号跟之前一样,因此 B 就知道原来是 之前回应的ACK报文丢了 ,那重发一次就好了。

超时重传的时间

超时时间的设置是很有讲究的:

  • 如果超时时间设的太长,会影响整体的重传效率;
  • 如果超时时间设的太短,有可能会频繁发送重复的包,对接收方造成困扰。

Linux 中(BSD UnixWindows 也是如此),超时以 500ms 为一个单位进行控制,每次超时时间都是 500ms 的整数倍。

根据 karn算法RTO(新的重传时间)= γ × RTO(旧的重传时间)【系数 γ 的典型值是2】

  • 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。
  • 如果仍然得不到应答,等待 4*500ms 进行重传,依次类推。
  • 累计到一定的重传次数,TCP认为 网络 or 对端主机 出现异常,强制关闭连接。

超时重传的次数

Linux 有两个重要内核参数管理TCP的超时重传次数:

  • /proc/sys/net/ipv4/tcp_retries1 :指定在底层 IPARP 接管之前 最少 要执行多少次重传。默认值是 3
  • /proc/sys/net/ipv4/tcp_retries2 : 指定强制关闭连接前TCP 最多 可以执行的重传次数 ,默认值为 15 (一般对应 13~30min ,可以用 date命令 测量)。

滑动窗口

从前面的确认应答机制可以看到,如果对于每一个数据,都需要等到回复确认,才发送下一个数据,就会导致效率的低下,尤其是网络较差时,数据的往返之间过长的情况。既然这样一收一发的效率过慢,那么一次性发送多条,就可以解决这种问题,但是如果发送的数量过多,就可能会导致缓冲区溢出而丢包,为了控制发送数据的规模,于是TCP就引入了滑动窗口机制。

在TCP首部中我们看到有一个 16位窗口大小的字段 ,那个就是滑动窗口的大小,其意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据,在三次握手的时候双方进行协商,规定好通信时的 MSS窗口大小 。当滑动窗口为 0 时,发送方一般不能再发送数据报,但有两种情况除外:

  • 可以发送 紧急数据
  • 发送方可以发送一个 1字节 的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。

发送端维护发送窗口:用于限制一次能够发送的数据。

  • 后沿:数据发送的起始位置,等待确认回复的数据,如果得到了回复,则后沿移动。
  • 前沿:数据发送的结束位置,前沿减去后沿必须小于等于窗口大小,移动取决于窗口大小。

接收端维护接受窗口:用于进行数据的排序。

  • 后沿:数据接受的起始位置,移动取决于是否收到后沿数据。
  • 前沿:接收缓冲区剩余空间大小加上后沿,移动取决于接收缓冲区剩余大小。

下面举例说明,假设发送窗口尺寸为2,接收窗口尺寸为1:

  1. 初始态,发送方没有帧发出,发送窗口前后沿相重合。接收方0号窗口打开,等待接收0号帧;
  2. 发送方打开0号窗口,表示已发出0帧但尚未确认返回信息。此时接收窗口状态不变;
  3. 发送方打开0、1号窗口,表示0、1号帧均在等待确认之列。至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧。接收窗口此时状态仍未变;
  4. 接收方已收到0号帧,0号窗口关闭,1号窗口打开,表示准备接收1号帧。此时发送窗口状态不变;
  5. 发送方收到接收方发来的0号帧确认返回信息,关闭0号窗口,表示从重发表中删除0号帧。此时接收窗口状态仍不变;
  6. 发送方继续发送2号帧,2号窗口打开,表示2号帧也纳入待确认之列。至此,发送方打开的窗口又已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧,此时接收窗口状态仍不变;
  7. 接收方已收到1号帧,1号窗口关闭,2号窗口打开,表示准备接收2号帧。此时发送窗口状态不变;
  8. 发送方收到接收方发来的1号帧收毕的确认信息,关闭1号窗口,表示从重发表中删除1号帧。此时接收窗口状态仍不变。

根据窗口尺寸的大小不同,滑动窗口分为 1比特滑动窗口后退n选择重传 三种协议:

  • 1比特滑动窗口协议:发送窗口=1,接收窗口=1;
  • 后退n协议:发送窗口>1,接收窗口=1;
  • 选择重传协议:发送窗口>1,接收窗口>1。

滑动窗口协议

当发送窗口和接收窗口的大小固定为1时,滑动窗口协议退化为停等协议(stop-and-wait)。该协议规定发送方每发送一帧后就要停下来,得到回复后才能继续发送下一帧。


后退n协议

由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。

后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧。且发送方在每发送完一个数据帧时都要设置超时定时器。如果 某帧在计时器超时后仍未返回其确认信息,就要重发该帧及后续所有帧。

从这里不难看出,后退n协议一方面因连续发送数据帧而提高了效率,但另一方面,在重传时又必须把原来已正确传送过的数据帧进行重传(仅因这些数据帧之前有一个数据帧出了错),这种做法又使传送效率降低。


选择重传协议

在后退n协议中,接收方若发现错误帧就不再接收后续正确到达的帧,这显然是一种浪费,因此提出了选择重传协议。

选择重传协议(SELECTICE REPEAT)中,接收方发现某帧出错后,其后继续送来的正确的帧 虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。 一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。

显然,选择重发减少了浪费,但要求接收方有足够大的缓冲区空间。


流量控制

接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区满, 这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。

因此TCP提出了 流量控制机制:根据接收端的处理能力,来决定发送端的发送速度。

  • TCP连接阶段,双方协商窗口尺寸,同时接收方预留数据缓存区;
  • 发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认;
  • 接收端将 接收缓冲区空闲大小 放入 TCP 首部中的 窗口大小字段,通过ACK报文通知发送端。窗口大小字段越大,说明网络的吞吐量越高;
  • 发送方根据确认信息,改变窗口的尺寸,增加或者减少发送未得到确认的字节流中的字节数。
  • 如果接收端缓冲区满了,就会将窗口置为0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,询问接收端当前 接收缓冲区的空闲大小
  • 如果出现发送拥塞,发送窗口缩小为原来的一半,同时将超时重传的时间间隔扩大一倍。

而在传输过程中,发送窗口(SWND)的大小并不仅仅取决于接收窗口(RWND),还取决于下文将提到的拥塞窗口(CWND)。


拥塞控制

拥塞控制的作用是:提高网络利用率、降低丢包率,并保证网络资源对每条数据流的公平性。

拥塞控制有四个部分:

  • 慢启动(slow start)
  • 拥塞避免(congestion avoidance)
  • 快速重传(fast retransmit)
  • 快速恢复(fast recovery)

拥塞控制算法或者部分或者全部实现了上述四个部分。/proc/sys/net/ipv4/tcp_congestion_control 文件指示当前所使用拥塞控制算法。

这里介绍一个拥塞控制算法——Nagle算法

Nagle算法:为了尽可能发送大块数据,避免网络中充斥着许多小数据块。 主要用于解决交互数据流(见下文)带来的网络拥塞。

  • 要求一个TCP连接通信双方在任意时刻都最多只能发送一个未被确认的TCP报文段,在该TCP报文段的确认到达之前不能发送新的TCP报文段。
  • 发送方在等待ACK报文的同时收集本端下次需要发送的微量数据,并在确认到来时以一个TCP报文段将它们全部发送出去。这样就大大减少了网络上的微小TCP报文段的数量。

Nagle更详细的发送规则:

  1. 如果包长度达到MSS,则允许发送;
  2. 如果该包含有FIN,则允许发送;
  3. 设置了TCP_NODELAY选项,则允许发送;
  4. 未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
  5. 上述条件都未满足,但发生了超时(一般为200ms),则立即发送。

发送窗口、接收窗口、拥塞窗口

  • 拥塞控制的最终受控变量是 发送端向网络一次连续写入的数据量 ,被称为 发送窗口(SWND,Send Window)。
  • 发送窗口限定了发送端能连续发送的TCP报文段数量。这些TCP报文段 数据部分的最大长度 称为 发送者最大段大小(SMSS)。 其值一般等于 最大报文长度(MSS)。
  • 发送端要合理选择SWND大小,太小需要在网络上频繁的传输确认信息,导致通信效率下降;太大容易产生多次丢包重传(从快速局域网进入慢速局域网、接收端缓存不够溢出),导致网络拥塞。
  • 接收端 可以通过 接收窗口(RWND)来控制 发送端 的 SWND。但还不够,发送端还引入了 拥塞窗口(CWND,Congestion Window)这个状态变量。SWND值 是 RWND 和 CWND 中的较小值。

拥塞窗口

  • 发送开始的时候,定义拥塞窗口初始值为 IW(Initial Window) ,大小为 1个MSS
  • 每收到一个ACK应答,拥塞窗口: CWND*=2

第二点就是上面提到的 慢启动 :TCP模块一开始并不清楚网络的实际情况,因为需要用一种试探的方式平滑地增加CWND的大小。但慢启动实际上并不慢,如果不加限制,慢启动必然使得CWND指数级增长,并最终导致网络拥塞。为了避免网络拥塞,拥塞控制定义了 慢启动门限(slow start threshold size,ssthresh) 这个状态变量。当CWND大小超过该值时,TCP拥塞控制将进入 拥塞避免阶段

  • cwnd < ssthresh,慢开始算法。
  • cwnd>ssthresh,拥塞避免算法。采用 加法增大 的策略,即CWND不再以 2倍 的方式增加,而是转变为 每次加1 的方式。
  • cwnd = ssthresh,两者皆可。

正如上图所示,拥塞避免并不能避免网络拥塞发生,它只是 将CWND由指数增长拉低到线性增长,降低出现拥塞的可能。 当网络拥塞发生时,CWND迅速缩小,那么 发送端是如何判断拥塞发生的呢?

  • 传输超时,或者说TCP重传定时器溢出。
  • 接收到重复的确认报文段。

拥塞控制对这两种情况有不同的处理方式:

  • 第一种情况:慢启动和拥塞避免。
  • 第二种情况:快速重传和快速恢复。如果第二种情况发生在重传定时器溢出之后,则也被当成第一种情况来对待。

当发送端检测出 拥塞发生 是由于第一种情况,会执行 拥塞避免

  • ssthresh = CWND/2 :CWND是拥塞发生时的拥塞窗口大小。
  • CWND=MSS :重置CWND的值,重新开始慢启动。

快速重传和快速恢复

快速重传

假设发送方发送了 M1–M4 四个分组,接收方收到了 M1、M2、M4接收方不能确认 M4,因为 M3 没有收到。 此时接收方可以什么都不干,但 快重传算法要求接收方继续发送对M2的确认:

  • 发送方还会试着发送 M5、M6,接收方继续发送对 M2 的重复确认,这样可以让发送方知道 M3 并没有被传过来。按照规定,只要发送方收到三个重复确认(加上一个正常确认,总共发送了4个ACK报文),就立即重传确认序号指向的数据(对方未收到的报文段 M3),这样 比起等待 M3 超时后进行重传效率快了很多,因此被叫做快速重传。

快速恢复

当出现了快重传的情况时,就说明当前网络状况存在问题,但是又由于我们能够连续三次收到确认应答,就说明了当前的问题并不是很严重,没有必要重新进行慢开始。所以当前会:

  • ssthresh 设置为 当前CWND大小 的一半
  • 将 CWND 设置为 新的ssthresh
  • 然后实行拥塞避免算法

由于跳过了慢开始阶段直接进行拥塞避免,因此被称为快恢复。


连接管理机制

这篇关于三次握手四次挥手的博客很全:面试官,不要再问我三次握手和四次挥手

三次握手

  • 第一次握手:客户端发送网络包,服务端收到了。
    这样 服务端 就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
  • 第二次握手:服务端发包,客户端收到了。
    这样 客户端 就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。 不过此时 服务器并不能确认客户端的接收能力是否正常。
  • 第三次握手:客户端发包,服务端收到了。
    这样 服务端 就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

socket 接口与三次握手:

为什么握手不能两次?即客户端发起SYN连接,服务端确认后回复ACK+SYN就直接建立连接。

如果只有两次就能建立连接,那就代表着就容易产生这种情况:

  1. 如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。
  2. 客户端收到了第二个请求报文的确认,与服务器建立了连接。数据传输完毕后,就释放了连接。
  3. 然而第一个丢失的请求报文段只是在某些网络结点长时间滞留了。假定这个滞留时间未超过MSL,并且延误到连接释放以后的某个时间才到达服务端。
  4. 此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了。
  5. 但此时客户端并不想和服务器建立连接,因此忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。

为什么不用四次?

四次握手可以用但完全没有必要,建立连接的SYN和确认回复的ACK报文是可以一起发送的(上文提到过的 捎带应答),没有必要分开来增加操作。


连接超时

类似超时重传机制。

如果客户端访问一个距离它很远,或者由于网络繁忙,导致 服务器对于客户端发送出的同步报文段没有应答。 此时客户端将进行多次重连,若仍然联系不上,则通知应用程序连接超时:

  • 超时重连报文一般会有 5 个,这是由 /proc/sys/net/ipv4/tcp_syn_retries 内核变量定义的,(加上第一个正常的请求报文也就是说,通知应用程序连接超时之前,最多会发送 6 个TCP同步报文段请求与服务器连接)。
  • 每次发重连请求报文段的时间间隔是递增的:1s 、 2s 、 4s 、 8s 、 16s 、 32s 。也就是 每次重连的超时时间都增加一倍 ,如果不增加的话,那么后五次发送没有意义,同样的时间第一个到不了显然后五个很大概率从也到不了。

服务端发送了SYN和ACK后,没有收到客户端的ACK,服务端如何处理?

说明客户端可能不在线,此时发送一个RST重置连接,并且释放已有资源。


四次挥手

  • 第一次挥手: 客户端:我要说的话已经说完了。
  • 第二次挥手: 服务端:你要说的我都听到了,但是我的话还没说完。
  • 第三次挥手: 服务端:我要说的话也说完了。
  • 第四次挥手: 客户端:既然我们都说完了,那就结束通话吧。

半关闭状态 与 如何检测连接是否被对方关闭

在第二次挥手完成后,客户端不会再向服务端发送数据,但允许继续接收对方的数据。客户端这种状态为 半关闭状态。 socket网络编程接口通过 shutdown函数 提供了对半关闭的支持,Linux 也提供了诸多检测连接是否被对方关闭的方法,例如:收到结束报文段时,read 系统调用返回 0

为什么不是三次挥手?

就像上面所说的,处于 半关闭状态 是为了接受对方未发送完的数据,但是如果 服务器收到客户端的FIN报文时凑巧也没有数据要发送给客户端了 ,那么客户端可以从 FIN_WAIT1状态 直接进入 TIME_WAIT状态 ,也就是服务器 不发送 ACK 报文 ,而是 直接发送 FIN+ACK 报文 (从)。此时就是三次挥手。


TIME_WAIT

什么是MSL?

MSL(Maximum Segment Lifetime) :最长报文段寿命,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

TIME_WAIT状态存在的原因

主动关闭端 收到 被动关闭端 的 (FIN,ACK)报文 后并不立刻进入 CLOSED状态 。而是等待 2MSL 时间。

  1. 防止 旧连接TCP报文段 出现在 新连接 中。
  2. 确保 主动关闭端 最后一次 ACK 报文 能到达 被动关闭端 ,这样 被动关闭端 就可以进入 CLOSED状态

第二点详细来说:
如果过了一个MSL时间,被动关闭端还没有收到主动关闭端第四次挥手报文,被动关闭端启动 超时重传机制 ,重发第三次挥手报文,主动关闭端再一次收到第三次挥手报文 ,就知道自己之前发送的第四次挥手报文丢了,因此重置时间等待计时器为2MSL,并且重发ACK报文,从而确保双方都可以进入CLOSED状态。

如果总是能收到第三次挥手报文、但第四次挥手报文一直丢,那也只能一直发第三次挥手报文了,直到达到超时重传次数上限,被动关闭端不再重传,直接进入CLOSED状态。下面我们分析另一种情况:如果第二次重传的 第三次挥手报文 丢了怎么办呢?

首先明确如果第二次重传的 第三次挥手报文 丢了,肯定要进行第三次重传,那就分为下面两种情况:

  1. 若干个(超时重传次数上限)第三次挥手报文 都丢在半路上了,那么主动关闭端早已进入 CLOSED状态 。被动关闭端根据 超时重传机制 ,此时TCP认为网络或者对端主机出现异常,强制关闭连接。
  2. 如果 第三次重传第三次挥手报文 到达主动关闭端,但此时已经过了2MSL,那么主动关闭端会进入 CLOSED状态 ,此时主动关闭端不会理会这个报文,而是直接丢弃,不会发送ACK报文回应。那么其实就跟上一种情况没什么区别了。

更多关于重传的内容可以参考这篇博客。

避免TIME_WAIT状态

有时候我们希望避免 TIME_WAIT状态比如:服务端 主动关闭连接之后想立刻与 刚断开的客户端 重新连接。但是因为服务端总使用同一个 知名服务端口号 与客户端通信,旧连接的断开导致这个 知名服务端口号 仍处于 TIME_WAIT状态 ,导致不能立即重新建立连接。那么有两种方法避免 TIME_WAIT状态

  1. 通过 socket选项 SO_REUSEADDR 来强制进程 立即使用 处于 TIME_WAIT状态旧连接 占用的 端口
  2. 修改内核参数 /proc/sys/net/ipv4/tcp_tw_recycle 来快速回收被关闭的 socket,从而使得 TCP连接 根本就不进入 TIME_WAIT状态

值得一提的是,由于客户端一般使用系统自动分配的临时端口号来建立连接,基于随机性,临时端口号一般和程序上一次使用的端口号(还处于TIME_WAIT的连接使用的端口号)不同,因此客户端程序一般可以立即重启。

一台主机上出现大量的TIME_WAIT是什么原因?如何处理?

TIME_WAIT状态是出现在主动关闭方的,如果出现大量的TIME_WAIT,就说明有大量的连接被主动关闭,可能是恶意攻击或者爬虫等。处理方法——使用上文中 避免TIME_WAIT状态的两种方法。

一台主机上出现大量的CLOSE_WAIT是什么原因?如何处理?

CLOSE_WAIT状态是在 被动关闭方 收到 主动关闭方的FIN 后进入的,此时 被动关闭方 会等待 上层应用 处理积压数据,处理结束后才会发送FIN。而如果出现大量的CLOSE_WAIT,则说明 被动关闭方的上层应用处理有问题 ,没有正确的关闭SOCKET释放资源,所以此时就不会发送FIN,导致一直卡在CLOSE_WAIT。解决方法:查代码bug,因为问题出在服务器程序里……


保活机制

在TCP通信中,如果两端长时间没有数据往来(默认7200秒),则每隔一段时间(默认75秒),服务端就会向客户端发送一个保活探测数据报,让客户端进行回复,如果多次(默认9次)没有收到响应,则代表连接已经断开。

通过保活机制来确保如果有一端断开,能够及时处理。


TCP的数据类型

交互/成块数据流

TCP报文段所携带的应用程序数据按照长度分为两种:交互数据、成块数据。

交互数据流

交互数据仅包含很少的字节。使用交互数据的应用程序或协议对实时性要求很高。 如:telnet、ssh等。

在广域网上的交互数据流可能经受很大的延迟,携带交互数据的微小TCP报文段数量很多(一个键入就可能导致一个TCP报文段),很可能导致网络拥塞。交互数据流导致的网络拥塞可通过Nagle算法解决。

成块数据流

成块数据的长度通常为TCP报文段允许的最大数据长度(40字节)。使用成块数据的应用程序或协议对传输效率要求很高。比如:ftp。


带外数据

概念

带外数据(Out Of Band):用于迅速通告对方本端发生的重要事件。因此,带外数据比普通数据(也成为带内数据)有更高的优先级,应该总是立即被发送。带外数据的传输可以使用一条独立的传输层连接,也可以映射到传输普通数据的连接中。 实际应用中,带外数据的使用很少见,已知的仅有telnet、ftp等远程非活跃程序。

UDP没有实现带外数据传输,TCP也没有真正的带外数据。但是TCP利用头部中的 紧急指针标志紧急指针 两个字段,给应用程序提供了一种紧急方式。紧急方式利用传输普通数据的连接来传输紧急数据,来模拟带外数据的传输。

格式

发送端一次发送的多字节带外数据中只有最后一字节被当作带外数据,其他数据被当成普通数据。带有带外数据的TCP报文段头部将被设置 URG标志 ,紧急指针指向最后一个带外数据的下一个字节。

发送

如果含有带外数据的数据流很长,TCP模块将以多个TCP报文段来发送,每个报文段都设置URG标志,并且它们的紧急指针指向同一个位置——数据流中带外数据的下一个位置,但只有一个TCP报文段真正携带带外数据。

接收

接收端在接收到URG标志时开始检查紧急指针,根据指向位置确定带外数据位置,并将它读入只有 1 字节带外缓存。如果上层应用程序没有及时将带外数据从带外缓存中读出,则下一个数据流的带外数据(如果有的话)将覆盖它。

上面讨论的接收过程是默认方式,而当给TCP连接设置了SO_OBBINLINE选项(详见socket编程的相关博客)后,带外数据和普通数据一样存放在 TCP接收缓冲区 中,这种情况下如何区分带外数据和普通数据呢?

  • 紧急指针可以指出带外数据的位置
  • socket编程接口也提供了系统调用来识别带外数据

TCP粘包

对于TCP来说,每当创建一个 socket 的时候,就会同时在内核中创建一个 接收缓冲区发送缓冲区

  • 接收缓冲区: 对于接收到的数据,并非像UDP一样一条一条往上交付,而是先将接收到的数据放入缓冲区,再根据上层所需要的长度,从接收缓冲区中取出相应的数据交付。

  • 发送缓冲区: 对于发送的数据并不会直接发送,而是先存入发送缓冲区。如果数据过大,则会被拆分成多个TCP数据包发送(成块数据流)。而如果数据过小(交互数据流),则会在发送缓冲区中等待,积累一定的微量数据后再从发送缓冲区中取出数据发送。【延迟发送机制,亦可选择不存入缓冲区直接发送。】

  • 优点: 这种传输方式比较灵活,对于多个小数据,会合并为一条大的数据一次性发送过去,这样就大大的减少了发送端IO的次数和网络中的报文数量。接收方式也很灵活,接收端可以从接收缓冲区任意读取想要的数据,不必像UDP一样必须交付一条完整的报文。

  • 缺点: 因为数据会在缓冲区中进行合并或者拆分,这就导致了数据直接的边界无法控制,所以TCP交付上层应用程序的这条数据可能并非一条完整的数据,而是半条或者多条数据,从而导致上层会将多条数据按照一条来处理。这也就是TCP粘包问题。

TCP粘包:TCP可能将多条数据按照一条交付给上层应用程序。

解决方案:自行管理边界

  1. 每条数据之间以特殊字符进行间隔(如果数据中有该字符可能要转义处理)
  2. 数据定长传输,不够则补位(数据如果过短,则会传递大量无用的补位数据)
  3. 应用层协议头部定义数据长度(这里可以参考 HTTP协议UDP协议 的做法)
    • HTTP协议: 头部以 \r\n 表示结束,并且在头部的 Content-Length 确定正文长度。
    • UDP协议: 在首部中定义了数据报长度,确保每次只交付一条完整的数据。

基于TCP的应用层知名协议

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

网络协议分析 | 传输层 :史上最全UDP、TCP协议详解,一篇通~相关推荐

  1. centos7中ps显示的内容_值得收藏,史上最全Linux ps命令详解

    原标题:值得收藏,史上最全Linux ps命令详解 一.程序员的疑惑 大概在十多年前,我当时还是一个产品经理.由于一些工作的原因,需要向运维工程师学习一些linux常用命令. 当使用linux ps这 ...

  2. 史上最全的IP地址详解,速来get

    史上最全的IP地址详解 白在了解虚拟机网络之前,我们首先得了解ip地址是什么? 1.简单局域网的构成 局域网:一般称为内网 简单局域网的构成:交换机.网线.PC(其他IT终端) 交换机:用来组建内网的 ...

  3. 史上最全YYModel的使用详解

    原文链接:http://www.jianshu.com/p/25e678fa43d3 demo链接:https://github.com/walkertop/YYModel---Demo 插件链接:h ...

  4. 史上最全JavaScript数组对象详解(二)

    JavaScript数组对象详解(二) 上一篇博客我们讲到了JavaScript数组对象的创建,访问和属性,接下来一篇博客主要讲一下JavaScript数组对象的方法及使用.说到数组的方法,主要分为两 ...

  5. 史上最全交叉熵损失函数详解

    在我们自学神经网络神经网络的损失函数的时候会发现有一个思路就是交叉熵损失函数,交叉熵的概念源于信息论,一般用来求目标与预测值之间的差距.比如说我们在人脑中有一个模型,在神经网络中还有一个模型,我们需要 ...

  6. 广成子:值得收藏-史上最全Linux ps命令详解

    From 闻茂泉(广成)AliDataOps 一.程序员的疑惑   大概在十多年前,我当时还是一个产品经理.由于一些工作的原因,需要向运维工程师学习一些linux常用命令.当使用linux ps这个十 ...

  7. 史上最全jmeter逻辑控制器案例详解

    在jmeter中逻辑控制器可以控制采样器(samplers)的执行顺序.由此可知,控制器需要和采样器一起使用,否则控制器就没有什么意义了.放在控制器下面的所有的采样器都会当做一个整体,执行时也会一起被 ...

  8. agx 安装ros opencv_史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门...

    一.Python OpenCV 入门 欢迎阅读系列教程,内容涵盖 OpenCV,它是一个图像和视频处理库,包含 C ++,C,Python 和 Java 的绑定. OpenCV 用于各种图像和视频分析 ...

  9. 史上最全的OpenCV入门教程!这篇够你学习半个月了!万字长文入门

    推荐下我自己建的人工智能Python学习群:[809160367],群里有我整理的一份关于pytorch.python基础,图像处理opencv\自然语言处理.机器学习.数学基础等资源库,想学习人工智 ...

最新文章

  1. 14年阿里巴巴管理经验总监:教你管理7步心法(演讲全文)
  2. 判断数组元素相等 php,php中判断数组相等的方法以及数组运算符介绍
  3. ajax注册表单用户名实时验证,Ajax注册表单用户名实时验证..doc
  4. 【iOS XMPP】使用XMPPFramewok(五):好友列表
  5. linkedin上看到的CRM团队信息
  6. 2020亚太杯数学建模_比赛 | 2020年APMCM亚太地区大学生数学建模竞赛
  7. 操作系统上机作业--使用系统调用实现mycp
  8. 使用Docker安装的Tuleap-aio.查看admin默认密码
  9. 6代u笔记本完美支持win7_Z170等六代主板装WIN7后USB不能用实测超简单解决教程
  10. 并行、并发和代码性能优化
  11. js分享微信 ,微博 ,qq空间
  12. Shiro 支持三种方式的授权
  13. Junit +cucumber 运行报错 initiallizationError
  14. 网页设计html5留言板代码,web网页设计期末大作业_留言板制作.doc
  15. Win10安装Eclipse以及可能出现的问题
  16. 《白帽子讲web安全》读书笔记_2021-07-16
  17. 快速傅里叶变换(蝶形算法)
  18. 如何让阿三 Windows 10、11 的恢复分区(Recovery Partition)恢复到 “盖茨” 模式
  19. STM32--DSP库使用(1)
  20. Hadoop基础操作--查询集群的计算资源信息

热门文章

  1. python实战扫码下载_实例:用 Python 做一个扫码工具
  2. 安装sqlserver2008,重新启动计算机不通过的解决办法
  3. 第一人称视角获得运动方向和视角的夹角
  4. Boost学习之指针容器--pointer_container
  5. 内存spd规范_C语言内存泄露很严重,如何应对?
  6. 海量数据持久层解决方案_爱数AnyBackup重磅发布海量非结构化数据超可用解决方案...
  7. python生成动态二维码实例_python生成动态个性二维码(示例代码)
  8. springboot mysql脚本_springboot配置mysql连接的实例代码
  9. 【转】03.Dicom 学习笔记-DICOM C-Get 消息服务
  10. [你必须知道的.NET]第三十一回,深入.NET 4.0之,从“新”展望