Network 网络
在操作系统、内核、网卡(NIC)和用户态之间,最大化吞吐、最小化延迟 的所有工程技术手段。
TCP/IP网络五层模型
物理层
数据单位:比特(bit)
数据链路层
MAC地址:是否是 48 位(6 字节)十六进制:常见写法
00:1A:2B:3C:4D:5E 或 00-1A-2B-3C-4D-5E 或 001A.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/8、172.16.0.0/12、192.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:思科私有起家(后来有开放部分实现),混合特性,收敛快,企业网里见得多(
Data Link
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.0~126.255.255.255(首字节 1–126) - 默认子网掩码:
255.0.0.0(/8) - 用途:超大网络
B 类(Class B)
- 范围:
128.0.0.0~191.255.255.255(首字节 128–191) - 默认子网掩码:
255.255.0.0(/16) - 用途:中等规模网络
C 类(Class C)
- 范围:
192.0.0.0~223.255.255.255(首字节 192–223) - 默认子网掩码:
255.255.255.0(/24) - 用途:小网络(常见局域网)
D 类(Class D)
- 范围:
224.0.0.0~239.255.255.255(224.0.0.0/4) - 用途:多播/组播(Multicast) 不是给主机分配的“单播地址”,而是“组”的地址。
E 类(Class E)
- 范围:
240.0.0.0~255.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 建立可靠连接的过程,确保双方都具备发送和接收数据的能力。
- 第一次握手(SYN): 客户端发送一个 SYN=x(同步序列号)报文段到服务器,并进入
SYN-SENT状态。 - 第二次握手(SYN + ACK): 服务器接收到 SYN 后,发送 SYN=y , ACK=x+1(确认)报文段。它确认收到了客户端的请求,并发送自己的序列号。服务器进入
SYN-RECEIVED状态。 - 第三次握手(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 一类的函数)。
该回调做两件事:
- 把对应的 epitem 标记就绪并(若未在队列中)入就绪队列(去重,避免反复入同一项)。
- 唤醒 正在
epoll_wait上睡眠的线程(把它从等待队列上唤起)。
基础服务器客户端流程
服务端(像开店)
socket():开一个电话机
int fd = socket(AF_INET, SOCK_STREAM, 0); // <sys/socket.h>
if (fd < 0) {
std::perror("socket");
return 1;
}
...
close(fd); // <unistd.h>
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;
}
listen():开始接听
if (::listen(fd, 16) == -1) { // 16: 排队区最大能容纳多少个“等着被 accept 的连接”。
perror("listen");
return 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;
-
recv/send:聊天 -
close:挂电话 (<unistd.h>)
客户端(像打电话)
socket()connect():拨号recv/sendclose
socket
socket(domain, type, protocol)
domain(地址族)
AF_INET:IPv4AF_INET6:IPv6AF_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 在接收包时会:
- 根据包头(通常是 IP + TCP/UDP)计算一个 哈希值;
- 通过这个哈希值查一个表(称为 RSS 重定向表,Indirection Table);
- 根据查到的结果,把包放到不同的 RX 队列(Receive Queue);
- 每个 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 属于内核层(驱动到协议栈)。而应用程序也有自己的“等待事件”机制,比如 epoll、poll() 等。
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 亲和性 | 使用 taskset、irqbalance 手动绑定 |
| 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 亲和性,测延迟到纳秒级精度 |