splice系统调用及示例

splice 函数详解

  1. 函数介绍

splice 是Linux系统调用,用于在两个文件描述符之间高效地移动数据,而无需将数据复制到用户空间。它是Linux特有的零拷贝I/O操作,特别适用于管道、套接字和文件之间的数据传输,能够显著提高I/O性能。

  1. 函数原型
1
2
3
4
5
6
7
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>

ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out,
size_t len, unsigned int flags);

  1. 功能

splice 在两个文件描述符之间直接传输数据,避免了用户空间和内核空间之间的数据拷贝。它利用内核中的管道缓冲区机制,实现了高效的零拷贝数据传输。

  1. 参数
  • int fd_in: 源文件描述符

  • *loff_t off_in: 源文件偏移量指针(NULL表示使用当前文件位置)

  • int fd_out: 目标文件描述符

  • *loff_t off_out: 目标文件偏移量指针(NULL表示使用当前文件位置)

  • size_t len: 要传输的数据长度

  • unsigned int flags: 控制标志

  1. 返回值
  • 成功: 返回实际传输的字节数

  • 失败: 返回-1,并设置errno

  1. 相似函数,或关联函数
  • tee: 在两个管道之间复制数据

  • vmsplice: 将用户空间数据写入管道

  • sendfile: 在文件描述符之间传输数据

  • copy_file_range: 复制文件数据

  1. 示例代码

示例1:基础splice使用

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
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

/**
* 演示基础splice使用方法
*/
int demo_splice_basic() {
int pipefd&#91;2];
int input_fd, output_fd;
const char *input_file = "input.txt";
const char *output_file = "output.txt";
char test_data&#91;] = "This is test data for splice operation.\nHello, splice!\n";
ssize_t bytes_transferred;

printf("=== 基础splice使用示例 ===\n");

// 创建测试输入文件
input_fd = open(input_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (input_fd == -1) {
perror("创建输入文件失败");
return -1;
}

write(input_fd, test_data, strlen(test_data));
close(input_fd);
printf("创建测试输入文件: %s\n", input_file);

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
unlink(input_file);
return -1;
}

printf("创建管道: 读端=%d, 写端=%d\n", pipefd&#91;0], pipefd&#91;1]);

// 打开输入文件用于读取
input_fd = open(input_file, O_RDONLY);
if (input_fd == -1) {
perror("打开输入文件失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
return -1;
}

// 打开输出文件用于写入
output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (output_fd == -1) {
perror("创建输出文件失败");
close(input_fd);
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
return -1;
}

printf("开始splice操作:\n");

// 第一步:从文件读取到管道(使用SPLICE_F_MOVE标志)
bytes_transferred = splice(input_fd, NULL, pipefd&#91;1], NULL,
strlen(test_data), SPLICE_F_MOVE);
if (bytes_transferred == -1) {
perror("splice读取到管道失败");
goto cleanup;
}
printf(" 从文件到管道传输了 %zd 字节\n", bytes_transferred);

// 第二步:从管道写入到文件
bytes_transferred = splice(pipefd&#91;0], NULL, output_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_transferred == -1) {
perror("splice从管道写入文件失败");
goto cleanup;
}
printf(" 从管道到文件传输了 %zd 字节\n", bytes_transferred);

printf("splice操作完成\n");

// 验证输出文件内容
printf("\n验证输出文件内容:\n");
char buffer&#91;256];
lseek(output_fd, 0, SEEK_SET);
int output_read_fd = open(output_file, O_RDONLY);
if (output_read_fd != -1) {
ssize_t read_bytes = read(output_read_fd, buffer, sizeof(buffer) - 1);
if (read_bytes > 0) {
buffer&#91;read_bytes] = '\0';
printf("输出文件内容: %s", buffer);
}
close(output_read_fd);
}

cleanup:
close(input_fd);
close(output_fd);
close(pipefd&#91;0]);
close(pipefd&#91;1]);
unlink(input_file);
unlink(output_file);

return 0;
}

int main() {
return demo_splice_basic();
}

示例2:管道到套接字的数据传输

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>

/**
* 服务器线程函数
*/
void* server_thread(void *arg) {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
int pipefd&#91;2];
char buffer&#91;1024];
ssize_t bytes_read, bytes_written;

printf("服务器线程启动\n");

// 创建TCP服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建服务器套接字失败");
return NULL;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定套接字失败");
close(server_fd);
return NULL;
}

// 监听连接
if (listen(server_fd, 1) == -1) {
perror("监听失败");
close(server_fd);
return NULL;
}

printf("服务器监听在端口 8080\n");

// 接受客户端连接
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受连接失败");
close(server_fd);
return NULL;
}

printf("客户端连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 创建管道用于splice操作
if (pipe(pipefd) == -1) {
perror("创建管道失败");
close(client_fd);
close(server_fd);
return NULL;
}

// 准备测试数据
const char *test_data = "Hello from splice server!\nThis is data transferred using splice.\n";

// 将数据写入管道
bytes_written = write(pipefd&#91;1], test_data, strlen(test_data));
if (bytes_written == -1) {
perror("写入管道失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(client_fd);
close(server_fd);
return NULL;
}

printf("写入管道 %zd 字节数据\n", bytes_written);

// 使用splice将管道数据传输到套接字
ssize_t bytes_transferred = splice(pipefd&#91;0], NULL, client_fd, NULL,
bytes_written, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred == -1) {
perror("splice传输失败");
} else {
printf("splice传输了 %zd 字节到客户端\n", bytes_transferred);
}

// 清理资源
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(client_fd);
close(server_fd);

printf("服务器线程结束\n");
return NULL;
}

/**
* 客户端函数
*/
int client_function() {
int client_fd;
struct sockaddr_in server_addr;
char buffer&#91;1024];
ssize_t bytes_received;

printf("客户端启动\n");

// 创建TCP客户端套接字
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("创建客户端套接字失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

// 连接到服务器
printf("连接到服务器...\n");
if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("连接服务器失败");
close(client_fd);
return -1;
}

printf("连接成功\n");

// 接收数据
bytes_received = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytes_received > 0) {
buffer&#91;bytes_received] = '\0';
printf("接收到服务器数据 (%zd 字节):\n%s", bytes_received, buffer);
} else if (bytes_received == -1) {
perror("接收数据失败");
} else {
printf("连接关闭\n");
}

close(client_fd);
printf("客户端结束\n");
return 0;
}

/**
* 演示管道到套接字的splice传输
*/
int demo_pipe_to_socket_splice() {
pthread_t server_tid;

printf("=== 管道到套接字splice传输演示 ===\n");

// 创建服务器线程
if (pthread_create(&server_tid, NULL, server_thread, NULL) != 0) {
perror("创建服务器线程失败");
return -1;
}

// 等待服务器启动
sleep(1);

// 运行客户端
client_function();

// 等待服务器线程结束
pthread_join(server_tid, NULL);

return 0;
}

int main() {
return demo_pipe_to_socket_splice();
}

示例3:文件到文件的零拷贝传输

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>

/**
* 创建大文件用于测试
*/
int create_large_test_file(const char *filename, size_t size) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}

// 分配缓冲区
char *buffer = malloc(1024 * 1024); // 1MB缓冲区
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}

// 填充测试数据
for (int i = 0; i < 1024 * 1024; i++) {
buffer&#91;i] = 'A' + (i % 26);
}

// 写入数据
size_t written = 0;
while (written < size) {
size_t to_write = (size - written < 1024 * 1024) ? size - written : 1024 * 1024;
ssize_t result = write(fd, buffer, to_write);
if (result == -1) {
perror("写入文件失败");
free(buffer);
close(fd);
return -1;
}
written += result;
}

free(buffer);
close(fd);

printf("创建了 %zu MB 的测试文件: %s\n", size / (1024 * 1024), filename);
return 0;
}

/**
* 使用splice进行文件传输
*/
ssize_t splice_file_transfer(int src_fd, int dst_fd, size_t total_size) {
int pipefd&#91;2];
ssize_t total_transferred = 0;
ssize_t bytes_transferred;
const size_t chunk_size = 64 * 1024; // 64KB chunks

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
return -1;
}

printf("开始splice文件传输 (%zu 字节)...\n", total_size);

while (total_transferred < (ssize_t)total_size) {
size_t remaining = total_size - total_transferred;
size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;

// 从源文件读取到管道
bytes_transferred = splice(src_fd, NULL, pipefd&#91;1], NULL,
to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue; // 重试
}
perror("splice读取失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
return -1;
}

if (bytes_transferred == 0) {
break; // 文件结束
}

// 从管道写入到目标文件
ssize_t bytes_written = splice(pipefd&#91;0], NULL, dst_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_written == -1) {
perror("splice写入失败");
close(pipefd&#91;0]);
close(pipefd&#91;1]);
return -1;
}

total_transferred += bytes_written;

if (total_transferred % (10 * 1024 * 1024) == 0) { // 每10MB显示一次进度
printf("已传输: %.1f MB\n", total_transferred / (1024.0 * 1024.0));
}
}

close(pipefd&#91;0]);
close(pipefd&#91;1]);

printf("文件传输完成: %zd 字节\n", total_transferred);
return total_transferred;
}

/**
* 比较splice和传统read/write性能
*/
int compare_transfer_performance() {
const char *src_file = "large_source.dat";
const char *dst_file_splice = "large_dest_splice.dat";
const char *dst_file_traditional = "large_dest_traditional.dat";
const size_t file_size = 100 * 1024 * 1024; // 100MB
struct timespec start, end;
double splice_time, traditional_time;

printf("=== 文件传输性能对比 ===\n");

// 创建测试文件
if (create_large_test_file(src_file, file_size) != 0) {
return -1;
}

// 测试splice性能
printf("\n1. 测试splice传输性能:\n");
clock_gettime(CLOCK_MONOTONIC, &start);

int src_fd = open(src_file, O_RDONLY);
int dst_fd = open(dst_file_splice, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (src_fd != -1 && dst_fd != -1) {
ssize_t transferred = splice_file_transfer(src_fd, dst_fd, file_size);
if (transferred != -1) {
clock_gettime(CLOCK_MONOTONIC, &end);
splice_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("splice传输时间: %.3f 秒\n", splice_time);
printf("splice传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / splice_time);
}
}

if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);

// 测试传统read/write性能
printf("\n2. 测试传统read/write性能:\n");
clock_gettime(CLOCK_MONOTONIC, &start);

src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file_traditional, O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (src_fd != -1 && dst_fd != -1) {
char *buffer = malloc(64 * 1024);
if (buffer) {
ssize_t bytes_read, bytes_written;
ssize_t total_transferred = 0;

while ((bytes_read = read(src_fd, buffer, 64 * 1024)) > 0) {
bytes_written = write(dst_fd, buffer, bytes_read);
if (bytes_written == -1) {
perror("写入失败");
break;
}
total_transferred += bytes_written;
}

free(buffer);
clock_gettime(CLOCK_MONOTONIC, &end);
traditional_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("传统传输时间: %.3f 秒\n", traditional_time);
printf("传统传输速度: %.2f MB/s\n", file_size / (1024.0 * 1024.0) / traditional_time);
}
}

if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);

// 显示性能对比
printf("\n=== 性能对比结果 ===\n");
if (splice_time > 0 && traditional_time > 0) {
double improvement = (traditional_time - splice_time) / traditional_time * 100;
printf("splice性能提升: %.1f%%\n", improvement);
}

// 验证文件一致性
printf("\n验证文件一致性:\n");
struct stat src_stat, splice_stat, traditional_stat;

if (stat(src_file, &src_stat) == 0 &&
stat(dst_file_splice, &splice_stat) == 0 &&
stat(dst_file_traditional, &traditional_stat) == 0) {

if (src_stat.st_size == splice_stat.st_size &&
splice_stat.st_size == traditional_stat.st_size) {
printf("✓ 文件大小一致\n");
} else {
printf("✗ 文件大小不一致\n");
}
}

// 清理测试文件
unlink(src_file);
unlink(dst_file_splice);
unlink(dst_file_traditional);

return 0;
}

int main() {
return compare_transfer_performance();
}

示例4:网络代理服务器

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <poll.h>

/**
* 代理连接结构
*/
typedef struct {
int client_fd;
int target_fd;
int pipe_to_target&#91;2];
int pipe_to_client&#91;2];
volatile int active;
} proxy_connection_t;

/**
* 创建到目标服务器的连接
*/
int connect_to_target(const char *target_ip, int target_port) {
int target_fd;
struct sockaddr_in target_addr;

target_fd = socket(AF_INET, SOCK_STREAM, 0);
if (target_fd == -1) {
perror("创建目标连接失败");
return -1;
}

memset(&target_addr, 0, sizeof(target_addr));
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(target_port);
target_addr.sin_addr.s_addr = inet_addr(target_ip);

if (connect(target_fd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == -1) {
perror("连接目标服务器失败");
close(target_fd);
return -1;
}

printf("成功连接到目标服务器 %s:%d\n", target_ip, target_port);
return target_fd;
}

/**
* 代理连接处理线程
*/
void* proxy_thread(void *arg) {
proxy_connection_t *conn = (proxy_connection_t*)arg;
ssize_t bytes_transferred;
struct pollfd fds&#91;2];
int nfds = 2;

printf("代理线程启动,处理客户端连接 %d\n", conn->client_fd);

// 设置非阻塞模式
int flags = fcntl(conn->client_fd, F_GETFL, 0);
fcntl(conn->client_fd, F_SETFL, flags | O_NONBLOCK);
flags = fcntl(conn->target_fd, F_GETFL, 0);
fcntl(conn->target_fd, F_SETFL, flags | O_NONBLOCK);

// 初始化poll结构
fds&#91;0].fd = conn->client_fd;
fds&#91;0].events = POLLIN;
fds&#91;1].fd = conn->target_fd;
fds&#91;1].events = POLLIN;

// 主代理循环
while (conn->active) {
int activity = poll(fds, nfds, 1000); // 1秒超时
if (activity == -1) {
if (errno == EINTR) continue;
perror("poll失败");
break;
}

if (activity == 0) {
continue; // 超时,继续循环
}

// 处理客户端到目标服务器的数据
if (fds&#91;0].revents & POLLIN) {
bytes_transferred = splice(conn->client_fd, NULL, conn->pipe_to_target&#91;1], NULL,
64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred > 0) {
printf("从客户端接收 %zd 字节\n", bytes_transferred);

// 将数据从管道传输到目标服务器
ssize_t sent = splice(conn->pipe_to_target&#91;0], NULL, conn->target_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (sent > 0) {
printf("转发到目标服务器 %zd 字节\n", sent);
} else if (sent == -1) {
perror("转发到目标服务器失败");
break;
}
} else if (bytes_transferred == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("从客户端接收数据失败");
break;
}
} else if (bytes_transferred == 0) {
printf("客户端连接关闭\n");
break;
}
}

// 处理目标服务器到客户端的数据
if (fds&#91;1].revents & POLLIN) {
bytes_transferred = splice(conn->target_fd, NULL, conn->pipe_to_client&#91;1], NULL,
64 * 1024, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred > 0) {
printf("从目标服务器接收 %zd 字节\n", bytes_transferred);

// 将数据从管道传输到客户端
ssize_t sent = splice(conn->pipe_to_client&#91;0], NULL, conn->client_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (sent > 0) {
printf("转发到客户端 %zd 字节\n", sent);
} else if (sent == -1) {
perror("转发到客户端失败");
break;
}
} else if (bytes_transferred == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("从目标服务器接收数据失败");
break;
}
} else if (bytes_transferred == 0) {
printf("目标服务器连接关闭\n");
break;
}
}

// 检查错误条件
if (fds&#91;0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("客户端连接异常\n");
break;
}

if (fds&#91;1].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("目标服务器连接异常\n");
break;
}
}

conn->active = 0;
printf("代理线程结束\n");
return NULL;
}

/**
* 代理服务器主函数
*/
int demo_proxy_server() {
int server_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);

printf("=== 网络代理服务器演示 ===\n");

// 创建代理服务器套接字
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("创建代理服务器失败");
return -1;
}

// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8081);

// 绑定套接字
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("绑定代理服务器失败");
close(server_fd);
return -1;
}

// 监听连接
if (listen(server_fd, 10) == -1) {
perror("代理服务器监听失败");
close(server_fd);
return -1;
}

printf("代理服务器监听在端口 8081\n");
printf("注意:这是一个演示程序,实际使用需要完善错误处理\n");

// 由于这是一个演示程序,我们只处理一个连接
printf("等待客户端连接...\n");

int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("接受客户端连接失败");
close(server_fd);
return -1;
}

printf("客户端连接: %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// 连接到目标服务器(这里使用回环地址作为示例)
int target_fd = connect_to_target("127.0.0.1", 80); // 假设80端口有服务
if (target_fd == -1) {
printf("注意:无法连接到目标服务器,演示继续但不会转发数据\n");
target_fd = socket(AF_INET, SOCK_STREAM, 0);
if (target_fd == -1) {
close(client_fd);
close(server_fd);
return -1;
}
}

// 创建代理连接结构
proxy_connection_t conn;
conn.client_fd = client_fd;
conn.target_fd = target_fd;
conn.active = 1;

// 创建管道
if (pipe(conn.pipe_to_target) == -1 || pipe(conn.pipe_to_client) == -1) {
perror("创建管道失败");
close(client_fd);
close(target_fd);
close(server_fd);
return -1;
}

printf("代理连接建立完成\n");
printf("代理服务器功能演示:\n");
printf(" - 使用splice实现零拷贝数据传输\n");
printf(" - 双向数据转发\n");
printf(" - 非阻塞I/O操作\n");
printf(" - 高效的网络代理\n");

// 由于这是一个演示,我们不实际运行代理循环
// 在实际应用中,这里会启动代理线程处理数据转发

sleep(2); // 模拟处理时间

// 清理资源
close(conn.pipe_to_target&#91;0]);
close(conn.pipe_to_target&#91;1]);
close(conn.pipe_to_client&#91;0]);
close(conn.pipe_to_client&#91;1]);
close(client_fd);
close(target_fd);
close(server_fd);

printf("代理服务器演示完成\n");
return 0;
}

int main() {
return demo_proxy_server();
}

示例5:splice性能优化和最佳实践

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/resource.h>

/**
* splice性能测试配置
*/
typedef struct {
size_t buffer_size;
size_t chunk_size;
int use_splice;
int use_flags;
const char *description;
} splice_test_config_t;

/**
* 创建测试数据文件
*/
int create_test_data_file(const char *filename, size_t size) {
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd == -1) {
perror("创建测试文件失败");
return -1;
}

// 使用随机数据填充文件
char *buffer = malloc(1024 * 1024);
if (!buffer) {
perror("分配缓冲区失败");
close(fd);
return -1;
}

srand(time(NULL));
for (size_t i = 0; i < size; i += 1024) {
for (int j = 0; j < 1024 && i + j < size; j++) {
buffer&#91;j] = rand() % 256;
}
write(fd, buffer, (size - i < 1024) ? size - i : 1024);
}

free(buffer);
close(fd);

printf("创建测试文件: %s (%.1f MB)\n", filename, size / (1024.0 * 1024.0));
return 0;
}

/**
* 使用splice进行数据传输
*/
double test_splice_performance(const char *src_file, const char *dst_file,
size_t chunk_size) {
int src_fd, dst_fd, pipefd&#91;2];
struct timespec start, end;
ssize_t bytes_transferred, total_transferred = 0;
struct stat file_stat;

// 获取文件大小
if (stat(src_file, &file_stat) == -1) {
perror("获取文件状态失败");
return -1;
}

// 打开文件
src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (src_fd == -1 || dst_fd == -1) {
perror("打开文件失败");
if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);
return -1;
}

// 创建管道
if (pipe(pipefd) == -1) {
perror("创建管道失败");
close(src_fd);
close(dst_fd);
return -1;
}

// 开始计时
clock_gettime(CLOCK_MONOTONIC, &start);

// 执行splice传输
while (total_transferred < file_stat.st_size) {
size_t remaining = file_stat.st_size - total_transferred;
size_t to_transfer = (remaining < chunk_size) ? remaining : chunk_size;

// 读取到管道
bytes_transferred = splice(src_fd, NULL, pipefd&#91;1], NULL,
to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred <= 0) {
if (bytes_transferred == -1) {
perror("splice读取失败");
}
break;
}

// 写入到目标文件
ssize_t bytes_written = splice(pipefd&#91;0], NULL, dst_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_written <= 0) {
if (bytes_written == -1) {
perror("splice写入失败");
}
break;
}

total_transferred += bytes_written;
}

// 结束计时
clock_gettime(CLOCK_MONOTONIC, &end);

// 清理资源
close(pipefd&#91;0]);
close(pipefd&#91;1]);
close(src_fd);
close(dst_fd);

double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
return elapsed;
}

/**
* 使用传统read/write进行数据传输
*/
double test_traditional_performance(const char *src_file, const char *dst_file,
size_t buffer_size) {
int src_fd, dst_fd;
struct timespec start, end;
char *buffer;
ssize_t bytes_read, bytes_written;
ssize_t total_transferred = 0;
struct stat file_stat;

// 获取文件大小
if (stat(src_file, &file_stat) == -1) {
perror("获取文件状态失败");
return -1;
}

// 分配缓冲区
buffer = malloc(buffer_size);
if (!buffer) {
perror("分配缓冲区失败");
return -1;
}

// 打开文件
src_fd = open(src_file, O_RDONLY);
dst_fd = open(dst_file, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (src_fd == -1 || dst_fd == -1) {
perror("打开文件失败");
free(buffer);
if (src_fd != -1) close(src_fd);
if (dst_fd != -1) close(dst_fd);
return -1;
}

// 开始计时
clock_gettime(CLOCK_MONOTONIC, &start);

// 执行传统传输
while ((bytes_read = read(src_fd, buffer, buffer_size)) > 0) {
bytes_written = write(dst_fd, buffer, bytes_read);
if (bytes_written == -1) {
perror("写入失败");
break;
}
total_transferred += bytes_written;
}

// 结束计时
clock_gettime(CLOCK_MONOTONIC, &end);

// 清理资源
free(buffer);
close(src_fd);
close(dst_fd);

double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
return elapsed;
}

/**
* 演示splice性能优化和最佳实践
*/
int demo_splice_optimization() {
const char *src_file = "performance_test_src.dat";
const char *dst_file_splice = "performance_test_dst_splice.dat";
const char *dst_file_traditional = "performance_test_dst_traditional.dat";
const size_t file_size = 200 * 1024 * 1024; // 200MB
const size_t chunk_sizes&#91;] = {4096, 16384, 65536, 262144, 1048576}; // 4KB到1MB
const int num_chunk_sizes = sizeof(chunk_sizes) / sizeof(chunk_sizes&#91;0]);

printf("=== splice性能优化和最佳实践演示 ===\n");

// 检查系统是否支持splice
printf("检查splice支持:\n");
int test_pipe&#91;2];
if (pipe(test_pipe) == 0) {
printf(" ✓ 系统支持管道操作\n");
close(test_pipe&#91;0]);
close(test_pipe&#91;1]);
} else {
printf(" ✗ 系统不支持管道操作\n");
return -1;
}

// 创建测试文件
printf("\n创建测试文件...\n");
if (create_test_data_file(src_file, file_size) != 0) {
return -1;
}

printf("\n=== 性能测试结果 ===\n");
printf("%-12s %-12s %-15s %-15s %-10s\n",
"传输方式", "块大小", "传输时间(秒)", "传输速度(MB/s)", "性能提升");
printf("%-12s %-12s %-15s %-15s %-10s\n",
"--------", "--------", "------------", "------------", "--------");

// 测试不同块大小的splice性能
for (int i = 0; i < num_chunk_sizes; i++) {
char dst_file&#91;256];
snprintf(dst_file, sizeof(dst_file), "splice_chunk_%zu.dat", chunk_sizes&#91;i]);

double elapsed = test_splice_performance(src_file, dst_file, chunk_sizes&#91;i]);
if (elapsed > 0) {
double speed = file_size / (1024.0 * 1024.0) / elapsed;
printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n",
"splice", chunk_sizes&#91;i], elapsed, speed, "N/A");
}

unlink(dst_file);
}

// 测试传统方法性能(使用不同缓冲区大小)
const size_t buffer_sizes&#91;] = {4096, 16384, 65536, 262144};
const int num_buffer_sizes = sizeof(buffer_sizes) / sizeof(buffer_sizes&#91;0]);

for (int i = 0; i < num_buffer_sizes; i++) {
char dst_file&#91;256];
snprintf(dst_file, sizeof(dst_file), "traditional_buf_%zu.dat", buffer_sizes&#91;i]);

double elapsed = test_traditional_performance(src_file, dst_file, buffer_sizes&#91;i]);
if (elapsed > 0) {
double speed = file_size / (1024.0 * 1024.0) / elapsed;
printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n",
"传统方法", buffer_sizes&#91;i], elapsed, speed, "N/A");
}

unlink(dst_file);
}

// 最佳实践建议
printf("\n=== splice使用最佳实践 ===\n");
printf("1. 块大小选择:\n");
printf(" - 小文件: 4KB-16KB\n");
printf(" - 大文件: 64KB-1MB\n");
printf(" - 根据系统和硬件调整\n");

printf("\n2. 标志使用:\n");
printf(" - SPLICE_F_MOVE: 移动页面而不是复制\n");
printf(" - SPLICE_F_MORE: 提示还有更多数据\n");
printf(" - SPLICE_F_GIFT: 释放用户页面\n");

printf("\n3. 性能优化:\n");
printf(" - 使用合适的管道大小\n");
printf(" - 避免频繁的小块传输\n");
printf(" - 结合非阻塞I/O使用\n");
printf(" - 监控系统资源使用\n");

printf("\n4. 错误处理:\n");
printf(" - 检查返回值和errno\n");
printf(" - 处理部分传输情况\n");
printf(" - 优雅降级到传统方法\n");

// 清理测试文件
unlink(src_file);
unlink(dst_file_splice);
unlink(dst_file_traditional);

return 0;
}

int main() {
return demo_splice_optimization();
}

splice 使用注意事项

系统要求:

内核版本: 需要Linux 2.6.17或更高版本

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

架构支持: 支持所有主流架构

编译选项: 需要定义_GNU_SOURCE

文件描述符要求:

管道支持: 至少一个文件描述符必须是管道

类型限制: 某些文件类型可能不支持splice

权限要求: 需要适当的文件访问权限

标志参数:

SPLICE_F_MOVE: 尝试移动页面而不是复制

SPLICE_F_NONBLOCK: 非阻塞操作

SPLICE_F_MORE: 提示还有更多数据

SPLICE_F_GIFT: 释放用户页面给内核

错误处理:

EINVAL: 参数无效

ENOMEM: 内存不足

ESPIPE: 文件描述符不支持lseek

EAGAIN: 非阻塞操作时资源不可用

性能考虑:

块大小: 选择合适的传输块大小

管道大小: 调整管道缓冲区大小

系统调用: 减少不必要的系统调用

内存对齐: 考虑内存对齐优化

安全考虑:

权限检查: 确保有适当的文件访问权限

资源限制: 避免消耗过多系统资源

错误恢复: 妥善处理错误情况

最佳实践:

适当使用: 在合适的场景下使用splice

性能测试: 在实际环境中测试性能

错误处理: 完善的错误处理机制

资源管理: 及时释放分配的资源

splice vs 相似函数对比

splice vs sendfile:

1
2
3
4
5
6
7
// splice: 需要管道作为中间缓冲
splice(fd_in, NULL, pipe_write, NULL, len, flags);
splice(pipe_read, NULL, fd_out, NULL, len, flags);

// sendfile: 直接在文件描述符间传输
sendfile(fd_out, fd_in, &offset, count);

splice vs tee:

1
2
3
4
5
6
// splice: 移动数据
splice(pipe1_read, NULL, pipe2_write, NULL, len, flags);

// tee: 复制数据(不消耗源数据)
tee(pipe1_read, pipe2_write, len, flags);

常见使用场景

1. 网络代理:

1
2
3
4
// 在代理服务器中高效转发数据
splice(client_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, target_fd, NULL, len, SPLICE_F_MOVE);

2. 文件复制:

1
2
3
4
// 高效的文件复制操作
splice(src_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, dst_fd, NULL, len, SPLICE_F_MOVE);

3. 日志处理:

1
2
3
4
// 实时日志转发和处理
splice(log_fd, NULL, pipe_write, NULL, len, SPLICE_F_MORE);
splice(pipe_read, NULL, network_fd, NULL, len, SPLICE_F_MOVE);

总结

splice 是Linux系统中强大的零拷贝I/O操作函数,提供了:

高效传输: 避免用户空间和内核空间的数据拷贝

灵活使用: 支持多种文件描述符类型

性能优化: 显著提高大文件和网络传输性能

系统集成: 与Linux内核深度集成

通过合理使用 splice,可以构建高性能的数据传输应用。在实际应用中,需要注意系统要求、错误处理和性能优化等关键问题。

data-ad-format="auto" data-full-width-responsive="true">