# 电信号传输 TCP/IP 数据
# 协议栈
- TCP、UDP、ICMP
- 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 头部结构:
- 发送方端口号(2 字节)、接收方端口号(2 字节)
- 序号(4 字节)
- ACK(4 字节)
- 数据偏移量(4 位)、保留(6 位)、控制位(6 位)、窗口(2 字节)
- 校验和(2 字节)、紧急指针(2 字节)
- 可选字段(可变长度,可有可无)
解释:
- 序号:发送数据的顺序编号
- ACK:接收数据的顺序编号
- 数据偏移量:数据部分的起始位置,因为头部占用一定空间,也就是头部的长度。
- 控制位:URG、ACK、PSH、RST、SYN、FIN
- 窗口:接收方告知发送方窗口大小
- 紧急指针:应急处理的数据位置
- 可选字段,一般连接阶段使用,平时很少使用
除了可选字段,前面已经占用 20 字节;而且头部最多可用 60 字节;因此:可选字段,最多只有 40 字节,而且是 4 的整数倍。
# 收发数据
数据不会立即被发送出去的,有一个缓冲区。根据不同策略发送。
立即发送可能会导致出现很多小的网络包,效率不高。
- 立即发送,由应用层控制。
- 缓冲区长度达到 MSS 或接近,才发送。
- 计时器,达到一定时间后,发出去。
浏览器这种会话类型的应用,需要立即发送,这样交互时间才降低。
如果交互频率很低,缓冲区导致延时时间发送,因此需要计时器,到达时间,即使缓冲区没满,也需要发出了。
# MTU、MSS
MTU:最大传输单元(Maximum Transmission Unit),一个网络包允许的最大长度,以太网中是 1500 字节。
MSS:最大分段大小(Maximum Segment Siz),去掉头部,剩余真正的数据的长度。
# 拆分
如果传输的数据很长,就需要按 MSS 进行拆分。每个分段,都加上 TCP 头部。
# ACK 确认网络包收到
网络包组装好,就开始发送了,发送之后,还没有结束,还有一个确认的过程。
确认原理
拆分的数据,会有一个序号。好处
- 数据不会乱,有序,接收方能知道是否遗漏。
- 能确认每个分段都收到。
接收方收到一个网络包后,就将此序号,写入 TCP,以及 ACK 确认,然后发送给发送方。
序号不是从 1 开始的,而是连接的时候,初始化阶段,是随机的一个值,从这个值开始累计。
这样可以防止通信过程可预测,防止被攻击。
# 连接阶段
因为是双向通信的,因此双方都有初始的序号,需要通知对方。
连接阶段:
- 发送方:发送,【初始的序号 x】、SYN
- 接收方:发送,ACK、【ACK:x+1】、SYN、【初始的序号 y】
- 发送方:发送,ACK、【ACK:y+1】
这样,就能知道双方的初始的序号了。
- SYN + 初始的序号
- ACK + 确认序号
# 动态调整 ACK 号的等待时间
当网络传输繁忙时就会发生拥堵,ACK 号返回就变慢。这时候就要延长等待 ACK 的时间了。否则会认为丢失了该网络包而重新传送。
本来网络传输已经繁忙了,这时候出现不必要的重传,那加重网络的拥堵。
ACK 号的返回,时间可能随机的,因此不能固定一个等待时间,需要动态的调整。
监测 ACK 号的返回时间,如果返回变慢了,则相应延长等待时间;相反地,如果返回时间变快了,则相应缩短等待时间。
# 使用窗口有效管理 ACK 号
每发送一个包就等待一个 ACK 号的方式是最简单的,但在等待这段时间什么也做不了,就很浪费了。
因此引入滑动窗口。
滑动窗口:发送一个包后,不等待 ACK 号返回,而是直接发送后续一系列包。这样就可以有效利用这段等待时间了。
但不断发送一系列包,可能导致接收方处理不过来。
- 首先,接收方要告诉发送方,自己最多能接收多少数据。
- 发送方根据这个值,对数据进行发送操作。
- 发送方发完了这个值的数量,就等待发送。
- 接收方接收后,就确认 ACK 的时候,告知对方,自己目前新的、可用的大小。
- 发送方接收到 ACK 后,得到对方的窗口大小,就可以继续根据窗口大小发送网络包。
TCP 调优的一个参数:窗口大小。
# ACK 和窗口的合并
如果每次 ACK 都发送,以及窗口需要更新,也独立发送网络包,这样就增加的网络包的数量。
因此,接收方,并不马上发送 ACK 包,而是等待一段时间发送 ACK 包,可以连续处理多个包,然后发送最后一个 ACK 的包;因此可以减少网络包的数量。
以及,窗口需要更新时,也可以在 ACK 包中携带,因此又可以减少因窗口更新而导致的网络包增加。
这就是 ACK 和窗口更新的控制数据的合并。
# 断开连接
数据发送完毕后,就可以断开连接了。
协议栈允许任何一方先发起断开过程,因此断开顺序取决于应用本身。
Web 服务器返回响应信息后,服务器先发起断开。
- 服务器发送:FIN
- 客户端发送:ACK
- 客户端发送:FIN
- 服务器发送:ACK
这里为什么客户端发送 ACK 和 FIN 分开呢?
因为服务器需要关闭,因此通知客户端;而客户端可能并不打算立即就断开本身,因此先发 ACK 确认自己收到对方的断开操作。然后根据本身再发送 FIN 通知对方,等待对方的确认。
客户端可能这时候还没处理缓冲区的数据,因此不会在 ACK 的同时,立即断开,等待处理完成缓冲区的数据后,再发送 FIN 断开。
# 删除套接字
套接字要在双方都发送 FIN 和确认 ACK 后,才删除套接字;这样可以防止误操作。
如果对方提早删除套接字,则可能对方没收到 FIN 包,因此可能会重复发送 FIN 包。
# 总结
连接阶段:
- 发送方发送:SYN、【初始的序号 x】、【窗口大小】
- 接收方发送:【ACK:x+1】、【窗口大小】、SYN、【初始的序号 y】
- 发送方发送:【ACK:y+1】
通信阶段:
- 发送方:【序号】【数据】
- 接收方:【ACK 号】【窗口】
断开阶段:
- 发送方:FIN
- 接收方:【ACK 号】
- 接收方:FIN
- 发送方:【ACK 号】
- 双方删除套接字
TCP 模块需要底层的 IP 模块
IP 模块负责添加如下两个头部:
- MAC 头部:以太网的头部,包含 MAC 地址
- IP 头部:IP 用的头部,包含 IP 地址。
# IP 模块
无论要收发的包是控制包,还是数据包,IP 对各种类型的包的收发操作都是相同的。
# IP 头部
- 版本号(4b)、头部长度(4b)、服务类型(1B)、总长度(2B)
- ID 号(2B)、标志(3b)、分片偏移量(13b)
- 生存时间(1B)、协议号(1B)、头部校验和(2B)
- 发送方 IP 地址(4B)
- 接收方 IP 地址(4B)
- 可选字段:可变长度
解释:
- 版本号:4,IPv4
- 服务类型:包传输优先级,该字段比较模糊,DiffServ 规格重新定义
- 总长度:IP 消息总长度
- ID 号:包的序列号,如果分片,则所有分片都有相同的 ID
- 标志:仅 2 比特有效,即是否允许分片,以及当前是否为分片包
- 分片偏移量:当前数据是第几个字节开始的
- 生存时间:每经过一个路由器就减 1 ,直到 0 丢弃
- 协议号:0x06 - TCP;0x11 - UDP;0x01 - ICMP
- 校验和:现在不用了
# 网卡的选择
接收方 IP 地址直接是对方的 IP 地址,但发送方如果有多个网卡,有多个 IP 地址,那应该选哪个呢?
根据系统配置的路由表,匹配是否有相同子网下的 IP,因此使用此 IP,如果没有则使用默认的路由表
# 添加 MAC 头部
- 接收方 MAC 地址(6 字节)
- 发送方 MAC 地址(6 字节)
- 以太类型(2 字节)
类型:
- 0000-05DC:IEEE 802.3
- 0800:IP 协议
- 0806:ARP 协议
- 86DD:IPv6
一般使用 0800 和 0806
发送方,通过 IP 就可以知道是哪快网卡,因此可以直接知道 MAC 地址;
而接收方,知道 IP 地址,但还未知对方的 MAC 地址;
因此需要通过对方 IP 地址,查询对方的 MAC 地址。
# ARP 查询目标路由器的 MAC 地址
ARP 利用广播,询问目标主机的 MAC 地址。
查询到的目标主机的 MAC 地址,会缓存起来;下次查询时,可以直接获取。
ARP 缓存删除比较简单粗暴,一般经过几分钟,所有缓存都清空。
# 为什么以太网的包,需要 IP 模块来组装?
以太网的结构,应该由以太网来封装,但为什么由上层来封装呢?
因为由上层来封装,则可以实现各种类型的协议,IP 协议、ARP 协议等等;这样就不需要以太网层来考虑类型了,直接发送网络包即可。
# 以太网基础知识
# 以太网结构的演变
- 10BASE5(以太网原型)
- 信号通过网线流过整个网络
- 10BASE-T
- 信号通过中继式集线器扩散到整个网络。使用双绞线,以及使用中继式集线器替换骨干网线。因此,结构改变,但性质不变。
- 交换式集线器
- 交换式集线器会根据接收方的 MAC 地址,将包发送到指定的目的地。因此,信号只会到达指定设备。
- 交换式集线器和中继式集线器结构一样,但信号发送方式不同。
以上三种的变迁,但以太网封装是不变的。
# IP 包转换成电 / 光信号传输
# 网卡
不同厂商的网卡,需要不同的驱动,这集成于操作系统中了,因此可以直接使用。
网卡结构
包含:MAC、缓冲区、ROM、PHY(MAU)、RJ-45 接口等
ROM 储存全球唯一的 MAC 地址,代表此网卡的地址,厂商生产时,烧录进去的。
网卡中的 MAC 地址由驱动读取,然后分配给 MAC 模块
# 网卡添加控制数据
MAC 模块从网卡缓冲区中取出帧,然后头部添加报头和起始帧分界符,尾部添加帧校验序列(FCS),用于错误校验。
头部添加的是 1 和 0 的交替,56 bit。
这用于确定读取的时机:1 和 0 的交替,可以测量 1 到 0 这段时间,作为读取数据的间隔。
起始帧分隔符是以 11 结尾的。
- 报头(56 bit,1 和 0 交替)
- 起始帧分隔符(8 bit,结尾是 11):1010 1011
起始帧分隔符(SFD)用于测量帧的起始位置
电信号通过电压高低来确定 1 或 0,但如果是连续的电压,变化不大,那么从哪里划分数据呢?
因此需要时钟信号。
合并数据信号和时钟信号,合并的信号,容易读取数据。
合并规则:
- 高 + 高 = 低
- 低 + 低 = 低
- 高 + 低 = 高
- 低 + 高 = 高
如果单独发送数据信号和时钟信号,网线较长时,导致传输数据信号和时钟信号产生时间差,时钟就会发生偏移。
因此数据信号需要叠加时钟信号,然后发送叠加的信号。
因此,如果能知道发送方的时钟信号的周期,就能读取叠加信号。
通过统计一段时间,可以知道发送方时钟信号的周期,因此,报头的作用就是提供给接收方统计用的,统计出发送方的时钟周期,进而读取叠加信号的数据。
末尾的 FCS 也是用于检验因噪声导致信号紊乱导致的数据错误。
# 向集线器发送信号
集线器是半双工的;交换机是全双工的。
集线器可能会发生信号冲突,因此发送前,需要检测网线是否有其它设备发送。
MAC 模块将数据转成通用的电信号,然后由 PHY 或 MAU 模块发送出去。
转换成电信号的速率,即为传输速率。
PHY 会将电信号转成不同网线规格的信号。网线传输情况很复杂。有很多编码,这些不同编码导致发送的速率不同。
举例:4B/5B、曼彻斯特编码...
PHY 还要检测线路是否已经有其它设备发送信号了。
如果发生冲突,则通信终止,然后发送一段时间阻塞,即等待一段时间,然后再发送,如果再有冲突,则延长一倍等待时间,如果重试 10 次都冲突,就报告通信错误。
全双工就不会发生信号冲突,也因此简单很多。
# 接收信号
集线器半双工模式,设备发送的信号,连接同一个集线器的都接收到信号,因此所有设备都接收集线器上的信号。
- PHY 模块将解码电信号(取决于网线,以及编码),转成通用的电信号
- MAC 模块处理电信号,转成数字信息,然后放到缓冲区。到达 FCS 结尾,还要校验数据是否错误。错误就丢弃。
- FCS 校验没问题,就检查头部 MAC 接收地址,是否为本网卡的 MAC 地址,如果不匹配,则不是发给此网卡的,则抛弃。
- MAC 地址一致时,才返回到协议层,然后到应用层。
网卡驱动程序也不是一直监控网卡的活动,因此网卡通过中断机制通知网卡驱动程序。
网卡通过扩展总线的中断线路发送中断信号。中断线路连接 CPU,CPU 暂时挂起其它任务,然后切换到操作系统的中断程序。中断程序调用网卡驱动,然后通知到了网卡驱动,驱动程序就开始读取网卡数据。
中断信号都有一个编号的。网卡安装时,就设置了一个中断编号,网卡的中断号和驱动程序中断号绑定起来。
现代设备都是可以通过即插即用的方式,自动设置中断号,就不用手动设置了。
# IP 模块到 TCP 模块
网卡驱动将数据交给协议栈处理。
先由 IP 模块处理,如果接收方 IP 不匹配,则抛弃数据,客户端不做转发功能,以及,返回 ICMP 通知错误。
ICMP 错误消息有很多种类。
ICMP 包含在 IP 报文中
以及增加 ICMP 头部信息
- 类型(1 字节)、代码(1 字节)、校验和(2 字节)
- 内容
ping 使用 ICMP 协议类型的:8,0
traceroute 使用 ICMP 协议
# UDP 协议的收发
# 不需要重发的 UDP 更高效
TCP 复杂是由于要确保可靠,需要确保对方收到,没收到就要重发。
但有些简单的包,没有分片,就只有一个包,因此,就算对方没收到,也只需重发此包即可。
# UDP 协议头部
像 DNS 这种查询的只需要 1 个包就能解决。
UDP 没有确认机制、窗口等,因此不需要建立、连接;只需要获取对方端口信息,就可以直接发送了。
遇到错误的包,丢包,也不用处理。UDP 只管发送。
- 发送方端口(2 字节)、接收方端口(2 字节)
- 数据长度(2 字节)、校验和(2 字节)
# UDP 的使用场景
音频和视频
因为音频和视频要求时效性,如果掉帧了,播放就卡掉。但这时候再重传,也挽回不了画面卡掉的情况。因此就放弃了重传。
产生少量的失真和卡顿,也是可以接受的;如果存在大量的这种情况,那即使重传也解决不了问题,而是要提高传输速率。