Skip to content

Network 网络

在操作系统、内核、网卡(NIC)和用户态之间,最大化吞吐、最小化延迟 的所有工程技术手段。

TCP/IP网络五层模型

物理层

数据单位:比特(bit)

数据链路层

MAC地址:是否是 48 位(6 字节)十六进制:常见写法 00:1A:2B:3C:4D:5E00-1A-2B-3C-4D-5E001A.2B3C.4D5E

常见协议:

Ethernet(以太网)、Wi-Fi(802.11)

ARP(把 IP 地址解析成 MAC 地址,常被视为链路层/网络层之间的“边界协议”)

VLAN(802.1Q)

数据单位:帧(frame)

网络层

IPv4:32 位 192.168.1.10

IPv4 私网(RFC1918):10.0.0.0/8172.16.0.0/12192.168.0.0/16

IPv6:128 位,2001:0db8:0000:0000:0000:0000:0000:0001

私网: fe80::/10

常见协议/技术:

IP(IPv4/IPv6)

ICMP(ping、网络不可达等)它是 网络层(IP 的控制/差错报告协议),专门用来回报网络传输过程中的问题

路由协议:OSPF、BGP 等(用于路由器之间交换路由信息)

数据单位:包/分组(packet)

IPX / SPX 是 Novell(网景时代的网络操作系统厂商)在 NetWare 上广泛使用的一套网络协议族

你可以把它类比成

  • IPX ≈ IP(负责把包送到目标网络/主机)
  • SPX ≈ TCP(负责可靠传输、按序到达)IPX/SPX 现在基本已被淘汰

传输层

TCP:面向连接、可靠、有序、流量控制与拥塞控制(适合网页、文件、邮件等)

UDP:无连接、尽力而为、开销小、时延低(适合语音、视频、游戏、DNS 等) 数据单位:段(TCP segment)/ 数据报(UDP datagram)

应用层

常见协议:

  • HTTP/HTTPS(网页与 API)
  • DNS(域名解析)

数据单位:消息/报文(message)

局域网

UPnP

一套让局域网设备“自动发现并配置网络服务”的机制。游戏机、下载软件、视频会议等程序可以请求路由器自动开放端口,让外网能主动连进来

风险:

  • 局域网里任何设备/程序都可能请求开端口

WAN

路由器“管理后台”(Web/SSH/Telnet 等)允许从 外网(WAN)访问时,对外暴露的端口。

举例:

  • 你在外面用手机 4G 打开家里路由器管理页面:这就属于 WAN 远程管理。

MAC 地址绑定

“IP–MAC 绑定”:

把某台设备的 MAC 地址固定对应一个 IP 地址(或 DHCP 分配时永远给它同一个 IP),也常用于 ARP 防护/防 IP 冲突

“MAC 绑定上网”

只有列表里的 MAC 才能上网/接入 Wi-Fi(白名单),或禁止某些 MAC(黑名单

内部网关协议

RIP:距离矢量协议,度量通常是“跳数”,实现简单,但收敛慢、规模受限(最大 15 跳)。

OSPF:链路状态协议,按区域(Area)分层,收敛快、适合中大型企业/园区网络。

IS-IS:链路状态协议,运营商网络里也很常见,扩展性强。

EIGRP:思科私有起家(后来有开放部分实现),混合特性,收敛快,企业网里见得多(

MAC地址

MAC 地址也被称为物理地址硬件地址以太网地址。它通常由网络设备的制造商在生产时烧录到设备的网络接口卡(NIC)中(如网卡)。

MAC 地址是一个 48 位(6 字节)的二进制数字。

MAC 地址的作用

地址在局域网(LAN)内部的通信中发挥着至关重要的作用

  • 数据被封装成数据帧进行传输。每个数据帧都包含一个源 MAC 地址目标 MAC 地址。交换机根据这些 MAC 地址来决定将数据帧转发到哪个端口。
  • ARP:MAC 地址与 IP 地址共同协作。当一台主机知道目标设备的 IP 地址,但不知道其 MAC 地址时,它会使用 ARP 协议广播请求,通过 IP 地址获取对应的 MAC 地址。

ARP协议

在局域网(同一网段)内,将一个已知的 IP 地址解析成对应的 MAC 地址(物理地址)。

网络层:IP

IP地址

A 类(Class A)

  • 范围1.0.0.0126.255.255.255(首字节 1–126)
  • 默认子网掩码255.0.0.0(/8)
  • 用途:超大网络

B 类(Class B)

  • 范围128.0.0.0191.255.255.255(首字节 128–191)
  • 默认子网掩码255.255.0.0(/16)
  • 用途:中等规模网络

C 类(Class C)

  • 范围192.0.0.0223.255.255.255(首字节 192–223)
  • 默认子网掩码255.255.255.0(/24)
  • 用途:小网络(常见局域网)

D 类(Class D)

  • 范围224.0.0.0239.255.255.255224.0.0.0/4
  • 用途多播/组播(Multicast) 不是给主机分配的“单播地址”,而是“组”的地址。

E 类(Class E)

  • 范围240.0.0.0255.255.255.255
  • 用途:早期定义为实验/保留(基本不用于普通主机分配) 注:255.255.255.255 是广播地址(全网广播)

子网掩码与子网划分

作用: 子网掩码(Subnet Mask)用于将 IP 地址分为网络部分(Network ID)和主机部分(Host ID)

结论: 经过按位与运算后得到的 192.168.10.0 就是这个 IP 地址的网络 ID

判断通信: 当一台主机要与另一个 IP 地址通信时,它会先计算目标 IP 地址的网络 ID。

  • 如果两个网络 ID 相同,则主机知道目标在同一本地子网,可以直接发送数据(使用 MAC 地址)。
  • 如果两个网络 ID 不同,则主机知道目标在远程网络,必须将数据发送给默认网关(路由器)进行转发。

传输层

TCP

三次握手

这是 TCP 建立可靠连接的过程,确保双方都具备发送和接收数据的能力。

  1. 第一次握手(SYN): 客户端发送一个 SYN=x(同步序列号)报文段到服务器,并进入 SYN-SENT 状态。
  2. 第二次握手(SYN + ACK): 服务器接收到 SYN 后,发送 SYN=y , ACK=x+1(确认)报文段。它确认收到了客户端的请求,并发送自己的序列号。服务器进入 SYN-RECEIVED 状态。
  3. 第三次握手(ACK): 客户端收到服务器的确认后,再发送一个 SYN=x+1,ACK=y+1 报文段进行确认。客户端进入 ESTABLISHED 状态。服务器收到 ACK 后,也进入 ESTABLISHED 状态。

四次挥手

四次挥手的关键在于 TCP 是全双工(Full-Duplex)的,这意味着数据在两个方向上是独立传输的。因此,当一方完成发送数据并希望关闭连接时,另一方可能还有未发送完的数据,需要等待其发送完毕才能关闭。这就导致了关闭连接需要四个步骤。

第一次挥手(FIN): 客户端发送一个 FIN(结束)SYN=u,ACK=v报文段,表明它已发送完所有数据,准备关闭客户端到服务器的数据通道。客户端进入 FIN-WAIT-1 状态。

第二次挥手(ACK): 服务器收到 FIN 后,发送SYN=v, ACK=u+1 确认报文段。此时服务器仍可能有未发送完的数据,因此连接处于半关闭状态。服务器进入 CLOSE-WAIT 状态。

第三次挥手(FIN): 当服务器也发送完所有数据后,发送自己的 FIN ,SYN=w,ACK=u+1报文段,表明它也准备关闭服务器到客户端的数据通道。服务器进入 LAST-ACK 状态。

第四次挥手(ACK): 客户端收到服务器的 FIN 后,发送SYN=u+1,ACK=w+1, 确认报文段。客户端进入 TIME-WAIT 状态,等待一段固定的时间(通常是 2MSL),以确保服务器收到了最后的 ACK。服务器收到 ACK 后,立即进入 CLOSED 状态。

为什么客户端最后要 TIME_WAIT

保证对方收到了最后的 ACK 如果第 4 次 ACK 丢了,服务端会重发第 3 次 FIN;客户端在 TIME_WAIT 还能再回 ACK。

让旧连接的“延迟报文”在网络里自然消失 避免旧连接的报文跑到新连接里造成干扰

TCP 如何保证可靠性

TCP 采用多种机制共同确保数据的可靠传输:

机制 作用 核心原理
序列号 (Sequence Number) 用于对 TCP 传输的数据报文进行编号,确保接收方可以按序组装数据,并识别重复数据。 每个发送的字节都被分配一个序列号。
确认应答 (ACK) 接收方收到数据后,发送 ACK 报文确认。ACK 字段的值表示它期望收到的下一个字节的序列号。 接收方通过 ACK 告诉发送方哪些数据已经安全到达。
超时重传 发送方在发送数据后启动定时器,如果在定时器超时时间内没有收到该数据的 ACK,则认为数据丢失,会重新发送 确保丢失的数据能够被恢复。
滑动窗口 (Sliding Window) 用于实现流量控制,定义了接收方当前能够接收的数据量(窗口大小)。 接收方通过 ACK 告知窗口大小,发送方据此调整发送速率,避免溢出。

TCP流量控制

接收端在每个 ACK 里都会带一个字段:窗口大小 rwnd (receiver window),意思是:

“我这边还能再接收多少字节

发送端必须保证:未确认的数据量 ≤ rwnd,否则对端缓冲区会溢出。

TCP拥塞控制

congestion window

区别: 流量控制是点对点(端到端)的,解决接收方处理速度问题;拥塞控制是全局性的,解决整个网络拥塞问题。

四大算法: 慢启动 (Slow Start)、拥塞避免 (Congestion Avoidance)、快速重传 (Fast Retransmit)、快速恢复 (Fast Recovery)。

慢启动 Slow Start

  • 初始 cwnd 很小(历史上常见 1~10 MSS,现代通常更大一些)
  • 每收到一个 ACK,cwnd 增长;效果上 每 RTT 近似翻倍(指数增长)
  • 直到达到 ssthresh(慢启动阈值)或出现丢包

拥塞避免 Congestion Avoidance

  • cwnd 改为 线性增长:大约每 RTT 增加 1 MSS(AIMD 的 “Additive Increase”)

快速重传 Fast Retransmit

  • 收到 3 个重复 ACK(dupACK)通常意味着“中间丢了一个段但后面段到了”
  • 不等 RTO 超时,立刻重传丢失段(比超时更快)
  • 紧接着进入 快速恢复

快速恢复 Fast Recovery

  • 认为网络“有点堵但没完全崩”
  • 一般做法:ssthresh = cwnd/2,cwnd 降到阈值附近,然后继续拥塞避免

超时重传

  • 发送了一个段迟迟收不到确认(ACK),到达重传超时 RTO 就重传。
  • 特点:保守且通用,什么情况下都能用,但一旦超时,说明网络可能更糟,往往伴随更强退让(如 cwnd =1并回到慢启动)。

TCP粘包 (Sticky Packet)

TCP是面向字节流的协议,数据包之间没有边界。发送方把一个大包拆成小包发送的时候,接受方会拼起来。

Nagle算法

TCP 端在发送端做的小包合并策略:当应用产生很多很小的报文(如逐字节写 socket),Nagle 会暂缓把这些小数据立刻发出去,攒成更大的段再发,从而减少报文数量、节省带宽和中间设备开销。

setsockopt(TCP_NODELAY) → 关闭 Nagle,小数据立刻发

UDP

UDP 的所有特性都围绕着一个目标:最小开销,最大速度。

特性 描述 带来的优势
无连接 (Connectionless) UDP 在发送数据前,不需要像 TCP 那样进行三次握手建立连接,也没有四次挥手来断开连接。 速度极快,通信开销小。
不可靠 (Unreliable) UDP 不保证数据一定能到达、不保证顺序、不进行重传。如果数据丢失、重复或乱序,它不会管。 延迟极低,适用于对实时性要求高的场景。
面向数据报 (Datagram-Oriented) UDP 保持了应用程序发送的消息边界。发送方发送了多少个数据报,接收方就会接收到多少个数据报。 接收方可以清楚地知道数据包的边界。
无拥塞控制 UDP 不会监测网络拥塞状况并调整发送速率。 可以持续以高速率发送,即使网络拥塞,也不会主动降速。

QUIC 协议

  • 全称: Quick UDP Internet Connections
  • 核心: 由 Google 开发,用于加速 HTTP/3。
  • 特性: 在 UDP 上实现了加密、可靠传输、流量控制、拥塞控制,并且解决了 TCP 的队头阻塞问题(Head-of-Line Blocking)。

应用层

核心应用协议

这是应用层最常考的内容,需要了解每个协议的作用、工作原理默认端口

协议 作用 传输层协议 默认端口 核心机制
HTTP/HTTPS 超文本传输协议。 用于 Web 浏览器与服务器之间的通信。HTTPS 是 HTTP 的安全版本。 TCP 80 / 443 请求-响应模型、无状态、Cookie/Session
DNS 域名系统。 将人类可读的域名解析成 IP 地址。 UDP/TCP 53 递归查询与迭代查询、DNS 记录类型(A, CNAME, MX)
FTP 文件传输协议。 用于在客户端和服务器之间传输文件。 TCP 20 (数据)/ 21 (控制) 控制连接与数据连接分离、主动模式与被动模式
SMTP 简单邮件传输协议。 用于发送邮件。 TCP 25 邮件的传输(MUA → MSA → MTA)
POP3/IMAP 邮件接收协议。 用于客户端从服务器接收邮件。 TCP 110 / 143 POP3(下载后删除) vs. IMAP(在服务器同步和管理)
DHCP 动态主机配置协议。 自动给设备分配 IP 地址、子网掩码、网关等。 UDP 67 / 68 DHCP DORA 过程(Discover, Offer, Request, Acknowledge)
SSH 安全外壳协议。 提供安全的远程命令行访问。 TCP 22 加密、公钥/私钥认证

HTTP

  • HTTP/1.0 vs. HTTP/1.1: 1.1 引入了持久连接 (Persistent Connection)(一个 TCP 连接可发送多个请求)和 管线化 (Pipelining),大幅提高了效率。
  • HTTP/2: 引入了 多路复用 (Multiplexing),解决了 HTTP/1.1 的队头阻塞问题,提高了并发性能。
  • HTTP/3: 基于 QUIC 协议(运行在 UDP 上),彻底解决了 TCP 带来的队头阻塞问题,并加快了连接建立速度。

I/O 多路复用

定义:单(少量)线程通过一个内核接口同时“盯住”很多 I/O 对象(socket/管道/文件描述符),谁就绪就处理谁

动机:传统“每连接一线程/进程”在高并发时上下文切换/栈内存/锁开销很大;多路复用把并发变“事件驱动”,极大降低开销。

select

位图 + FD_SETSIZE(常 1024)上限;每次调用把位图拷进内核,返回后用户态还要线性扫描。O(n)。

用户态准备好位图(把要监听的 fd bit 置为 1)。

调用 select()

  • 内核将整个位图拷贝到内核态(O(n))。

内核循环扫描所有 bit(从 0 到最大的 fd):

  • 判断对应 fd 是否可读/可写。

内核把扫描结果改写位图(仅保留就绪 fd 的 bit)。

内核将位图拷贝回用户态

用户态再次扫描位图找就绪 fd。

poll

数组无上限了,但仍每次拷贝 & 扫描。O(n)。

用户态准备一个 pollfd 数组。

poll() 调用时把整个数组拷到内核。

内核挨个遍历数组,检查每个 fd 当前是否就绪。

内核写入 revents 字段。

内核把整个数组拷回用户态。

用户态遍历数组找哪些 fd 就绪。

epoll(Linux):

eventpoll 对象:你 epoll_create1() 得到的那个 fd,内核里是一个 eventpoll 实例

  • 兴趣集(interest list):注册过的 fd+关注事件 的集合,通常用 红黑树
  • 就绪队列(ready list):链表/队列,只放“已就绪”的项

当该 fd 的底层(网络协议栈/设备驱动)检测到状态变化(如数据到达缓冲可写错误/挂断)时,会对相应等待队列 wake_up()

wake_up() → 调用之前被 poll_wait() 挂上的回调(就是 epoll 的 ep_poll_callback 一类的函数)。

该回调做两件事:

  1. 把对应的 epitem 标记就绪并(若未在队列中)入就绪队列(去重,避免反复入同一项)。
  2. 唤醒 正在 epoll_wait 上睡眠的线程(把它从等待队列上唤起)。

基础服务器客户端流程

服务端(像开店)

  1. socket():开一个电话机
int fd = socket(AF_INET, SOCK_STREAM, 0); // <sys/socket.h>
if (fd < 0) {
    std::perror("socket");
    return 1;
}
...
close(fd); // <unistd.h>
  1. bind():绑定号码(端口)
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(2333);
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0

if (bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
    perror("bind");
    return 1;
}
  1. listen():开始接听
if (::listen(fd, 16) == -1) { // 16: 排队区最大能容纳多少个“等着被 accept 的连接”。
    perror("listen");
    return 1;
} 
  1. accept():接起某个客户电话(得到一个新的 fd)
int cfd = accept(fd, reinterpret_cast<sockaddr*>(&client_addr), &len);
if (cfd < 0) {
    perror("accept");
    return -1;
}
std::cout << "client connected" << std::endl;

char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)); // networktopresentation
std::cout << "ip: " << ip << ":" << ntohs(client_addr.sin_port) << std::endl; 
  1. recv/send:聊天

  2. close:挂电话 (<unistd.h>)

客户端(像打电话)

  1. socket()
  2. connect():拨号
  3. recv/send
  4. close

socket

socket(domain, type, protocol)

domain(地址族)

  • AF_INET:IPv4
  • AF_INET6:IPv6
  • AF_UNIX:本机进程间通信(Unix domain socket)
  • AF_PACKET:链路层抓包(较底层,不常考后端)

type(套接字类型)

  • SOCK_STREAM:面向连接(TCP)
  • SOCK_DGRAM:无连接(UDP)
  • SOCK_RAW:原始套接字(需要权限)

常见组合:

  • TCP:socket(AF_INET, SOCK_STREAM, 0)
  • UDP:socket(AF_INET, SOCK_DGRAM, 0)

protocol

  • 一般填 0:让系统按 domain+type 自动选(TCP/UDP)

setsockopt

setsockopt(fd, level, optname, &val, sizeof(val))

recv

从 socket 接收数据的系统调用(POSIX / Windows 都有),常用于 TCP/UDP 网络编程。

返回值代表什么

  • > 0:实际收到的字节数(可能小于 len,这是正常现象)
  • == 0:对端正常关闭连接(TCP:收到了 FIN),你应该把连接当作结束
  • < 0:出错(POSIX:errno;Windows:WSAGetLastError()

错误处理

你写网络收包循环时,基本就处理这些:

  • EINTR:被信号中断,通常 continue 重试
  • EAGAIN / EWOULDBLOCK非阻塞 socket 没数据(或者你用了 MSG_DONTWAIT),这不是“致命错误”,通常退出本轮读取,等下次 epoll/select 通知再读
  • 其他:一般认为连接异常或系统错误,记录并关闭连接

网络硬件层(NIC / DMA / CPU)

NIC

网络接口卡(NIC)负责:

  • 从网线(或无线信号)接收数据包 → 交给操作系统;
  • 从操作系统发送数据包 → 通过网络传出去。
  • 但 NIC 自己不能直接访问操作系统的数据结构。它通过 DMA(Direct Memory Access,直接内存访问) 在系统内存中与 CPU 共享数据,这就需要有个“缓冲区”机制来协调数据传递。

RX/TX Ring

RX(Receive):表示接收方向(从网络 → 系统)。TX(Transmit):表示发送方向(从系统 → 网络)。

RX Ring Buffer:存放“接收到的数据包”的描述符。TX Ring Buffer:存放“要发送出去的数据包”的描述符。每个“环形槽位(slot)”通常不是直接放数据包,而是放一个 Descriptor。每个 Descriptor → 指向一个实际的数据缓冲区(packet buffer)。

是网卡与驱动共享的循环队列(ring buffer),用于收发包描述符(descriptor)

接收流程

驱动程序 在内存中预先分配好一组“包缓冲区”(每个缓冲区能装一个接收包)。

驱动把这些缓冲区的地址放进 RX Ring(环形队列)中,并告诉 NIC。

NIC 接收到网络包后:

  • 直接通过 DMA 把数据写进这些缓冲区;
  • 更新描述符状态为“包已接收”;
  • 通知 CPU(通常通过中断或轮询)。

驱动程序 从 Ring 中取出新的包,交给上层协议栈处理(如 TCP/IP)。

发送流程

当操作系统要发送数据包时:

  • 驱动程序把包的数据放到内存缓冲区;
  • 将该缓冲区的地址和长度填入 TX Ring;
  • 通知 NIC 有新包要发。

NIC 通过 DMA 读取数据并发到网络;

发送完成后,NIC 更新状态,驱动可以回收这些缓冲区

Receive Side Scaling (RSS)

在网卡层面(NIC)就决定——哪个 CPU 核心来处理这个包。

NIC 在接收包时会:

  1. 根据包头(通常是 IP + TCP/UDP)计算一个 哈希值
  2. 通过这个哈希值查一个表(称为 RSS 重定向表,Indirection Table);
  3. 根据查到的结果,把包放到不同的 RX 队列(Receive Queue)
  4. 每个 RX 队列绑定到一个特定的 CPU 核心。

Zero Copy

传统网络 I/O 的数据流:以 recv() 为例:

Network  [NIC DMA]  Kernel Buffer  [CPU copy]  User Buffer

问题:每次收发都要进行 一次或多次内存拷贝

Zero Copy 的目标是: 让 NIC 直接把数据 DMA 到用户空间(user space)

实现思路: 用户空间的缓冲区(user buffer)提前注册并映射到内核和 NIC 可访问的物理内存区域。然后让 NIC 直接通过 DMA 把数据写入这块区域。

  • mmap() + sendfile() :sendfile() 可以让文件内容直接从内核文件缓存传到 NIC,无需拷贝到用户空间。

  • mmap(), splice() 收文件

  • DPDK / RDMA / io_uring 等用户态网络框架, 这些框架干脆跳过内核网络栈,让应用直接管理 DMA buffer

中断与轮询(Interrupt vs Polling)

当 NIC 收到网络包后,它必须让 CPU 处理(交给协议栈 → socket → 应用)。通知 CPU 有两种方式:Interrupt, Polling

  • Interrupt:空闲时节能、高响应性, 高流量时中断太多,CPU 忙于处理中断
  • polling (busy polling): CPU 定期去问:“你有新包吗?”, 高速时高效,不被中断打断, 低流量时浪费 CPU,一直空转

NAPI 模型

NAPI 是 Linux 网络子系统引入的一种“中断 + 轮询混合模型”,

低流量时:中断模式, 高流量时:切换到轮询模式.

应用层的 Polling

上面的 NAPI 属于内核层(驱动到协议栈)。而应用程序也有自己的“等待事件”机制,比如 epollpoll() 等。

epoll

用户态事件机制: epoll 不再每次扫描所有 fd。而是由内核在有事件发生时主动通知我们。

早期 Linux 提供了两种多路复用接口: select, poll : 程序问内核 “这些 fd 有变化吗?”(轮询)

函数 作用
epoll_create() 创建一个 epoll 实例(返回一个 epoll fd)
epoll_ctl() 向 epoll 实例注册 / 修改 / 删除 关注的 fd
epoll_wait() 阻塞等待事件发生(返回已就绪的 fd 列表)

Cache Line Alignment

CPU 缓存不是以“变量”为单位加载的,而是以Cache Line(缓存行)为基本单元。一般大小:64 字节. 含义:CPU 一次会把一整行(64B)数据从内存加载到缓存。Cache Line Alignment 指的是让重要或频繁访问的数据结构的内存地址.

False Sharing

当多个线程修改不同变量,但这些变量恰好位于同一个缓存行时,它们会不断触发缓存同步(cache coherence),导致性能急剧下降。

  • Cache Line 对齐
  • 每个线程使用独立对象 / 独立数组块

Prefetch(预取) 是指让 CPU 或程序提前加载即将使用的数据到缓存中,避免未来访问时产生 cache miss。

Intel DPDK

DPDK 是 Intel 主导的一个用户态网络加速框架, 主要用于 高性能包处理(packet processing)

DPDK 的目标 是在用户态直接访问网卡(绕过内核协议栈),所以它需要把网卡从内核驱动解绑(unbind) 然后重新绑定到用户态驱动(UIO 或 VFIO)

UIO (Userspace I/O Framework)是 Linux 提供的一种通用机制,允许用户态程序访问硬件设备的 MMIO 寄存器和中断

VFIO (Virtual Function I/O Framework) 是比 UIO 更新、更安全、更强大的用户态 I/O 框架。它通过 IOMMU(输入输出内存管理单元)提供.

  • NAPI 的思想在用户态实现。
  • 每个核绑定一个队列(RSS Queue);实现完全 lock-free 的包处理。

Mellanox RDMA

RDMA 是一种远程直接内存访问技术,允许网卡(NIC)直接把数据从一台机器的内存读写到另一台机器的内存.

操作系统内核 I/O 模型

I/O 模型

模型 特点 应用
blocking 最简单,但阻塞线程 教学级
non-blocking + select/poll 多路复用但效率低 旧系统
epoll / kqueue 高效多路复用,O(1) 唤醒 高并发网络服务
io_uring Linux 新一代异步 I/O 框架 高频交易 / 超低延迟系统

用户态 I/O 框架与协议实现

I/O 框架与协议实现

要点 说明
异步 I/O 框架 io_uring, libevent, libuv, boost::asio
零拷贝 sendfile()/splice() 避免多次用户态缓冲区拷贝
mmap + ring buffer 用户态直接读写共享内存
DPDK / RDMA / VMA / Onload 完全绕过内核的用户态网络栈
高性能 socket 设计 批量收发(batch I/O)、减少 syscalls、使用 recvmmsg/sendmmsg

系统级性能调优

模块 要点
Linux 网络参数 net.core.somaxconn, tcp_tw_reuse, rmem_max, wmem_max
中断绑定 / CPU 亲和性 使用 tasksetirqbalance 手动绑定
HugePages / Memory Pool 减少 TLB miss 与 malloc 调用
Profiling 工具 perf, bpftrace, sar, ethtool -S, tcpdump, Wireshark
Lock-free 环形缓冲区 环状队列、无锁队列(lock-free ring buffer)
时间同步 PTP(Precision Time Protocol)、NTP、Time Sync Accuracy < 1μs

网络协议与序列化优化

UDP in HFT

UDP 是最快的传输层协议之一,非常适合对延迟敏感的系统。 HFT 系统会在 应用层(用户态)自己实现 TCP 类功能轻量级的确认(ACK)+ 重传(Retransmission)机制

  • 每个 UDP 包都有一个自增的 seq_num

  • 接收端周期性发送 ACK,告诉发送端自己收到了哪些包。

  • 当接收端发现包丢失(比如缺 1002),

会发送 NACK (Negative Acknowledgment)

二进制协议

传统文本协议(例如 JSON、XML)的问题: 数据量大;字符串解析慢;内存拷贝多;无法零拷贝。

  • Manual Serialization
  • Protobuf(Google)
  • Cap’n Proto

延迟分析

p99 latency:指 99% 的请求延迟都小于该值。 p999 latency:指 99.9% 的请求延迟都小于该值。

Tail Latency: 高百分位延迟通常指 p95、p99、p999 等值。

协议层 要点
TCP/UDP 原理 三次握手、拥塞控制、Nagle、延迟 ACK、MSS
UDP in HFT 因为低延迟但无可靠性,常自实现重传与确认
序列化 自定义二进制协议(手写序列化 / FlatBuffers / Cap’n Proto)
延迟分析 p99, p999 latency、tail latency 分布分析

层面要求的“水平”

能力层次 你要做到的事情
理解层(合格) 能清晰解释 epoll、zero-copy、内核缓冲区、syscall 开销
实现层(中高级) 能写高并发异步 I/O 系统(基于 epoll 或 io_uring)
优化层(专业级) 能定位网络延迟瓶颈(从系统调用、内核到 NIC)
突破层(Rentech级) 能写用户态网络栈(DPDK/RDMA),微调 CPU 亲和性,测延迟到纳秒级精度