Skip to content

传输层

网络互连模型

请求过程

image-20230620164830494

网络分层

image-20230620165045379

传输层

传输层有2个协议

  • TCP(Transmission Control Protocol),传输控制协议
  • UDP(User Datagram Protocol),用户数据报协议

image-20230626104242178

TCP 面向连接:首先需要建立连接,3 次握手,之后可以通过一条管道来互相发数据。发送完数据要断开连接,要不然服务器会同一个端口去监听客户端发送过来的数据,这样会占用服务器资源

UDP基础

数据格式

UDP 是无连接的,减少了建立和释放连接的开销

  • UDP 尽最大能力交付,不保证可可靠交付
  • 因此不需要维护一些复杂的参数,首部只有 8 个字节(TCP 的首部至少 20 个字节)

image-20230626111427876

UDP 长度(Length)

  • 占 16 位,首部的长度 + 数据的长度

检验和

  • 检验和的计算内容:伪首部 + 首部 + 数据
  • 伪首部:仅在计算检验和时起作用,并不会传递给网络层

image-20230626111554754

端口号

UDP 首部中端口号是占用 2 字节(16 位)

  • 可以推测出端口号的取值范围是:0 ~ 65535

客户端的源端口是临时开启的随机端口

防火墙可以设置开启/关闭某些端口来提高安全性

img

常用命令行:

  • netstat -an:查看被占用的端口
  • netstat -anb:查看被占用的端口、占用端口的应用程序
  • telenet 主机 端口:查看是否可以访问主机的某个端口

安装 telnet:控制面板-程序-启用或关闭Windows功能-勾选Telnet Client-确定

TCP基础

数据格式

数据偏移

  • 占 4 位,取值范围是 0x0101~0x1111
  • 乘以 4:首部长度(Header Length),二进制乘以 4 才是最终长度
    • 0b0101:20(最小值)
    • 0b1111:60(最大值)

保留

  • 占 6 位,目前全为 0

image-20230626150234197

小细节

有些资料中,TCP 首部的保留(Reserved)字段占 3 位,标志(Flags)字段占 9 位

image-20230626151622137

image-20230626153041267

UDP 的首部中有个 16 位的字段记录了整个 UDP 报文段的长度(首部 + 数据)

  • 但是,TCP 的首部中仅仅有 4 位的字段记录了 TCP 报文段的首部长度,并没有字段记录 TCP 报文段的数据长度

分析

  • UDP 首部中占 16 位的长度字段是冗余的,纯粹是为了保证首部是 32bit 对齐
  • TCP/UDP 的数据长度,完全可以由 IP 数据包的首部推测出来

传输层的数据传输层 = 网络层的总长度 - 网络层的首部长度 - 传输层数据首部长度

image-20230626154139738

校验和

跟 UDP 一样,TCP 校验和的计算内容:伪首部 + 首部 + 数据

  • 伪首部:占用 12 字节,仅在计算校验和时起作用,并不会传递给网络层

image-20230626154315127

标识位

URG(Urgent)紧急

  • 当 URG=1 时,紧急指针字段才有效。表明当前报文段中有紧急数据,应优先尽快传送

ACK(Acknowledgment)确认

  • 当 ACK=1 时,确认号字段才有效

PSH(Push)

RST(Reset)重置

  • 当 RST=1 时,表明连接中出现严重差错,必须释放连接,然后再重新建立连接

SYN(Synchronization)同步

  • 当 SYN=1、ACK=0 时,表明这是一个建立连接的请求
  • 若对方同意建立连接,则回复 SYN=1、ACK=1

FIN(Finish)结束

  • 当 Fin=1 时,表明数据已经发送完毕,要求释放连接

image-20230626154434311

序号、确认号、窗口

序号(Sequence Number)

  • 占 4 字节
  • 首先,在传输过程的每一个字节都会有一个编号
  • 在建立连接后,序号代表:这一次传给对方的 TCP 数据部分的第一个字节的编号

确认号(Acknowledgment Number)

  • 占 4 字节
  • 在建立连接后,确认代表:期望对方下一次传过来的 TCP 数据部分的第一个字节的编号

窗口(Window)

  • 占 2 字节
  • 这个字节有流量控制功能,用以告知对方下一次允许发送的数据大小(字节为单位)

要点

  • 可靠传输(丢掉的包都发给你)
    • 等了一段时间之后发现对方没有给回复,就重新传(超时重传)
    • 如果是一个一个发,给确认再发下一个,这样效率很低。可以一口气发很多个包,之后统一回复
    • 连续收到 3 个重新确认,也重新传(快重传)
  • 流量控制(发包慢一点)
    • 互相告诉接收窗口大小(点对点、端对端)
    • 通过控制拥塞窗口、接收窗口来控制发送窗口
  • 拥塞控制(大家一起维护一个网络)
    • 慢开始("指数增大"),当拥塞窗口达到一定阈值,开始拥塞避免("加法增大")
    • 当包发到一定程度,会出现网络拥塞,开始阈值减半("乘法减小")
    • 之后再次循环如上操作
    • 新版本改为 快重传+快恢复,直接从阈值开始进行拥塞避免("加法增大")
  • 连接管理
    • 建立连接
    • 释放连接

TCP可靠传输

停止等待ARQ协议

ARQ(Automatic Repeat-reQuest),自动重传请求

image-20230626170638886

image-20230626170711211

连续ARQ协议+滑动窗口协议

image-20230626172747794

  • 假设每一组数据是 100 个字节,代表一个数据段的数据
  • 每一组一个编号

SACK(选择性确认)

  • 在 TCP 通信过程中,如果发送序列中间某个数据包丢失(比如:1、2、3、4、5 中的 3 丢失了)
  • TCP 会通过重传最后确认的分组后续的分组(最后确认的是 2,会重传 3、4、5)
  • 这样原先已经正确传输的分组也可能重复发送(比如:4、5),降低了 TCP 性能
  • 为了改善上述情况,发展出了 SACK(Selective Acknowledgment 选择性确认)技术
    • 告诉发送方哪些数据丢失,哪些数据已经提前收到
    • 使 TCP 只重新发送丢失的包(比如:3),不用发送后续所有的分组(比如:4、5)

image-20230626175608267

SACK 信息会放在 TCP 首部的选项部分

image-20230626180459883

  • Kind:占 1 字节。值为 5 代表这是 SACK 选项
  • Length:占 1 字节。表明 SACK 选项一共占用多少字节
  • Left Edge:占 4 字节,左边界
  • Right Edge:占 4 字节,右边界

image-20230626180722126

一对边界信息需要占用 8 字节,由于 TCP 首部的选项部分最多 40 字节,所以

  • SACK Kind、Length 为固定位,占用 2 字节
  • SACK 选项最多携带 4 组边界信息
  • SACK 选项的最大占用字节数 = 4 * 8 + 2 = 34

TCP疑问

重发次数

若有个包重传了 N 次还是失败,会一直重传到成功为止么?

  • 这个取决于系统的设置,比如有些系统,重传 5 次还未成功就会发送 reset 报文(RST)断开 TCP 连接

image-20230627110758891

不足接收窗口大小

如果接收窗口最多能接收 4 个包,但是发送方只发了 2 个包,接收方如何确定后面还有没有 2 个包?

  • 等待一定时间后没有第 3 个包,就会返回确认收到 2 个包发给发送方

为什么传输层分割

为什选择在传输层就将数据 "大卸八块" 分成多个段,而不是等到网络层再进行分片传递给数据链路层?

  • 因为可以提高重传的性能
  • 需要明确的是:可靠传输是在传输层进行控制的
    • 如果传输层不分段,一旦出现数据丢失,整个传输层的数据都得重传
    • 如果传输层分了段,一旦出现数据丢失,只需要重传丢失的那些段即可

TCP流量控制

RWND(Receive Window)滑动窗口

  • 滑动窗口技术是 TCP 流量控制的核心,存在于 TCP 的 Header 中,主要用于并发处理网络 Seq
  • 滑动窗口是动态改变的,随着发送放每次发送 Seq 的时候,接收方都会根据当前机器的执行效率、缓存上限、当前缓存大小得出一个合适的窗口大小,并且随着 Ack 回传到发送方
  • 发送方在下次发送数据包的时候,就可以根据新的窗口大小去发送数据了

image-20230627142501441

流量控制

如果接收方的缓存区满了,发送方还在疯狂着发送数据

  • 接收方只能把收到的数据包丢掉,大量的丢包会极大的浪费网络资源
  • 所以要进行流量控制

什么是流量控制?

  • 让发送方的发送速率不要太快,让接收方来得及接收处理

原理:

  • 通过确认报文中窗口字段来控制发送方的发送速率
  • 发送方的发送窗口大小不能超过接收方给出窗口大小
  • 当发送方接收到窗口的大小为 0 时,发送方就会停止发送数据

特殊情况

有一种特殊情况

  • 一开始,接收方给发送方发送了 0 窗口的报文段
  • 后面,接收方又有了一些存储空间,给发送方发送的非 0 窗口的报文段丢失了
  • 发送方的发送窗口一直为 0,双方陷入僵局

解决方案

  • 当发送方收到 0 窗口通知时,这时发送方停止发送报文
  • 并且同时开启一个定时器,隔一段时间就发个测试报文去询问接收方最新的窗口大小
  • 如果接收的窗口大小还是 0,则发送方再次刷新启动定时器

TCP拥塞控制

拥塞控制

  • 防止过多的数据注入到网络中
  • 避免网络中的路由器或链路过载

拥塞控制是一个全局性的过程

  • 涉及到所有主机、路由器
  • 以及与降低网络传输性能有关的所有因素
  • 是大家共同努力的结果

相比而言,流量控制是点对点通信的控制

image-20230627144824934

拥塞方法

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

几个缩写:

  • mss(maximum segment size):每个段最大的数据部分大小

    在建立连接时确定

    数据链路层(以太网帧规定1500字节) -> 网络层(首部包20字节,数据包1480) -> 传输层(首部段至少20字节,数据段至多1460字节)

    帧(frame) -> 包(packet) -> 段(segment)

  • cwnd(congestion window):拥塞窗口。发送方自己调整的

  • rwnd(receive window):接收窗口。接收方告诉发送方

  • swnd(send window):发送窗口

    • swnd = min(cwnd, rwnd)

cwnd=1460

image-20230627152425342

rwnd=1380

image-20230627152745504

TCP拥塞方法

慢开始

image-20230627153316987

  • cwnd 的初始值比较小,然后随着数据包倍接收方确认(收到一个 ACK)
  • cwnd 就成倍增长(指数级)

image-20230627153617227

拥塞避免

  • ssthresh(slow start threshold):慢开始阈值,cwnd 达到阈值后,以线性方式增加
  • 拥塞避免(加法增大):拥塞窗口缓慢增大,以防网络过早出现拥塞
  • http://email.163.com/乘法减少:只要网络出现拥塞,把 ssthresh 减半,与此同时,执行慢开始算法(cwnd 又恢复到初始值)

image-20230627153758914

快重传

接收方

  • 每收到一个失序的分组后就立即发出重复确认
  • 使发送方及时知道有分组没有到达
  • 而不要等待自己发送数据时才进行确认

发送方

  • 只要连续收到三个重复确认(总共 4 个相同的确认),就应当立即重传对方尚未收到的报文段
  • 而不必继续等待重传计时器到期后再重传

image-20230627160505238

image-20230627164532156

快恢复

当发送方连续收到三个重复确认,说明网络出现拥塞

  • 就执行 "乘法减小" 算法,把 ssthresh 减为拥塞峰值的一半

与慢开始不同之处是现在不执行慢开始算法,即 cwnd 现在不恢复到初始值

  • 而是把 cwnd 值设置为新的 ssthresh 值(减小后的值)
  • 然后开始执行拥塞避免算法("加法增大"),使拥塞窗口缓慢线性增大

image-20230627160821405

发送窗口的最大值

发送窗口最大值:swnd = min(cwnd, rwnd)

  • rwnd < cwnd 时,是接收方的接收能力限制发送窗口的最大值
  • cwnd < rwnd 时,则是网络的拥塞限制发送窗口的最大值

TCP序号确认号

详细

image-20230627165922957

image-20230627170441004

  1. seq=0 没有发送数据,ack=0 对方没发东西,没有东西可确认
  2. seq=0 没有发送数据,ack=seq1+1 期望对方发下一个字节
  3. seq=1 虽然上一次没有发数据,但是为了响应服务器 seq 置为 1,期望对方发下一个字节 ack=seq2+1
  4. 客户端发 http 给服务器,这时有数据了,假设占用了 k 字节,ack=seq2+1 期望对方发下一个字节,seq=1 虽然上一次没有发数据,但是为了响应服务器 seq 置为 1
  5. ack=k+1 ack是对上一次对方发过来的回应

image-20230627173212053

image-20230627175920840

image-20230627180544893

真正序号

相对,咱们理解的

image-20230627171541680

真正的序号是在建立连接时确认的,如果用 1、1025 这样的数很容易暴露出示第几个包

  • 1 syn=1, seq=123456
  • 2 syn=1, ack=1, seq=234567
  • 4 seq = 123456+1, len=100
  • 5 ack=123456+1+100
  • 6 seq=234567+1, len=100
  • 7 ack=234567+1+100

image-20230627172148218

image-20230628090851515

TCP建立连接

3次握手

  • closed:client 处于关闭状态

  • listen:server 处于监听状态,等待 client 连接

    服务器首先要占用一个端口监听把服务器程序启动,之后进行监听

  • syn-rcvd(received):表示 server 接收到了 SYN 报文,当收到 client 的 ACK 报文后,它会进入到 ESTABLISHED 状态

  • syn-sent:表示 client 已发送 SYN 报文,等待 server 的第 2 次握手

  • established:表示连接已经建立

image-20230628111357400

前 2 次握手特点:

  • SYN 都设置为 1 SYN=1
  • 数据部分的长度都为 0 LEN=0
  • TCP 头部的长度一般是 32 字节
    • 固定头部:20字节
    • 选项部分:12字节
  • 双方会交换一些信息
    • 比如:MSS、是否支持 SACK、Window scale(窗口缩放系数)等
    • 这些数据都放在了 TCP 头部的选项部分中(12 字节)

为什么要3次握手

为什么建立连接的时候,要进行 "3次握手"?2次不行么?

  • 主要目的:防止 server 端一直等待,浪费资源

如果建立连接只需要 "2次握手",可能会出现的情况

  • 假设 client 发出的第一个连接请求报文段,因为网络延迟,在连接释放以后的某个时间才到达 server
  • 本来这就是一个早已失效的连接请求,但 server 收到此消失的请求后,误认为是 client 再次发出的一个新的连接请求
  • 于是 server 就向 client 发出确认报文段,同意建立连接
  • 如果不采用 "3次握手",那么只要 server 发出确认,新的连接就建立了
  • 由于现在 client 并没有真正想连接服务器的意愿,因此不会理睬 server 的确认,也不会向 server 发送数据,这样,server 的很多资源就白白浪费掉了

采用 "3次握手" 的办法可以防止上述现象发生

  • 例如上述情况,client 没有向 server 的确认发出确认,server 由于收不到确认,就知道 client 并没有要建立连接

第3次握手失败了,会怎么处理?

  • 此时 server 的状态为 SYN-RCVD,若等不到 client 的 ACK,server 会重新发送 SYN+ACK 包
  • 如果 server 多次重发 SYN+ACK 都等不到 client 的 ACK,就会发送 RST 包,强制关闭连接

TCP释放连接

4次挥手

  • fin-wait-1:表示想主动关闭连接
    • 向对方发送 FIN 报文,此时进入 FIN-WAIT-1 状态
  • close-wait:表示在等待关闭
    • 当对方发送 FIN 给自己,自己会回应一个 ACK 报文给对方,此时进入到 CLOSE-WAIT 状态
    • 在此状态下,需要考虑自己是否还有数据要发送给对方,如果没有,发送 FIN 报文给对方
  • fin-wait-2:只要对方发送 ACK 确认后,主动发就会处于 FIN-WAIT-2 状态,然后等待对方发送 FIN 报文
  • closing:一种比较罕见的例外状态
    • 表示你发送 FIN 报文,并没有收到对方的 ACK 报文,反而却收到了对方的 FIN 报文
    • 如果双方几乎在同时准备关闭连接的话,那么就会出现双方同时发送 FIN 报文的情况,也会出现 CLOSING 状态
    • 表示双方都正在关闭连接
  • last-ack:被动关闭一方在发送 FIN 报文后,最后等待对方的 ACK 报文
    • 当收到 ACK 报文后,即可进入 CLOSED 状态
  • time-wait:表示收到了对方的 FIN 报文,并发送了 ACK 报文,就等 2msl 后即可进入 CLOSED 状态了
    • 如果 FIN-WAIT-1 状态,收到了对方同时带 FIN 标志和 ACK 标志的报文是
      • 可以直接进入到 TIME-WAIT 状态,而无需经过 FIN-WAIT-2 状态
  • closed:关闭状态

由于有些状态的时间比较短暂,所以很难用 netstat 命令看到,比如:SYN-RCVD、FIN-WAIT-1 等

image-20230628163721047

image-20230628172328384

TCP/IP 协议栈在设计上,允许任何一方先发起断开请求

  • client 发送 ACK 后,需要有个 TIME-WAIT 阶段,等待一段时间,再真正关闭连接

  • 一般等待 2 倍的 MSL(Maximum Segment Lifetime,最大分段生存期)

    • MSL 是 TCP 报文在 Internet 上的最长生存时间
    • 每个具体的 TCP 实现都必须选择一个确定的 MSL 值,RFC 1122 建议是 2 分钟
    • 可以防止本次连接中产生的数据误传到下一次连接中(因为本次连接中的数据包都会在 2MSL 时间内消失了)
  • 如果 client 发送 ACK 后马上释放了,然后又因为网络原因,server 没有收到 client 的 ACK,server 就会重发 FIN

    这时可能出现的情况是:

    • client 没有任何响应,服务器那边会干等,甚至多次重发 FIN,浪费资源
    • client 有个新的应用程序刚好分配了同一个端口号,新的应用程序收到 FIN 后马上执行断开连接的操作,本来它可能是想跟 server 建立连接的

为什么要4次挥手

为什么释放连接的时候,要进行 "4次挥手"?

TCP 是全双工模式

  • 第1次挥手:当主机1发出 FIN 报文段时
    • 表示主机1告诉主机2,主机1已经没有数据要发送了,但是,此时主机1还是可以接收主机2的数据
  • 第2次挥手:当主机2返回 ACK 报文时
    • 表示主机2已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的
  • 第3次挥手:当主机2也发送了 FIN 报文段时
    • 表示主机2告诉主机1,主机2英没有数据要发送了
  • 第4次挥手:当主机1返回 ACK 报文段时
    • 主机1已经知道主机2没有数据发送了。随后正式断开整个 TCP 连接

image-20230628180243292

常备不懈,才能有备无患