# 电信号传输 TCP/IP 数据

# 协议栈

  1. TCP、UDP、ICMP
  2. IP、ARP

一般来说,浏览器、邮件等应用程序使用 TCP 协议;DNS 查询这种收发较短的控制数据的时候则使用 UDP。

  • ICMP 用于告知网络包传送过程中产生的错误和各种控制信息
  • ARP 用于根据 IP 地址查询 MAC 地址

# 套接字的实体

套接字是抽象概念,那它的实体代表什么呢?

在协议栈内部,有一块存放控制信息的内存空间,记录了用于通信操作的控制信息。例如:对象的 IP 地址、端口号、通信操作的进行状态等。

# 查看套接字信息

Windows 下:

netstat -ano
Proto   Local Address   Foreign Address  State       PID
TCP     0.0.0.0:135     0.0.0.0:0        LISTENING   984
TCP     10.10.1.16:1031 10.10.1.80:139   ESTABLISHED 4

使用协议、本地 IP 和端口、对方的 IP 和端口、通信状态、进程编号

LISTENING 表明是在等待被连接,因此 Foreign Address 还不知道。本地的 IP 也还不知道。

ESTABLISHED 表明已经建立连接,可以进行通信了。

# 连接的意义

将本身的 IP 和端口告诉对方,这样才相互知道双方的 IP,才能建立通道,进行通信。

并不像网线的连接概念;网线是一直连接的,并没有断开。

# 负责保存控制信息的头部

每次通信都需要这些控制信息,因此进行通信时,这些控制信息都放在网络包的开头。

TCP 头部结构:

  1. 发送方端口号(2 字节)、接收方端口号(2 字节)
  2. 序号(4 字节)
  3. ACK(4 字节)
  4. 数据偏移量(4 位)、保留(6 位)、控制位(6 位)、窗口(2 字节)
  5. 校验和(2 字节)、紧急指针(2 字节)
  6. 可选字段(可变长度,可有可无)

解释:

  1. 序号:发送数据的顺序编号
  2. ACK:接收数据的顺序编号
  3. 数据偏移量:数据部分的起始位置,因为头部占用一定空间,也就是头部的长度。
  4. 控制位:URG、ACK、PSH、RST、SYN、FIN
  5. 窗口:接收方告知发送方窗口大小
  6. 紧急指针:应急处理的数据位置
  7. 可选字段,一般连接阶段使用,平时很少使用

除了可选字段,前面已经占用 20 字节;而且头部最多可用 60 字节;因此:可选字段,最多只有 40 字节,而且是 4 的整数倍。

# 收发数据

数据不会立即被发送出去的,有一个缓冲区。根据不同策略发送。

立即发送可能会导致出现很多小的网络包,效率不高。

  1. 立即发送,由应用层控制。
  2. 缓冲区长度达到 MSS 或接近,才发送。
  3. 计时器,达到一定时间后,发出去。

浏览器这种会话类型的应用,需要立即发送,这样交互时间才降低。

如果交互频率很低,缓冲区导致延时时间发送,因此需要计时器,到达时间,即使缓冲区没满,也需要发出了。

# MTU、MSS

MTU:最大传输单元(Maximum Transmission Unit),一个网络包允许的最大长度,以太网中是 1500 字节。

MSS:最大分段大小(Maximum Segment Siz),去掉头部,剩余真正的数据的长度。

# 拆分

如果传输的数据很长,就需要按 MSS 进行拆分。每个分段,都加上 TCP 头部。

# ACK 确认网络包收到

网络包组装好,就开始发送了,发送之后,还没有结束,还有一个确认的过程。

确认原理
拆分的数据,会有一个序号。好处

  1. 数据不会乱,有序,接收方能知道是否遗漏。
  2. 能确认每个分段都收到。

接收方收到一个网络包后,就将此序号,写入 TCP,以及 ACK 确认,然后发送给发送方。

序号不是从 1 开始的,而是连接的时候,初始化阶段,是随机的一个值,从这个值开始累计。

这样可以防止通信过程可预测,防止被攻击。

# 连接阶段

因为是双向通信的,因此双方都有初始的序号,需要通知对方。

连接阶段:

  1. 发送方:发送,【初始的序号 x】、SYN
  2. 接收方:发送,ACK、【ACK:x+1】、SYN、【初始的序号 y】
  3. 发送方:发送,ACK、【ACK:y+1】

这样,就能知道双方的初始的序号了。

  • SYN + 初始的序号
  • ACK + 确认序号

# 动态调整 ACK 号的等待时间

当网络传输繁忙时就会发生拥堵,ACK 号返回就变慢。这时候就要延长等待 ACK 的时间了。否则会认为丢失了该网络包而重新传送。

本来网络传输已经繁忙了,这时候出现不必要的重传,那加重网络的拥堵。

ACK 号的返回,时间可能随机的,因此不能固定一个等待时间,需要动态的调整。

监测 ACK 号的返回时间,如果返回变慢了,则相应延长等待时间;相反地,如果返回时间变快了,则相应缩短等待时间。

# 使用窗口有效管理 ACK 号

每发送一个包就等待一个 ACK 号的方式是最简单的,但在等待这段时间什么也做不了,就很浪费了。

因此引入滑动窗口。

滑动窗口:发送一个包后,不等待 ACK 号返回,而是直接发送后续一系列包。这样就可以有效利用这段等待时间了。

但不断发送一系列包,可能导致接收方处理不过来。

  1. 首先,接收方要告诉发送方,自己最多能接收多少数据。
  2. 发送方根据这个值,对数据进行发送操作。
  3. 发送方发完了这个值的数量,就等待发送。
  4. 接收方接收后,就确认 ACK 的时候,告知对方,自己目前新的、可用的大小。
  5. 发送方接收到 ACK 后,得到对方的窗口大小,就可以继续根据窗口大小发送网络包。

TCP 调优的一个参数:窗口大小。

# ACK 和窗口的合并

如果每次 ACK 都发送,以及窗口需要更新,也独立发送网络包,这样就增加的网络包的数量。

因此,接收方,并不马上发送 ACK 包,而是等待一段时间发送 ACK 包,可以连续处理多个包,然后发送最后一个 ACK 的包;因此可以减少网络包的数量。

以及,窗口需要更新时,也可以在 ACK 包中携带,因此又可以减少因窗口更新而导致的网络包增加。

这就是 ACK 和窗口更新的控制数据的合并。

# 断开连接

数据发送完毕后,就可以断开连接了。

协议栈允许任何一方先发起断开过程,因此断开顺序取决于应用本身。

Web 服务器返回响应信息后,服务器先发起断开。

  1. 服务器发送:FIN
  2. 客户端发送:ACK
  3. 客户端发送:FIN
  4. 服务器发送:ACK

这里为什么客户端发送 ACK 和 FIN 分开呢?

因为服务器需要关闭,因此通知客户端;而客户端可能并不打算立即就断开本身,因此先发 ACK 确认自己收到对方的断开操作。然后根据本身再发送 FIN 通知对方,等待对方的确认。

客户端可能这时候还没处理缓冲区的数据,因此不会在 ACK 的同时,立即断开,等待处理完成缓冲区的数据后,再发送 FIN 断开。

# 删除套接字

套接字要在双方都发送 FIN 和确认 ACK 后,才删除套接字;这样可以防止误操作。

如果对方提早删除套接字,则可能对方没收到 FIN 包,因此可能会重复发送 FIN 包。

# 总结

连接阶段:

  1. 发送方发送:SYN、【初始的序号 x】、【窗口大小】
  2. 接收方发送:【ACK:x+1】、【窗口大小】、SYN、【初始的序号 y】
  3. 发送方发送:【ACK:y+1】

通信阶段:

  1. 发送方:【序号】【数据】
  2. 接收方:【ACK 号】【窗口】

断开阶段:

  1. 发送方:FIN
  2. 接收方:【ACK 号】
  3. 接收方:FIN
  4. 发送方:【ACK 号】
  5. 双方删除套接字

TCP 模块需要底层的 IP 模块

IP 模块负责添加如下两个头部:

  • MAC 头部:以太网的头部,包含 MAC 地址
  • IP 头部:IP 用的头部,包含 IP 地址。

# IP 模块

无论要收发的包是控制包,还是数据包,IP 对各种类型的包的收发操作都是相同的。

# IP 头部

  1. 版本号(4b)、头部长度(4b)、服务类型(1B)、总长度(2B)
  2. ID 号(2B)、标志(3b)、分片偏移量(13b)
  3. 生存时间(1B)、协议号(1B)、头部校验和(2B)
  4. 发送方 IP 地址(4B)
  5. 接收方 IP 地址(4B)
  6. 可选字段:可变长度

解释:

  1. 版本号:4,IPv4
  2. 服务类型:包传输优先级,该字段比较模糊,DiffServ 规格重新定义
  3. 总长度:IP 消息总长度
  4. ID 号:包的序列号,如果分片,则所有分片都有相同的 ID
  5. 标志:仅 2 比特有效,即是否允许分片,以及当前是否为分片包
  6. 分片偏移量:当前数据是第几个字节开始的
  7. 生存时间:每经过一个路由器就减 1 ,直到 0 丢弃
  8. 协议号:0x06 - TCP;0x11 - UDP;0x01 - ICMP
  9. 校验和:现在不用了

# 网卡的选择

接收方 IP 地址直接是对方的 IP 地址,但发送方如果有多个网卡,有多个 IP 地址,那应该选哪个呢?

根据系统配置的路由表,匹配是否有相同子网下的 IP,因此使用此 IP,如果没有则使用默认的路由表

# 添加 MAC 头部

  1. 接收方 MAC 地址(6 字节)
  2. 发送方 MAC 地址(6 字节)
  3. 以太类型(2 字节)

类型:

  1. 0000-05DC:IEEE 802.3
  2. 0800:IP 协议
  3. 0806:ARP 协议
  4. 86DD:IPv6

一般使用 0800 和 0806

发送方,通过 IP 就可以知道是哪快网卡,因此可以直接知道 MAC 地址;

而接收方,知道 IP 地址,但还未知对方的 MAC 地址;

因此需要通过对方 IP 地址,查询对方的 MAC 地址。

# ARP 查询目标路由器的 MAC 地址

ARP 利用广播,询问目标主机的 MAC 地址。

查询到的目标主机的 MAC 地址,会缓存起来;下次查询时,可以直接获取。

ARP 缓存删除比较简单粗暴,一般经过几分钟,所有缓存都清空。

# 为什么以太网的包,需要 IP 模块来组装?

以太网的结构,应该由以太网来封装,但为什么由上层来封装呢?

因为由上层来封装,则可以实现各种类型的协议,IP 协议、ARP 协议等等;这样就不需要以太网层来考虑类型了,直接发送网络包即可。

# 以太网基础知识

# 以太网结构的演变

  1. 10BASE5(以太网原型)
    • 信号通过网线流过整个网络
  2. 10BASE-T
    • 信号通过中继式集线器扩散到整个网络。使用双绞线,以及使用中继式集线器替换骨干网线。因此,结构改变,但性质不变。
  3. 交换式集线器
    • 交换式集线器会根据接收方的 MAC 地址,将包发送到指定的目的地。因此,信号只会到达指定设备。
    • 交换式集线器中继式集线器结构一样,但信号发送方式不同。

以上三种的变迁,但以太网封装是不变的。

# IP 包转换成电 / 光信号传输

# 网卡

不同厂商的网卡,需要不同的驱动,这集成于操作系统中了,因此可以直接使用。

网卡结构
包含:MAC、缓冲区、ROM、PHY(MAU)、RJ-45 接口等

ROM 储存全球唯一的 MAC 地址,代表此网卡的地址,厂商生产时,烧录进去的。

网卡中的 MAC 地址由驱动读取,然后分配给 MAC 模块

# 网卡添加控制数据

MAC 模块从网卡缓冲区中取出帧,然后头部添加报头和起始帧分界符,尾部添加帧校验序列(FCS),用于错误校验。

头部添加的是 1 和 0 的交替,56 bit。

这用于确定读取的时机:1 和 0 的交替,可以测量 1 到 0 这段时间,作为读取数据的间隔。

起始帧分隔符是以 11 结尾的。

  1. 报头(56 bit,1 和 0 交替)
  2. 起始帧分隔符(8 bit,结尾是 11):1010 1011

起始帧分隔符(SFD)用于测量帧的起始位置

电信号通过电压高低来确定 1 或 0,但如果是连续的电压,变化不大,那么从哪里划分数据呢?

因此需要时钟信号

合并数据信号和时钟信号,合并的信号,容易读取数据。

合并规则:

  1. 高 + 高 = 低
  2. 低 + 低 = 低
  3. 高 + 低 = 高
  4. 低 + 高 = 高

如果单独发送数据信号和时钟信号,网线较长时,导致传输数据信号和时钟信号产生时间差,时钟就会发生偏移。

因此数据信号需要叠加时钟信号,然后发送叠加的信号。

因此,如果能知道发送方的时钟信号的周期,就能读取叠加信号。

通过统计一段时间,可以知道发送方时钟信号的周期,因此,报头的作用就是提供给接收方统计用的,统计出发送方的时钟周期,进而读取叠加信号的数据。

末尾的 FCS 也是用于检验因噪声导致信号紊乱导致的数据错误。

# 向集线器发送信号

集线器是半双工的;交换机是全双工的。

集线器可能会发生信号冲突,因此发送前,需要检测网线是否有其它设备发送。

MAC 模块将数据转成通用的电信号,然后由 PHY 或 MAU 模块发送出去。

转换成电信号的速率,即为传输速率。

PHY 会将电信号转成不同网线规格的信号。网线传输情况很复杂。有很多编码,这些不同编码导致发送的速率不同。

举例:4B/5B、曼彻斯特编码...

PHY 还要检测线路是否已经有其它设备发送信号了。

如果发生冲突,则通信终止,然后发送一段时间阻塞,即等待一段时间,然后再发送,如果再有冲突,则延长一倍等待时间,如果重试 10 次都冲突,就报告通信错误。

全双工就不会发生信号冲突,也因此简单很多。

# 接收信号

集线器半双工模式,设备发送的信号,连接同一个集线器的都接收到信号,因此所有设备都接收集线器上的信号。

  1. PHY 模块将解码电信号(取决于网线,以及编码),转成通用的电信号
  2. MAC 模块处理电信号,转成数字信息,然后放到缓冲区。到达 FCS 结尾,还要校验数据是否错误。错误就丢弃。
  3. FCS 校验没问题,就检查头部 MAC 接收地址,是否为本网卡的 MAC 地址,如果不匹配,则不是发给此网卡的,则抛弃。
  4. MAC 地址一致时,才返回到协议层,然后到应用层。

网卡驱动程序也不是一直监控网卡的活动,因此网卡通过中断机制通知网卡驱动程序。

网卡通过扩展总线的中断线路发送中断信号。中断线路连接 CPU,CPU 暂时挂起其它任务,然后切换到操作系统的中断程序。中断程序调用网卡驱动,然后通知到了网卡驱动,驱动程序就开始读取网卡数据。

中断信号都有一个编号的。网卡安装时,就设置了一个中断编号,网卡的中断号和驱动程序中断号绑定起来。

现代设备都是可以通过即插即用的方式,自动设置中断号,就不用手动设置了。

# IP 模块到 TCP 模块

网卡驱动将数据交给协议栈处理。

先由 IP 模块处理,如果接收方 IP 不匹配,则抛弃数据,客户端不做转发功能,以及,返回 ICMP 通知错误。

ICMP 错误消息有很多种类。

ICMP 包含在 IP 报文中

以及增加 ICMP 头部信息

  1. 类型(1 字节)、代码(1 字节)、校验和(2 字节)
  2. 内容

ping 使用 ICMP 协议类型的:8,0

traceroute 使用 ICMP 协议

# UDP 协议的收发

# 不需要重发的 UDP 更高效

TCP 复杂是由于要确保可靠,需要确保对方收到,没收到就要重发。

但有些简单的包,没有分片,就只有一个包,因此,就算对方没收到,也只需重发此包即可。

# UDP 协议头部

像 DNS 这种查询的只需要 1 个包就能解决。

UDP 没有确认机制、窗口等,因此不需要建立、连接;只需要获取对方端口信息,就可以直接发送了。

遇到错误的包,丢包,也不用处理。UDP 只管发送。

  1. 发送方端口(2 字节)、接收方端口(2 字节)
  2. 数据长度(2 字节)、校验和(2 字节)

# UDP 的使用场景

音频和视频

因为音频和视频要求时效性,如果掉帧了,播放就卡掉。但这时候再重传,也挽回不了画面卡掉的情况。因此就放弃了重传。

产生少量的失真和卡顿,也是可以接受的;如果存在大量的这种情况,那即使重传也解决不了问题,而是要提高传输速率。