TCP协议的这些那些事儿-7000字超全图文并茂

写在前面:这里是小王成长日志,一名在校大学生,想在学习之余将自己的学习笔记分享出来,记录自己的成长轨迹,帮助可能需要的人。欢迎关注与留言。

0. 导入-TCP概述

  1. TCP 是因特网运输层的面向连接的可靠的运输协议,与之相对的还有并不可靠的UDP协议,可以看这篇博文进行了解-没搞清运输层的UDP协议? -哎呀, 早来这看就好了啊

  2. TCP 依赖于许多基本原理,其中包括差错检测、重传、累积确认、定时器以及用于序号和确认号的首部字段 。

  3. TCP的产生历史

  • 在 20 世纪 70 年代早期,分组交换网开始飞速增长,而因特网的前身 ARPAnet 也只是当时众多分组交换网中的一个 。 这些网络都有它们各自的协议 ,彼此之间并不互通.
  • Vinton Cerf 和 Robert Kahn 这两个研究人员认识到互联这些网络的重要性,于是就发明了沟通网络的 TCP/IP 协议

1. TCP连接的建立与数据传输

  • 在TCP协议中,两个应用进程之间发送数据之前必须先建立连接(互相发送一些报文段初始化与 TCP 连接相关的许多 TCP 状态变量-就是"握手"的阶段)

  • 在TCP连接中,其连接状态完全保留在两个端系统之中.所以对于两个端系统之间的网络元素(路由器和链路层交换机)而言,他们是看不到连接的,他们也不会去维护连接状态。

  • 通过一条TCP连接,数据可以双向流动。这就叫做全双工服务,即可以双向传输数据。

TCP连接建立流程

  • 首先,我们定义发起连接的这个进程被称为客户进程,而另一个进程被称为服务器进程
  • 客户进程通知客户运输层,其想与服务器上的一个进程建立一条连接
    • 客户首先发送一个特殊的 TCP 报文段
    • 服务器用另一个特殊的 TCP 报文段来响应
    • 最后,客户再用第三个特殊报文段作为响应

建立TCP连接后

  • 两台端系统之间一旦建立起一条 TCP 连接,两个应用进程之间就可以相互发送数据了

    • 客户进程通过套接字(该进程之门)传递数据流 。 数据一旦通过该门,它就由客户中运行的 TCP 控制了
    • TCP 将这些数据引导到该连接的发送缓存 (send buffer) 里,发送缓存是在三次握手初期设置的缓存之一。
  • 接下来 TCP 就会不时从发送缓存里取出一块数据

    • 取出数据的数量受限于最大报文段长度 (Maximum Segmenl Size, MSS)

    • MSS 通常根据最初确定的由本地发送主机发送的最大链路层帧长度(即所谓的最大传输单元 (Maximum Transmission Unit , MTU)) 来设置。设置该 MSS 要保证一个 TCP 报文段(当封装在一个 IP 数据报中)加上 TCP/IP 首部长度(通常 40 字节)将适合单个链路层帧。以太网和 PPP 链路层协议都具有 1500 字节的 MTU ,因此 MSS 的典型值为 1460 字节 。

      • 最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,说明发送方能够接受的有效载荷大小。
      • 是包或帧的最大长度,一般以字节记。如果MTU过大,在碰到路由器时会被拒绝转发,因为它不能处理过大的包。如果太小,因为协议一定要在包(或帧)上加上包头,那实际传送的数据量就会过小,这样也划不来。大部分操作系统会提供给用户一个默认值,该值一般对用户是比较合适的。
    • 当 TCP 发送一个大文件,例如某 Web 页面上的一个图像时, TCP 通常是将该文件划分成长度为 MSS 的若干块(最 32比特后一块除外,它通常小于 MSS) 。

    • MSS 是指在报文段里应用层数据的最大长度,而不是指包括 TCP 首部的 TCP 报文段的最大长度。

  • TCP 为每块客户数据配上一个 TCP 首部,从而形成多个 TCP 报文段 (TCP segment) ,这些报文段被下传给网络层

  • 图例

在这里插入图片描述

2. TCP报文段结构

  • TCP报文段由首部字段和一个数据字段组成

  • 结构图例

在这里插入图片描述

首部字段

  1. 源端口号

  2. 目的端口号

这两个端口号都是用于多路分解和多路复用的,即报文段准确送达指定套接字,具体可以看我之前的博文-一文带你看懂多路复用与多路分解

  1. 检验和字段

用于检测在传输过程中报文段中的数据是否发生比特错误,具体可看我之前的博文-没搞清运输层的UDP协议? -哎呀, 早来这看就好了啊,在这篇博文的第四个副标题下提到了关于检验和的出现原因以及计算方式。

  1. 32 比特的序号字段 (sequence number field)

    • 序号依赖于传输的字节流,而非传送的报文段

    • 序号受MMS(最大报文段长度)的限制,例如现在有一个50 000字节的文件带传送,MMS为1000字节,则TCP会将其分为50个报文段,其中第一个报文段假设初始序号为0(但不一定是0,是随机选择的一个数以防止跟其他程序的报文段序号重复),则第二个报文段序号为1000(0+MMS * 1),第三个报文段序号为2000

    • 序号的选择通常在确定连接时由双方确定,均可随机选择

    • 图例

在这里插入图片描述

  1. 32 比特的确认号字段( acknowledgment number field)

    • 由于TCP的全双工特性,两个端系统之间互相都会发送数据(报文段)

    • 假设主机 A 填充进报文段的确认号为N

      • 累积确认:A已经收到序号为(N-1)及之前的所有字节
      • A接下来希望收到来自B的序号为N的报文段
      • 确认号只会有一个,即流中第一个丢失字节(或者最后一个确认的字节后面一个)
  2. 16 比特的接收窗口字段 (receive window field)

  3. 4 比特的首部长度字段 (header length field)

  4. 可选与变长的选项字段 ( options field)

    • 用于发送方与接收方协商最大报文段长度 (MSS) 时

    • 或在高速网络环境下用作窗口调节因子时使用 。

  5. 6 比特的标志字段 (fLag field)

Telnet: 序号和确认号的一个学习案例

  1. Telnet简介

    • 假设主机 A 发起一个与主机 B 的 Telnet 会话 。
    • (在客户端的)用户键人的每个字符都会被发送至远程主机;远程主机将回送每个字符的副本给客户,并将这些字符显示在 Telnet 用户的屏幕上。
    • 这种"回显" (echo back) 用于确保由 TelneL 用户发送的字符已经被远程主机收到并在远程站点上得到处理。
    • 因此,在从用户击键到字符被显示在用户屏幕上这段时间内,每个字符在网络中传输了两次 。
  2. 图解

在这里插入图片描述

  1. 第一个报文段

    • 序号为42

      • 在建立连接时双方确定主机A的初始序号为42,所以主机A发送的第一个报文段序号就是42
    • 确认号为79

      • 在建立连接时双方确定主机B的初始序号为79,所以主机A发送的第一个确认号就是79,表示接下来希望收到来自B的序号为79的报文段
    • 数据字段为字符C

      • 传输的字符
  2. 第二个报文段

    • 序号为79

      • 在建立连接时双方确定主机B的初始序号为79,所以主机B发送的第一个报文段序号就是79
    • 确认号为43

    • 1.已经收到序号为42及之前(在这里没有之前)的报文段(字节)

      • 2.接下来希望收到来自A的序号为43的报文段
    • 数据字段为字符C

      • 回显字符
  3. 第三个报文段

    • 序号为43

      • 表示这个由A发送的报文段序号为43
    • 确认号为80

      • 已经收到序号为79及之前(在这里没有之前)的报文段(字节)
      • 接下来希望收到来自B的序号为80的报文段(即是后面没了,但是他还是需要填入一个序号)

3. 往返时间的估计与超时

  1. 往返时间的估计

    • RTT(Round-Trip Time),表示为SampleRTT,在TCP协议中只会被在某个时刻测量一次,而非对所有报文段进行测量,且对于重传的报文段绝不做测量

    • 由于路由器和端系统的变化,我们测量的SampleRTT明显都会发生波动,是非典型的,我们要对SampleRTT取平均值

    • 每当获得一个新的SampleRTT,就采用如下公式更新EstimatedRTT

    • EstÌmatedRTT = (1 -α) . EstimatedRTT +α. SampleRTT

      • 指数加权移动平均 (Exponential Weighted Moving Average , EWMA)
      • 在 EWMA中的"指数"一词看起来是指一个给定的 SampleRTT 的权值在更新的过程中呈指数型快速衰减 。
    • 可见 EstimatedRTT 的新值是由以前的 EstimatedRTT 值与 SampleRTT 新值加权组合而成的 。

    • 在[RFC 6298]中给出的 α 参考值是α=0.125 (即 1/8)

  2. 往返偏差的计算

    • 测量RTT的变化,DevRTT表示RTT 偏差
    • 公式: DevRTT = (1 -β) . DevRTT +β. I SampleRTT 一 EstimatedRTT I
    • β 的推荐值为 0.25
  3. 设置和管理重传超时间隔

    • 很明显我们的超时间隔应大于我们的EstimatedRTT,但也不能大太多以免造成时延过大
    • 公式:TimeoutInterval =EstimatedRTT+4 * DevRTT

4. 可靠数据传输

  1. 我们先讨论一个高度简化版本-其只用超时恢复报文段的缺失。

    1.1 首先我们看看3 个与发送和重传有关的主要事件

    • 事件1:从上层应用程序接收数据

      • 收到数据
      • 封装进报文段
      • 赋予序号
      • 交给IP层
      • 定时器启动
    • 事件2:定时器超时

      • 定时超时重传则相应报文段

      • 然后重启定时器

    • 事件3:收到 ACK

      • 情况1:收到的序号等于sendbase

        • 累积确认

        • 若有还未确认的报文段,则重启定时器

      • 情况2:收到的序号大于sendbase

        • 累计确认

        • 若有还未确认的报文段,则重启定时器

    1.2 但是这也可能产生一些有趣的情况,考虑下列场景:

    • 接收方发送的确认报文在超时间隔之后才到达,发送方由于超时未接收到确认报文导致重传

    • 接收方发送的确认报文丢失,发送方由于超时未接收到确认报文导致重传

    1.3 超时间隔加倍

    • 对于重传的数据报,每次超时间都会加倍
    • 当然对于由于从上层接收到数据和收到ACK而启动的定时器还是正常的Timeoutlnterval 由最近的 EstimaledRTT 值与 DevRTT 值推算得到 。
    • 这是一种形式受限的拥塞控制
  2. 现在我们来进行一个更全面的描述-超时机制加上冗余确认技术

    • 对于超时重传,存在的问题可能是超时周期过长,会增加端到端的时延

    • 因此采用发送冗余ACK的技术

      • 重复发送某个报文段(最后确认字节)的ACK,表示后面的报文可能丢失了
      • 允许接收方不丢弃失序报文段
      • 一般收到三个冗余ACK,TCPP就执行快速重传,即立即发送丢失的报文段
    • 接收方会遇到的四种情况

      • 期望序号N的报文按序达到 - ACK延迟最多500ms,等待下一个报文段的是否在时间内到达,没到达则发送对于N的ACK

      • 具有所期望序号的按序报文段到达,另一个按序报文段等待ACK传输 - 对应第一种情况,直接发送ACK,以确认两个按序报文段

      • 比期望序号大的失序报文段到达,检测出间隔 - 立即发送冗余ACK

      • 能部分或完全填充接收数据间隔的报文段到达 - 立即发送最大确认ACK

  3. TCP是选择回退N步还是选择重传’?(若还对回退N步以及选择重传比较模糊可以看我之前的博文-一文带你看流水线协议,回退N步以及选择重传)

  • 想想TCP是GBN还是SR?

  • TCP 发送方仅需维持已发送过但未被确认的字节的最小序号( SendBase) 和下一个要发送的字节的序号( NextSeqNum) 。 在这种意义下, TCP 看起来更像一个 GBN 风格的协议。

  • 但是TCP又与GBN有一些区别

    • 许多TCP 实现会将正确接收但失序的报文段缓存起来 [Stevens 1994 J 。
  • 对 TCP 提出的一种修改意见是所谓的选择确认 (selective acknowledgment) [RFC 2018J ,它允许 TCP 接收方有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序报文段 。 当将该机制与选择重传机制结合起来使用时(即跳过重传那些已被接收方选择性地确认过的报文段), TCP 看起来就很像我们通常的 SR 协议 。

  • 因此, TCP 的差错恢复机制也许最好被分类为 GBN 协议与 SR 协议的混合体 。

5. 流量控制

流量控制服务的来源

  • 我们讲过TCP每一侧主机在建立连接时都会设置接收缓存
  • 当TCP连接收到正确,按序的字节后,就将数据放入接收缓存
  • 相关应用进程可以从缓存中读取数据,但不一定是刚到就去读
  • 如果进程读取数据的速度小于发送的速度就会造成接收缓存溢出
  • 流量控制服务( flow- control service) 就是TCP为其应用程序提供的消除发送方使接收方缓存溢出的可能性的服务

什么是流量控制服务

  • 流量控制服务其实就是一个速度匹配服务,用来匹配发送方的发送速率与接收方应用程序的读取速率
  • 区分于拥塞控制,两者起因不同,拥塞控制是因为网络的拥塞

流量控制服务的实现

  • TCP 通过让发送方维护一个称为接收窗口 (receive window) 的变量来提供流量控制,即接收窗口用于给发送方一个指示一一该接收方还有多少可用的缓存空间

  • 举例实现

    • 接收窗口用于给发送方一个指示一一该接收方还有多少可用的缓存空间

    • 主机 B 接收缓存为 RcvBuffer

    • 定义变量

      • LastByteRead

        • 主机 B 上的应用进程从缓存读出的数据流的最后一个字节的编号 。
      • LastByteRcvd

        • 从网络中到达的并且已放人主机 B 接收缓存巾的数据流的最后 一个字节的编号。
      • " LasLByteRcvd - LastByteRead ~ RcvBuffer "明显必须成立

    • 接收窗口用 rwnd 表示

    • 接收窗口rwnd 必须满足

      • rwnd = RcvBuffer - [LastByteRcvd - LastßyteRead ]
  • 图例

在这里插入图片描述

  • 思考一个问题

    • 假设主机 B 的接收缓存已经存满,使得 rwnd =0 。 在将 rwnd =0 通告给主机 A 之后,还要假设主机 B 没有任何数据要发给主机 A 。 此时,考虑会发生什么情况 。
    • 因为主机 B 上的应用进程将缓存清空, TCP 并不向主机 A 发送带有 rwnd 新值的新报文段;
    • 事实上 , TCP 仅当在它有数据或有确认要发时才会发送报文段给主机 A 。 这样,主机 A 不可能知道主机 B 的接收缓存已经有新的空间了,即主机 A 被阻塞而不能再发送数据!
    • 为了解决这个问题, TCP 规范中要求:当主机 B 的接收窗口为 0 时,主机 A 继续发送只有一个字节数据的报文段 。 这些报文段将会被接收方确认 。 最终缓存将开始清空,并且确认报文里将包含 一个非 0 的rwnd 值 。

6. TCP连接管理

TCP连接的建立-三次握手

  • 第一步

    • 客户端TCP向服务器TCP发送一个不含应用层数据的特殊TCP报文
    • 该报文首部的一个标志位(SYN)被置为1,因此该报文被称为SYN报文段
    • 客户随机选择一个初始序号置于该报文段的序号字段中
    • 报文段封装进一个IP数据报中,发送给服务器
  • 第二步

    • 服务器TCP接收到客户发来的SYN报文后,为该TCP分配TCP缓存和变量,并向客户发送允许连接的报文(确认报文)
    • 该允许报文SYN标志置为1
    • 该允许报文确认号置为客户的初始序号+1
    • 该允许报文携带了服务器自己选择的初始序号
    • 该确认报文也被称为SYNACK 报文段 (SYNACK segment)
  • 第三步

    • 客户收到服务器的SYNACK报文后,给该TCP分配缓存以及变量,并向服务器发送该SYNACK的确认报文
    • 确认报文确认号为服务器选择的初始序号
    • 因为已建立连接,所以标志SYN置为0
    • 这段报文可以携带应用层数据
  • 图例

在这里插入图片描述

  • 三次握手的原因

    • 确保双方都已经准备就绪了

TCP连接的拆除-四次挥手

  1. 概述
    • 在连接结束后,主机中的资源(缓存和变量)都将被释放
    • 客户首先发送一个特殊报文,该报文标志FIN置为1
    • 服务接收到FIN报文后,就像发送方回送一个确认报文
    • 服务器继续发送自己的数据
    • 服务器向客户发送自己的FIN报文(在这里FIN报文还是遵循超时间隔的规则的!,所以可能会多次发送)
    • 客户接收后向服务器回送FIN的ACK报文
    • 服务器接收到回送的ACK后直接关闭连接
    • 要注意到连接的关闭也可以由服务器发起
  2. 图例

在这里插入图片描述

  1. SYN洪泛攻击

    • SYN洪泛攻击是攻击方大量发送SYN报文而不回复SYNACK的ACK报文,使服务器为这些SYN分配资源建立大量半连接.

    • 应对手段

      • 现有的一种有效的防御系统:SYN cookie

      • 正常的过程, 半连接队列未满

        • 服务前端接收到 SYN(seq_a), 判断队列有没有如果没有满, 按照正常的处理过程, 将 SYN 放到半连接队列中, 回应SYN(seq_b)+ACK(seq_a+1)
        • 客户端回应ACK(seq_b+1), 服务端接受到客户端 ACK, 去检查半连接队列里是否有对应的 SYN, 如果有建立连接, 放到连接完成队列
      • SYN foold攻击, 半连接队列满

        • 接收到 SYN, 判断半连接队列是否满, 是否开启了syncookie, 按照syncookie进行接下来处理
        • 将接收到的 SYN 进行算法hash, 将关键的字段加密成 服务端回应的SYN的seq , 发送给客户端ACK+SYN(此时seq=hash后的而关键数值), 并丢弃SYN. (可以类比seq为base64-encode过程)
        • 如果客户端是正常的客户端会对服务器端的SYN(seq)回应ACK(seq+1)
        • 服务端接收到 ACK(seq+1), 先检查半连接队列是否存在该ACK对应的SYN, 如果不存在, 继续检查是否开启了syncookie, 如果开启了, 就检查seq是否为合法cookie, 如果是则对其进行逆运算, 恢复之前SYN, 继续操作.(可以类比base64-decode)

7.运输层相关博文


都看到这里了,各位哥哥姐姐叔叔阿姨给小王点个赞 关个注 留个言吧,和小王一起成长吧,你们的关注是对我最大的支持。
有事没事进来看看吧 : 小王的博客目录索引


如果以上内容有任何不准确或遗漏之处,或者你有更好的意见,就在下面留个言让我知道吧-我会尽我所能来回答。

已标记关键词 清除标记
表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页