传输层协议(TCP / UDP) & TCP 握手机制

参考文章:

TCP & UDP

TCP/IP 传输层中有两个代表性的协议: 1. TCP; 2. UDP;

UDP

UDP :面向无连接的数据报协议;
UDP 工作机制: 在接收到应用程序发来数据的那一刻, 就立即按照原样发送到网络上; (充当了一个不具备加工功能的中转站)

由于 UDP 不提供复杂的控制机制, 因此它也不具备 TCP 顺序控制, 重发控制等一系列操作; 这就导致 UDP 虽然可以确保发送消息的大小, 却不能保证消息一定会到达, 即使是出现网络拥堵的情况, UDP 也无法进行流量控制等避免网络拥塞的行为, 此外, 传输途中出现丢包, UDP 也不负责重发, 甚至当包的到达顺序出现乱序时也没有纠正的功能; 因此, 对于网络传输中的特殊情况, 需要应用进程自己实现对数据的处理;

UDP 特点总结

  1. UDP 是无连接的;
  2. UDP 不保证可靠交付, 主机不需要维持复杂的链接状态;
  3. UDP 是面向报文的;
  4. UDP 没有拥塞控制, 因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用, 如直播, 实时视频会议等);
  5. UDP 支持一对一、一对多、多对一和多对多的交互通信(广播通信);
  6. UDP 的首部开销小, 只有 8 个字节, 比 TCP 的 20 个字节的首部要短;

UDP 对应的应用层协议

  1. DNS: 域名解析服务, 将域名地址转换为 IP 地址; 使用 53 号端口;
  2. SNMP: 简单网络管理协议, 使用 161 号端口, 用于管理网络设备; 由于网络设备很多, 无连接的服务就体现出其优势;
  3. TFTP(Trival File Transfer Protocal): 简单文件传输协议, 可在端口 69 上使用 UDP 服务;

UDP 应用场景

  1. 包总量较少的通信(DNS、SNMP等)
  2. 视频、音频等多媒体通信(即时通信)
  3. 限定于 LAN 等特定网络中的应用通信
  4. 广播通信(广播、多播)

TCP

TCP :面向连接的流协议;
面向连接: 在数据通信开始前, 先建立两端的连接关系, 再进行数据传输;
流: 指不间断的数据结构;
当应用程序采用 TCP 发送消息时, 能将没有任何间隔的数据流按照一定的顺序发送给接收端;
TCP 控制机制: (一切控制机制的设计都是为了提供可靠性传输)

  1. 顺序控制;
  2. 重发控制;
  3. 流控制(流量控制)
  4. 拥塞控制
  5. 窗口控制

TCP 协议是如何保证可靠传输的?

  1. 数据包校验: 检测数据在传输过程中的任何变化, 若校验出包有错, 则丢弃报文段并且不给出响应, TCP 发送端接收不到确认响应, 将会超时并重发数据;
  2. 对失序数据包重排序: 应用报文被分割成多个 TCP 报文段传输, TCP 报文段到达接收端可能会失序; TCP 对失序数据进行重新排序后再交给应用层;
  3. 丢弃重复数据: 通过序列号判断并丢弃重复数据;
  4. 应答机制: 当 TCP 收到发自 TCP 连接另一端的数据, 它将发送一个确认; 这个确认不是立即发送, 通常将推迟几分之一秒;
  5. 超时重发: 当 TCP 发出一个段后, 它启动一个定时器, 等待目的端确认收到这个报文段; 如果不能及时收到一个确认, 将重发这个报文段;
  6. 流量控制: TCP 连接的每一方都有固定大小的缓冲空间; TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据, 这可以防止较快主机致使较慢主机的缓冲区溢出; TCP 使用的流量控制协议是可变大小的滑动窗口协议;

TCP 特点总结

  1. TCP 是面向连接的 (只有当两端都同意连接后才能传输数据);
  2. 每一条 TCP 连接只能有两个端点, 每一条 TCP 连接只能是点对点的(一对一);
  3. TCP 提供可靠交付的服务; 通过 TCP 连接传送的数据, 无差错、不丢失、不重复、并且按序到达;
  4. TCP 提供全双工通信; TCP 允许通信双方的应用进程在任何时候都能发送数据; TCP 连接的两端都设有发送缓存和接收缓存(与滑动窗口有关), 用来临时存放双方通信的数据;
  5. 面向字节流; TCP 中的“流”(Stream)指的是流入进程或从进程流出的字节序列; “面向字节流”的含义是: 虽然应用程序和 TCP 的交互是一次一个数据块(大小不等), 但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流;

TCP 对应的应用层协议

  1. FTP: 文件传输协议, 使用 21 端口; 下载文件, 上传主页, 都要用到 FTP 服务;
  2. Telnet: 远程登陆协议, 用户可以以自己的身份远程连接到计算机上;
  3. SMTP: 简单邮件传送协议, 现在很多邮件服务器都用的是这个协议, 用于发送邮件; 服务器开放的是 25 号端口;
  4. POP3: 与 SMTP 对应, 用于接收邮件; POP3 协议默认使用 110 端口;
  5. HTTP: 超文本传输协议, 从 Web 服务器传输超文本到本地浏览器的传送协议;

TCP 与 UDP

与 UDP 不同, TCP 充分地实现了数据传输时各种控制功能, 可以进行丢包时的重发控制, 还可以对次序乱掉的分包进行顺序控制;
此外, TCP 作为一种面向连接的协议, 只有在确认通信对端存在时才会发送数据, 从而可以控制通信流量的浪费;
根据 TCP 的这些机制, 在 IP 这种无连接的网络上也能够实现高可靠性的通信 (主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现);

区别总结

  1. TCP 面向连接, 通信可靠, 其在传送数据之前必须先建立连接, 数据传送结束后要释放连接; UDP 面向无连接,在传送数据之前不需要先建立连接, 远地主机在收到 UDP 报文后, 不需要给出任何确认;
  2. TCP 属于流协议, 传输的数据是连续不间断的; UDP 属于数据报协议, 传输数据是分割的;
  3. TCP 具有一系列完备的控制机制, 能对传输的数据进行操作, 提高通信的可靠性, 但与此同时也增大了开销, 占用了 CPU 资源; UDP 没有复杂的控制机制, 因此对数据的处理需要由应用进程自己实现, 但其优势在于首部开销小, 常用于即时通信;

TCP 和 UDP 的优缺点无法简单地, 绝对地去做比较: TCP 用于在传输层有必要实现可靠传输的情况; 而在一方面, UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信; TCP 和 UDP 应该根据应用的目的按需使用;

端口

在数据链路层中, 定义了 MAC 地址, 用来识别同一链路中不同的计算机, 其中 MAC 地址是固定的, 其在厂商生产网卡时就已经写入并且每个网卡都有属于自己的 MAC 地址;
在网络层 IP 协议中, 定义了 IP 地址; 用来识别 TCP/IP 网络中互连的主机和路由器; IP 地址也是主机或路由器所唯一持有的, 但与 MAC 地址不同在于, IP 地址可人为选择并更改;
在传输层也有这种类似于地址的概念, 那就是端口号; 端口号用来识别同一台计算机中进行通信的不同应用程序; 因此, 它也被称为程序地址;

个人理解: 应用程序 -> 主机/路由器 -> 网卡 (端口号 -> IP 地址 -> MAC 地址)

尽管端口号可以识别一台计算机上不同的应用程序, 但是只凭端口号识别通信是远远不够的;

如上图所示: 一次通信可能由同一计算机不同端口号发出, 可能由不同 IP 地址的计算机发出, 甚至由不同的协议发出;
因此, 一次通信识别需要通过 IP 地址, 端口号, 协议号共同判断, 这些信息都包含在 IP 协议和 TCP 协议的首部;

端口号分配

  1. 静态分配: 人为给每个应用程序注册分配的端口号; 一些广泛使用的应用协议(例如 HTTP、FTP、TELNET 等)其端口号是被静态分配的, 即这些端口号已经被专门注册用于表示对应的应用协议, 我们称这些端口号为”知名端口号”, 分布在 0~1023 之间;
  2. 时序分配: 客户端应用程序不自己设置端口号, 而是全权交给操作系统进行分配;

TCP 三次握手与四次挥手

三次握手


概念: 客户端与服务端通过发送三个包来确认一次 TCP 连接的过程;

流程:
第一次握手: 客户端将标志位 SYN 置为1, 随机产生一个序列号 seq=J, 并将该数据包发送给服务器端, 客户端进入 SYN_SENT 状态, 等待服务器端确认;
第二次握手: 服务器端收到数据包后由标志位 SYN=1 知道客户端请求建立连接, 服务器端将标志位 SYN 和 ACK 都置为1, ack=J+1, 随机产生一个序列号 seq=K (后续会提及, 序列号的作用就是告知下一次传输报文段的起始位置, 避免接收重复数据), 并将该数据包发送给客户端以确认连接请求, 服务器端进入 SYN_RCVD 状态;
第三次握手: 客户端收到确认后, 检查 ack 是否为 J+1, ACK 是否为 1, 如果正确则将标志位 ACK 置为 1, ack=K+1, 并将该数据包发送给服务器端, 服务器端检查 ack 是否为 K+1, ACK 是否为 1, 如果正确则连接建立成功, 客户端和服务器端进入 ESTABLISHED 状态, 完成三次握手, 随后客户端与服务器端之间可以开始传输数据了;

为什么不只进行两次握手?

防止已经失效的连接请求报文段传送到接收端, 进而产生错误;
示例:
发送端 A 发出的第一个连接请求报文段并没有丢失, 而是在网路结点长时间滞留了, 以致于延误到连接释放以后的某个时间段才到达接收端 B;
本来这是一个早已失效的报文段; 但是 B 收到此失效的链接请求报文段后, 就误认为 A 又发出一次新的连接请求; 于是就向 A 发出确认报文段, 同意建立连接;

我们已知第二次 TCP 握手时, 接收端 B 一旦接收到报文段就会返回确认响应, 此时若不进行第三次握手, 发送端 A 接收到确认响应后就会建立连接, 此时接收端 B 会并一直等待发送端 A 通过建立的连接通道发来数据, 导致接收端 B 的许多资源白白浪费; 如果采用了三次握手, 由于 A 实际上并没有发出建立连接请求, 所以不会理睬 B 的确认, 也不会向 B 发送数据; B 由于收不到确认, 就知道 A 并没有要求建立连接;

为什么不需要四次握手?

有人可能会说 A 发出第三次握手的信息后在没有接收到 B 的请求就已经进入了连接状态, 那如果 A 的这个确认包丢失或者滞留了怎么办?
我们需要明白一点, 完全可靠的通信协议是不存在的; 在经过三次握手之后, 客户端和服务端已经可以确认之前的通信状况, 都收到了确认信息; 所以即便再增加握手次数也不能保证后面的通信完全可靠, 所以是没有必要的;

简单来说, 就是前两次交换连接信息时, 都已经确认双方是可以互相通信的, 后续再增加握手次数也是得到同样的结果(除非后续出了一些意外, 这是小概率事件, 因为我们无法保证通信完全可靠);

四次挥手


概念: 客户端与服务端通过发送四个包来终止本次 TCP 连接的过程;

为什么要进行四次挥手才确认本次 TCP 连接终止?
这是由于TCP连接是全双工的(即两端可同时传输数据), 因此, 每个方向都必须要单独进行关闭;
当一方完成数据发送任务后, 发送一个 FIN 来终止这一方向的连接, 但是该连接终止是单向的, 接收端收到一个 FIN 只是意味着这一方向上没有数据流动了, 即不会再收到数据了, 但是将接收端作为发送端仍可以在它的对向 TCP 连接上发送数据, 直到这一方向也发送了 FIN;

流程:
中断连接端可以是客户端, 也可以是服务器端;
第一次挥手: 客户端发送一个 FIN=M, 用来关闭客户端到服务器端的数据传送, 客户端进入 FIN_WAIT_1 状态; 意思是说”我客户端没有数据要发给你了”, 但是如果你服务器端还有数据没有发送完成, 则不必急着关闭连接, 可以继续发送数据;
第二次挥手: 服务器端收到 FIN 后, 先发送 ack=M+1, 告诉客户端, 你的请求我收到了, 但是我仍有数据没有发送完, 请继续等待; 这个时候客户端就进入 FIN_WAIT_2 状态, 继续等待服务器端的 FIN 报文;
第三次挥手: 当服务器端确定数据已发送完成, 则向客户端发送 FIN=N 报文, 告诉客户端我这边数据发完了, 准备好关闭连接了; 服务器端进入 LAST_ACK 状态;
第四次挥手: 客户端收到 FIN=N 报文后, 就知道可以关闭连接了, 但是他还是不相信网络, 怕服务器端不知道要关闭, 所以发送 ack=N+1 后进入TIME_WAIT状态, 如果 Server 端没有收到 ACK 则可以重传; 服务器端收到 ACK 后, 就知道可以断开连接了; 客户端等待了 2MSL 后依然没有收到回复, 则证明服务器端已正常关闭, 那好, 我客户端也可以关闭连接了; 最终完成了四次握手;

上述流程一方主动关闭, 另一方被动关闭; 实际使用场景中也存在两方同时主动关闭;

为什么 TIME-WAIT 状态必须等待 2MSL 的时间呢?

  1. 为了保证发送端 A 发送的最后一个 ACK 报文段能够到达接收端 B; 这个 ACK 报文段有可能丢失, 因而使处在 LAST-ACK 状态的 B 收不到对已发送的 FIN + ACK 报文段的确认; B 会超时重传这个 FIN+ACK 报文段, 而 A 就能在 2MSL 时间内(超时 + 1MSL 传输)收到这个重传的 FIN+ACK 报文段; 接着 A 重传一次确认, 重新启动 2MSL 计时器; 最后, A 和 B 都正常进入到 CLOSED 状态; 如果 A 在 TIME-WAIT 状态不等待一段时间, 而是在发送完 ACK 报文段后立即释放连接, 那么就无法收到 B 重传的 FIN + ACK 报文段, 因而也不会再发送一次确认报文段, 这样, B 就无法按照正常步骤进入 CLOSED 状态;

    简单来说, 若发送端在最后发送 ACK 告诉服务端可以执行关闭通道后, 立马断开发送端, 那么假设 ACK 并没有发送成功, 那么服务端就永远都关闭不上了;

  2. 防止已失效的连接请求报文段出现在本连接中; A 在发送完最后一个 ACK 报文段后, 再经过时间 2MSL, 就可以使本连接持续的时间内所产生的所有报文段都从网络中消失; 这样就可以使下一个连接中不会出现这种旧的连接请求报文段;

为什么第二次跟第三次不能合并, 第二次和第三次之间的等待是什么?

当服务器执行第二次挥手之后, 此时就表明客户端不会再向服务端请求任何数据, 但是服务端可能还正在给客户端发送数据(可能是客户端上一次请求的资源还没有发送完毕), 所以此时服务端会等待把之前未传输完的数据传输完毕之后再发送关闭请求;

TCP 控制机制

重发机制

ACK 概念: 接收端成功接收到发送端传输数据后的一个响应信息;
重发机制作用: 有效防止了数据在传输过程中发生丢包导致传输失败的问题; 它主要通过 ACK 判断是否丢包, 并在丢包时重发数据, 保证数据能够到达对端, 从而实现可靠传输;

在 TCP 中, 当发送端的数据到达接收端时, 接收端主机会返回一个已收到消息的通知; 这个消息叫做确认应答(ACK); 当发送端将数据发出之后会等待对端的确认应答; 如果有确认应答, 说明数据已经成功到达对端; 反之, 则数据丢失的可能性很大; 若发送端在一定时间内没有等待到确认应答, 就会被认定为数据已经丢失, 并进行重发;

重发机制主要是依靠 ACK 确认应答来判断是否丢包的, 而未收到 ACK 应答信息又主要分为两种情况:

  1. 发送过程中数据丢失;
  2. 接收端数据已接收, 但ACK 确认应答在返回过程中丢失; 这种情况也会导致发送端误以为数据没有到达目的地而重发数据;
  3. 此外, 也有可能因为一些其他原因导致确认应答延迟到达, 在源主机重发数据以后才到达的情况;

针对接收端收到数据但 ACK 返回失败的问题, TCP 的重发机制会重复发送数据段并被接收端接收, 为了避免目标主机反复收到相同的数据, TCP 引入了序列号, 让目标主机具备放弃重复数据段的能力;

序列号


序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号; 接收端查询接收数据 TCP 首部中的序列号和数据的长度, 将自己下一步应该接收的序列号作为确认应答返送回去; 通过序列号和确认应答号, TCP 能够识别是否已经接收数据, 又能够判断是否需要接收, 从而实现可靠传输;

重发超时的判定

TCP 触发重发机制的依据是重发超时;
重发超时: 指在重发数据之前, 人为设定的给予等待 ACK 确认应答的时间阈值, 只要超出这个阈值仍没有接收到确认应答, 将会触发重发机制, 发送端将进行数据重发;
重发超时的理想阈值: TCP 在每次发包时都会计算往返时间及其偏差; 重发超时的阈值设定应当略大于”往返时间差 + 时间偏差”;
在 BSD 的 Unix 以及 Windows 系统中, 超时都以0.5秒为单位进行控制, 因此重发超时都是0.5秒的整数倍; 不过, 最初其重发超时的默认值一般设置为6秒左右;
数据被重发之后若还是收不到确认应答, 则进行再次发送; 此时, 等待确认应答的时间将会以2倍、4倍的指数函数延长; 此外, 数据也不会被无限、反复地重发, 达到一定重发次数之后, 如果仍没有任何确认应答返回, 就会判断为网络或对端主机发生了异常, 强制关闭连接; 并且通知应用通信异常强行终止;

TCP 报文段

传输层的 TCP 协议数据单元为 “段”;
客户端与服务端在建立 TCP 连接时, 会确认所发送数据包的单位, 即最大消息长度(MSS); 最理想的情况是, 最大消息长度正好是 IP 中不会被分片处理的最大数据长度;
TCP 在传送大量数据时, 是以 MSS 的大小将数据进行分割发送; 进行重发时也是以 MSS 为单位;
MSS 在三次握手时, 在两端主机之间被计算得出; 两端的主机在发出建立连接的请求时, 会在 TCP 首部中写入 MSS 选项, 告诉对方自己的接口能够适应的 MSS 的大小; 然后会在两者之间选择一个较小的值投入使用;

窗口控制


TCP 以1个段为单位, 每发送一个段进行一次确认应答的处理; 这就意味着原本完整数据仅需一次传输, 现在需要分成多段逐个传输, 该传输方式就导致包的往返时间越长通信性能就越低;
为解决数据分段后往返次数增多导致通信性能降低的问题, TCP 引入了窗口这个概念: 确认应答不再是以每个分段, 而是以更大的单位进行确认; 即发送端发送一个报文段后不需要一直等待本次的确认应答, 而是继续发送; 窗口机制的提出实现了使用缓冲区思想, 通过对多个段同时进行确认应答的功能;
窗口大小: 指无需等待确认应答而可以继续发送数据的最大值; (上图窗口大小为 4)

窗口内的重发机制

我们已知, 丢包出现的情况一般分为两种:

  1. 接收数据但 ACK 确认应答未能返回; 在这种情况下, 数据已经到达对端, 不需要再进行重发; 因为窗口有统一收集确认应答再一次性发送的机制, 因此即使有少部分确认应答丢失也不会触发重发机制, 因为丢失的确认应答可以通过下一个确认应答进行确认;
  2. 发送端传输的报文段数据丢失; 在窗口中, 某个报文段丢失会导致接收端收到一个自己应该接收的序列号以外的数据, 会返回到当前为止所收到数据的确认应答; 因此在窗口机制中出现报文段丢失时, 同一个序列号的确认应答将会被重复不断地返回, 而发送端主机如果连续 3 次收到同一个确认应答, 就会将其对应的数据进行重发; 这种机制比之前提到的超时管理更加高效, 因此也被称为高速重发控制;
    ![](/img/posts_img/TCP 窗口内重发机制2.png)

流量控制

作用: 控制发送方发送速率, 保证接收方来得及接收; 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小, 从而影响发送方的发送速率; 将窗口字段设置为 0, 则发送方不能发送数据;
TCP 利用滑动窗口实现流量控制;

滑动窗口

滑动窗口是一种流量控制技术;
解决的问题: 早期的网络通信中, 通信双方不会考虑网络的拥挤情况直接发送数据; 由于大家不知道网络拥塞状况, 同时发送数据, 导致中间节点阻塞掉包, 谁也发不了数据, 所以就有了滑动窗口机制来解决此问题;

滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据; 发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据; 当滑动窗口为 0 时, 发送方一般不能再发送数据报, 但有两种情况除外, 一种情况是可以发送紧急数据, 例如, 允许用户终止在远端机上的运行进程; 另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小;

此外, 滑动窗口还可以用来处理数据传输丢包的情况:

设置一个滑动窗口包含当前正在发送的数据, 在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据; 与此同时, 发送端会对当前滑动窗口内的数据进行缓存, 当数据发出后若如期收到确认应答, 则不再进行重发, 并将该条数据从发送端缓存中清除; 在收到确认应答的情况下, 将窗口滑动到确认应答中的序列号的位置, 顺序发送后续报文段;

拥塞控制

拥塞: 某段时间内, 对网络中某一资源的需求超过了该资源所能提供的可用部分, 从而导致网络性能变差的现象;

例如, 电脑遭受病毒攻击, 病毒在短时间内对网络中某一资源发起大量请求, 导致服务器端疲于处理该类请求而顾及不到其他正常的请求, 于是用户就会感觉到网络明显变慢;

拥塞控制: 防止过多的数据注入到网络中, 使网络中的路由器或链路不致于过载; 拥塞控制具有全局性特点, 它需要掌控所有主机, 路由器以及与降低网络传输性能有关的所有因素, 从而估算出网络现阶段所能承受的网络负荷; 除此之外, TCP 发送端还需维护一个拥塞窗口 (cwnd) 的状态变量, 根据评估的网络拥塞程度, 动态调整拥塞窗口的大小, 最终取拥塞窗口和接收方的接受窗口中较小的一个作为发送端的发送窗口大小;

拥塞控制和流量控制不同, 前者是一个全局性的过程, 而后者指点对点通信量的控制;

拥塞控制算法

TCP 的拥塞控制采用了四种算法, 即:慢开始、拥塞避免、快重传和快恢复; 在网络层也可以使路由器采用适当的分组丢弃策略(如:主动队列管理 AQM), 以减少网络拥塞的发生;

  1. 慢开始
    思路: 当主机最开始发送数据时, 由于现在还不知道网络的拥塞情况(第一次发送时还无法评估计算), 若此时立即把大量数据字节注入到网络, 很可能会引起网络阻塞; 因此在第一次发送数据时, 一般会先探测一下, 即由小到大逐渐增大发送窗口, 确保不会因为首轮发送数据过大导致拥塞 (cwnd 初始值为 1, 每经过一个传播轮次, cwnd 加倍);
  2. 拥塞避免
    思路: 控制拥塞窗口大小缓慢增大, 即每经过一个往返时间 RTT 就把发送方的 cwnd 加 1;
  3. 快重传和快恢复 (fast retransmit and recovery, FRR)
    作用: 快速恢复丢失的数据包
    没有 FRR, 如果数据包丢失了, TCP 将会使用定时器来要求传输暂停, 在暂停的这段时间内, 没有新的或复制的数据包被发送;
    有了 FRR, 如果接收机接收到一个不按顺序的数据段, 它会立即给发送机发送一个重复确认; 如果发送机接收到三个重复确认, 它会假定确认件指出的数据段丢失了, 并立即重传这些丢失的数据段;

    具体实现参考”窗口内的重发机制”;