积累沉淀

待山花烂漫,化茧成蝶

CPP网络编程

前言: 服务器网络编程是构建高性能、可扩展、可靠网络服务(如 Web 服务器、数据库、游戏后端、微服务等)的核心技术。其核心围绕 网络通信模型、I/O 模型、并发处理、协议实现与性能优化 展开。以下从底层到高层,系统详解服务器网络编程的关键技术。


一、基础:网络通信模型

1. OSI 与 TCP/IP 模型

  • 服务器编程主要工作在 传输层(TCP/UDP)应用层(HTTP、gRPC 等)
  • TCP:面向连接、可靠、有序、流量控制 → 适用于大多数业务场景(如 Web、数据库)。
    TCP协议基础: 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议.交互流程如下图:
    tcp-interaction
  • 注意:超时才能解决物理连接中断了默认的超时,可能长达两个小时;弥补:心跳包的机制,强制询问双方是否在线,如果终止,或者异常连续超过若干次(三次)。
  • UDP:无连接、不可靠、低延迟 → 适用于实时音视频、DNS、游戏等。

2. 套接字(Socket)

  • 是网络编程的抽象接口,由 IP 地址 + 端口号 唯一标识一个通信端点。

socket-interaction

  • 基本操作流程(以 TCP服务端 为例):
    1
    socket() → bind() → listen() → accept() → read()/write() → close()
  • 监听套接字(Listening Socket):用于接收新连接。
  • 已连接套接字(Connected Socket):用于与客户端通信。
  • 服务端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <csignal>
#include <cerrno>

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// echo_server.cpp 为echo 的服务端,接收客服端的连接,并把客服端的消息返回过去

constexpr int PORT = 8080;
constexpr int BUF_SIZE = 1024;

// 忽略 SIGPIPE 信号,防止写入已关闭连接时进程终止
// 防止在客户端断开后继续写入时进程收到 SIGPIPE 信号而终止(可选,但提高健壮性)。
void ignore_sigpipe() {
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGPIPE, &sa, nullptr);
}

// 循环发送全部数据(处理部分发送)
ssize_t writen(int fd, const void *buf, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char *ptr = static_cast<const char*>(buf);

while (nleft > 0) {
nwritten = write(fd, ptr, nleft);
if (nwritten < 0) {
if (nwritten == -1 && errno == EINTR)
continue; // 被信号中断,重试
return -1;
} else if (nwritten == 0) {
break; // EOF
}
nleft -= nwritten;
ptr += nwritten;
}
return (n - nleft);
}


int main()
{
// 忽略 SIGPIPE,防止 write 到关闭的 socket 时进程终止
ignore_sigpipe();

//1. 建立socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::perror("socket");
std::exit(1);
}
//2. 设置 SO_REUSEADDR 选项,允许端口重用
int opt = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
std::perror("setsockopt");
close(sockfd);
std::exit(1);
}
//3. 绑定地址和端口
struct sockaddr_in addr_in;
std::memset(&addr_in, 0, sizeof(addr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_addr.s_addr = INADDR_ANY;
addr_in.sin_port = htons(PORT);
if (bind(sockfd, reinterpret_cast<const sockaddr *>(&addr_in), sizeof(addr_in)) < 0) {
std::perror("bind");
close(sockfd);
std::exit(1);
}
//4. 监听
if (listen(sockfd, 15) < 0) {
std::perror("listen");
close(sockfd);
std::exit(1);
}

std::printf("Echo server listening on port:%d\n", PORT);

//5. 循环接收客服端链接
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
char buf[BUF_SIZE];

while (true) {
int client_fd;
do {
client_fd = accept(sockfd, reinterpret_cast<sockaddr *>(&client_addr), &client_len);
} while (client_fd < 0 && errno == EINTR);

if (client_fd < 0) {
std::perror("accept");
continue;
}

std::printf("New client from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

//6. 与客户端交互:接收数据并原样发送回去
ssize_t bytes_read;
while (true) {
// 处理 read 被信号中断的情况
do {
bytes_read = read(client_fd, buf, sizeof(buf));
} while (bytes_read < 0 && errno == EINTR);
if (bytes_read < 0) {
perror("read");
break;
}
if (bytes_read == 0) {
// 客户端关闭连接
break;
}
// 使用 writen 确保全部数据被写回
if (writen(client_fd, buf, bytes_read) != bytes_read) {
std::fprintf(stderr, "write error");
break;
}
}

close(client_fd);
std::printf("Client disconnected." );
}

// 服务器一般不会执行到这里,但为了完整性,关闭监听 socket
close(sockfd);

return 0;
}
  • 基本操作流程(以 TCP客户端 为例):
    1
    socket() → connect() → read()/write() → close()
  • 客服端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cerrno>

constexpr int PORT = 8080;
constexpr int BUF_SIZE = 1024;

// 循环发送全部数据(处理部分发送)
ssize_t writen(int fd, const void *buf, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char *ptr = static_cast<const char*>(buf);

while (nleft > 0) {
nwritten = write(fd, ptr, nleft);
if (nwritten < 0) {
if (nwritten == -1 && errno == EINTR)
continue; // 被信号中断,重试
return -1;
} else if (nwritten == 0) {
break; // EOF
}
nleft -= nwritten;
ptr += nwritten;
}
return (n - nleft);
}

// 循环接收指定字节数(处理部分接收)
ssize_t readn(int fd, void *buf, size_t n) {
size_t nleft = n;
ssize_t nread;
char *ptr = static_cast<char*>(buf);

while (nleft > 0) {
nread = read(fd, ptr, nleft);
if (nread < 0) {
if (nread == -1 && errno == EINTR)
continue; // 被信号中断,重试
return -1;
} else if (nread == 0) {
break; // 连接关闭 EOF
}
nleft -= nread;
ptr += nread;
}
return (n - nleft); // 返回实际读取的字节数
}

int main(int argc, char *argv[]) {
// 指定服务器 IP(默认为 127.0.0.1)
const char *server_ip = "127.0.0.1";
if (argc > 1) {
server_ip = argv[1];
}

// 1. 创建 socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
exit(EXIT_FAILURE);
}

// 2. 设置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
perror("inet_pton");
close(sockfd);
exit(EXIT_FAILURE);
}

// 3. 连接服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
close(sockfd);
exit(EXIT_FAILURE);
}

printf("Connected to echo server %s:%d\n", server_ip, PORT);
printf("Enter text (type 'quit' to exit):\n");

char send_buf[BUF_SIZE];
char recv_buf[BUF_SIZE];

// 4. 循环读取用户输入
while (fgets(send_buf, sizeof(send_buf), stdin) != NULL) {
// 如果用户输入 "quit\n",退出循环
if (strcmp(send_buf, "quit\n") == 0 || strcmp(send_buf, "quit\r\n") == 0) {
printf("Exiting...\n");
break;
}

size_t len = strlen(send_buf);
// 发送数据
if (writen(sockfd, send_buf, len) != (ssize_t)len) {
perror("writen");
break;
}

// 接收服务器回显(接收与发送相同字节数)
ssize_t n = readn(sockfd, recv_buf, len);
if (n < 0) {
perror("readn");
break;
} else if (n == 0) {
printf("Server closed connection\n");
break;
}

// 打印接收到的数据(确保以 null 结尾)
recv_buf[n] = '\0';
printf("Echo: %s", recv_buf);
}

// 5. 关闭 socket
close(sockfd);
return 0;
}
  • 基于TCP服务端/客户端的函数调用关系

TCP-server-client-call-relation

3. TCP 内部原理

  • 三次握手
    一个小故事:TCP 三次握手好比在一个夜高风黑的夜晚,你一个人在小区里散步,不远处看见小区里的一位漂亮妹子迎面而来,但是因为路灯有点暗等原因不能100%确认,所以要通过招手的方式来确定对方是否认识自己。
    你首先向妹子招手(syn),妹子看到你向自己招手后,向你点了点头挤出了一个微笑(ack)。你看到妹子微笑后确认了妹子成功辨认出了自己(进入established状态)。
    但是妹子有点不好意思,向四周看了一看,有没有可能你是在看别人呢,她也需要确认一下。妹子也向你招了招手(syn),你看到妹子向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑(ack),妹子看到对方的微笑后确认了你就是在向自己打招呼(进入established状态)。
    TCP-handshake
  • 四次挥手
    TCP-handshake-close

4. UDP 内部原理

  • 基本操作流程(以 UDP服务端 为例):
    1
    socket() → bind() → recvfrom() → sendto() → close()
  • 服务端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

constexpr int PORT = 8080;
constexpr int BUFFER_SIZE = 1024;

int main() {
// 1. 创建 UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cerr << "socket creation failed" << std::endl;
return 1;
}

// 2. 绑定地址和端口
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);

if (bind(sockfd, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "bind failed" << std::endl;
close(sockfd);
return 1;
}

std::cout << "UDP Echo Server running on port " << PORT << std::endl;

// 3. 循环接收并回显数据
char buffer[BUFFER_SIZE];
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);

while (true) {
ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(sockaddr*)&client_addr, &client_len);
if (recv_len < 0) {
std::cerr << "recvfrom error" << std::endl;
continue;
}

buffer[recv_len] = '\0'; // 确保字符串结束
std::cout << "Received from " << inet_ntoa(client_addr.sin_addr)
<< ":" << ntohs(client_addr.sin_port)
<< " -> " << buffer << std::endl;

// 将数据原样发回客户端
ssize_t sent_len = sendto(sockfd, buffer, recv_len, 0,
(sockaddr*)&client_addr, client_len);
if (sent_len < 0) {
std::cerr << "sendto error" << std::endl;
}
}

close(sockfd);
return 0;
}
  • 基本操作流程(以 UDP客户端 为例):
    1
    socket() → sendto() → recvfrom() → close()
  • 客服端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

constexpr int PORT = 8080;
constexpr int BUFFER_SIZE = 1024;
constexpr const char* SERVER_IP = "127.0.0.1";

int main() {
// 1. 创建 UDP socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
std::cerr << "socket creation failed" << std::endl;
return 1;
}

// 2. 设置服务器地址
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
std::cerr << "Invalid address" << std::endl;
close(sockfd);
return 1;
}

// 3. 发送消息给服务器
const char* message = "Hello, UDP Echo Server!";
socklen_t server_len = sizeof(server_addr);

ssize_t sent_len = sendto(sockfd, message, strlen(message), 0,
(sockaddr*)&server_addr, server_len);
if (sent_len < 0) {
std::cerr << "sendto failed" << std::endl;
close(sockfd);
return 1;
}
std::cout << "Sent: " << message << std::endl;

// 4. 接收服务器回显
char buffer[BUFFER_SIZE];
ssize_t recv_len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,
(sockaddr*)&server_addr, &server_len);
if (recv_len < 0) {
std::cerr << "recvfrom failed" << std::endl;
close(sockfd);
return 1;
}

buffer[recv_len] = '\0';
std::cout << "Received: " << buffer << std::endl;

close(sockfd);
return 0;
}

二、I/O 模型:决定服务器如何处理数据读写

这是服务器性能的核心瓶颈所在。主流 I/O 模型如下:

1. 阻塞 I/O(Blocking I/O)

  • 默认模式。调用 recv()accept() 时,若无数据/连接,则线程阻塞。
  • 缺点:每个连接需一个线程,资源消耗大,难以支撑高并发。

2. 非阻塞 I/O(Non-blocking I/O)

  • 设置 socket 为非阻塞(fcntl(fd, F_SETFL, O_NONBLOCK))。
  • 调用立即返回,若无数据则返回错误(如 EAGAIN)。
  • 问题:需轮询(busy-wait),CPU 浪费严重。

3. I/O 多路复用(I/O Multiplexing)

核心思想:单线程监控多个 socket 的就绪状态,避免为每个连接创建线程。

主流实现:

表格

技术 平台 特点
select 跨平台 最多 1024 fd,线性扫描,性能差
poll 跨平台 无 fd 数量限制,仍需线性扫描
epoll Linux 事件驱动,O(1) 性能,支持边缘触发(ET)/水平触发(LT)
kqueue BSD/macOS 类似 epoll,高效
IOCP Windows 异步完成端口,真正的异步 I/O
现代高性能服务器几乎都基于 epoll/kqueue/IOCP

epoll 工作模式:

  • LT(Level Triggered):默认模式。只要 socket 可读,每次 epoll_wait 都会通知。
  • ET(Edge Triggered):仅在状态变化时通知一次,要求一次性读完数据(需配合非阻塞 socket)。

三、并发模型:如何同时处理多个客户端

1. 多进程模型(Multi-process)

  • fork() 为每个连接创建子进程。
  • 优点:隔离性好,崩溃不影响主进程。
  • 缺点:进程创建开销大,内存不共享,IPC 复杂。
  • 典型:Apache(prefork MPM)。

2. 多线程模型(Multi-threading)

  • 主线程 accept(),工作线程处理连接。
  • 优点:共享内存,通信方便。
  • 缺点:线程上下文切换开销,需处理同步(锁、条件变量)。
  • 风险:C10K 问题(1 万并发)下线程数过多导致性能下降。

3. 事件驱动模型(Event-driven / Reactor)

  • 单线程 + I/O 多路复用(如 epoll)。
  • 所有 I/O 事件由一个事件循环(event loop)处理。
  • 优点:资源占用极低,可轻松支持 C10K~C10M。
  • 缺点:业务逻辑不能阻塞(否则整个服务卡住)。
  • 典型:Nginx、Node.js、Redis。

4. 协程模型(Coroutine / Goroutine)

  • 用户态轻量级线程,由运行时调度。
  • 写同步代码,底层自动切换(基于 epoll + 协程调度)。
  • 优点:开发简单(像同步),性能接近事件驱动。
  • 典型:Go(goroutine)、Python(asyncio)、Java(Virtual Thread)。

四、关键编程技术细节

1. 避免粘包/拆包(TCP 流式特性)

  • TCP 是字节流,无消息边界。
  • 解决方案:- 固定长度消息
    • 分隔符(如 \n
    • 长度前缀(最常用):先发 4 字节表示 body 长度,再发 body。

2. 心跳机制(Keep-alive)

  • 检测连接是否存活(防止客户端异常断开未通知)。
  • 应用层心跳 vs TCP keepalive(后者默认 2 小时,太长)。

3. 连接管理

  • 超时关闭(idle timeout)
  • 连接池(减少频繁建连开销)
  • 半关闭处理(shutdown()

4. 零拷贝(Zero-copy)

  • 减少内核 ↔ 用户空间的数据拷贝。
  • 技术:sendfile()splice()mmap() + write()
  • 应用:静态文件服务(Nginx 使用 sendfile)。

5. 内存池与对象复用

  • 避免频繁 malloc/free 导致内存碎片和性能抖动。
  • 如 Redis 的 SDS、Nginx 的内存池。

五、高性能服务器架构模式

表格

模式 描述 适用场景
Reactor 事件驱动,单线程或多线程事件循环 Nginx、Netty
Proactor 真正的异步 I/O(如 Windows IOCP) Windows 高性能服务
Master-Worker 主进程监听,子进程/线程处理 Apache、传统多进程服务器
Pipeline 请求分阶段处理(如解析 → 业务 → 响应) 复杂业务逻辑

六、现代服务器开发框架/库

表格

语言 框架/库 模型
C/C++ libevent, libuv, Boost.Asio Reactor
Java Netty, Undertow Reactor + 多线程
Go net/http, gnet Goroutine(协程)
Python asyncio, Tornado Event-loop + 协程
Rust Tokio, async-std Async/Await + Reactor

七、性能调优要点

  1. 避免阻塞操作:数据库查询、文件 I/O 应异步化或交由线程池。
  2. 合理设置 backloglisten(sockfd, backlog) 控制等待队列长度。
  3. TCP 参数调优:- SO_REUSEADDR:快速重启绑定端口
    • TCP_NODELAY:禁用 Nagle 算法(降低延迟)
    • SO_RCVBUF / SO_SNDBUF:调整缓冲区大小
  4. 使用 ET 模式 + 非阻塞 socket:最大化 epoll 效率。
  5. 负载均衡:单机性能有限,需结合 LVS/Nginx 做集群。

八、安全考虑

  • 防止 DDoS:连接数限制、速率限制
  • 防止 缓冲区溢出:严格校验输入长度
  • 使用 TLS/SSL:加密通信(OpenSSL、BoringSSL)
  • 避免 Slowloris 攻击:设置读写超时

总结

服务器网络编程的核心在于:

“用最少的资源,高效、可靠地处理海量并发连接。”

掌握以下四点即可构建高性能服务器:

  1. 理解 I/O 模型(尤其是 epoll/kqueue)
  2. 选择合适的并发模型(Reactor / 协程)
  3. 处理 TCP 流特性(粘包、心跳、超时)
  4. 避免性能陷阱(阻塞、内存拷贝、锁竞争)

详解IPv4 和 IPv6

IP 地址(Internet Protocol Address)是用于在网络中唯一标识设备的逻辑地址。它使得设备之间能够相互通信。目前广泛使用的 IP 地址主要有两种版本:IPv4IPv6。下面分别详细说明它们的组成、格式、特点及区别。


一、IPv4 地址

1. 基本组成

  • IPv4(Internet Protocol version 4)使用 32 位(4 字节) 的地址空间。
  • 总共可表示 255*255*255*255 =4,294,967,296232 个地址(约 43 亿个)。

2. 表示方式

  • 采用 点分十进制(Dotted Decimal Notation) 表示法:- 将 32 位地址分为 4 个 8 位(1 字节)的段,每段转换为十进制数,用点号 . 分隔。
    • 例如:192.168.1.1

3. 结构划分

IPv4 地址由两部分组成:

  • 网络号(Network ID):标识所属网络。
  • 主机号(Host ID):标识该网络中的具体设备。

根据地址的前几位,IPv4 地址被划分为 A、B、C、D、E 五类:
表格

类别 范围(首字节) 网络位数 主机位数 用途
A 1 – 126 8 位 24 位 大型网络
B 128 – 191 16 位 16 位 中型网络
C 192 – 223 24 位 8 位 小型网络
D 224 – 239 组播(Multicast)
E 240 – 255 保留(实验用)

注意:127.x.x.x 是回环地址(如 127.0.0.1),不用于实际网络通信。

  • 以0开头的地址,都是不可以用的
  • 以0结尾的地址,表示的是网段,而不是具体的地址
  • 224开头到239开头的地址,是组播地址,不可用于点对点的传输

4. 特殊地址

  • 0.0.0.0:表示默认路由或“任意地址”。
  • 255.255.255.255:受限广播地址。
  • 127.0.0.1:本地回环地址。
  • 169.254.x.x:APIPA(自动私有 IP 寻址),当 DHCP 失败时自动分配。
  • 私有地址(不可在公网路由):- A 类:10.0.0.0 – 10.255.255.255
    • B 类:172.16.0.0 – 172.31.255.255
    • C 类:192.168.0.0 – 192.168.255.255

5. 子网掩码(Subnet Mask)

  • 用于区分网络号和主机号。
  • 例如:255.255.255.0 表示前 24 位是网络号,后 8 位是主机号(即 /24)。

二、IPv6 地址

1. 基本组成

  • IPv6(Internet Protocol version 6)使用 128 位(16 字节) 地址空间。
  • 总共可表示 2128≈3.4×10382128≈3.4×1038 个地址,几乎无限。

2. 表示方式

  • 采用 冒号十六进制(Colon-Hexadecimal) 表示法:- 将 128 位地址分为 8 段,每段 16 位(4 个十六进制数字),用冒号 : 分隔。
    • 例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334

压缩规则(简化写法):

  • 前导零可省略2001:db8:85a3:0:0:8a2e:370:7334
  • 连续全零段可用 :: 代替(仅一次):- 上例可进一步压缩为:2001:db8:85a3::8a2e:370:7334

3. 地址类型

IPv6 不再使用“类别”概念,而是按功能分为三类:
表格

类型 说明
单播(Unicast) 标识单个接口,数据包发往一个目标。
组播(Multicast) 标识一组接口,数据包发往多个目标(取代广播)。
任播(Anycast) 标识一组接口,但数据包只送达“最近”的一个(按路由距离)。

IPv6 没有广播地址,广播功能由组播实现。

4. 常见 IPv6 地址前缀

  • ::1/128:本地回环地址(相当于 IPv4 的 127.0.0.1)
  • ::/128:未指定地址(类似 0.0.0.0)
  • fe80::/10:链路本地地址(Link-Local),仅在同一物理/逻辑链路内有效
  • fc00::/7:唯一本地地址(ULA,Unique Local Address),相当于 IPv4 的私有地址
  • 2000::/3:全球单播地址(Global Unicast),可在互联网上路由

5. 地址结构(以全球单播为例)

典型的全球单播 IPv6 地址结构如下:
text编辑

1
2
3
| 3 bits |   45 bits   |   16 bits   |      64 bits       |
+--------+-------------+-------------+--------------------+
| 001 | Global Routing Prefix | Subnet ID | Interface ID (EUI-64 或随机) |
  • Global Routing Prefix:由 ISP 分配的全球路由前缀。
  • Subnet ID:组织内部子网划分。
  • Interface ID:设备接口标识,常由 MAC 地址通过 EUI-64 生成,或使用隐私扩展(随机生成)。

三、IPv4 与 IPv6 主要区别总结

表格

特性 IPv4 IPv6
地址长度 32 位 128 位
地址数量 ~43 亿 ~3.4×10³⁸
表示法 点分十进制 冒号十六进制
配置方式 手动 / DHCP 自动配置(SLAAC)、DHCPv6
广播 支持 不支持(用组播替代)
安全性 依赖附加协议(如 IPsec) 内置 IPsec 支持
报头结构 较复杂,含校验和等 更简洁,无校验和,提高路由效率
移动性 有限支持 原生支持移动 IPv6
NAT 需求 广泛使用(因地址不足) 基本不需要(地址充足)

四、过渡技术(IPv4 → IPv6)

由于 IPv4 仍在广泛使用,IPv6 的部署通常采用以下过渡机制:

  • 双栈(Dual Stack):设备同时运行 IPv4 和 IPv6。
  • 隧道(Tunneling):如 6to4、GRE,将 IPv6 包封装在 IPv4 中传输。
  • 协议转换(Translation):如 NAT64,实现 IPv6 与 IPv4 互访。
Buy me a coffee please.