splice 函数详解
1. 函数介绍
splice
是Linux系统调用,用于在两个文件描述符之间高效地移动数据,而无需将数据复制到用户空间。它是Linux特有的零拷贝I/O操作,特别适用于管道、套接字和文件之间的数据传输,能够显著提高I/O性能。
2. 函数原型
#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);
3. 功能
splice
在两个文件描述符之间直接传输数据,避免了用户空间和内核空间之间的数据拷贝。它利用内核中的管道缓冲区机制,实现了高效的零拷贝数据传输。
4. 参数
- int fd_in: 源文件描述符
- *loff_t off_in: 源文件偏移量指针(NULL表示使用当前文件位置)
- int fd_out: 目标文件描述符
- *loff_t off_out: 目标文件偏移量指针(NULL表示使用当前文件位置)
- size_t len: 要传输的数据长度
- unsigned int flags: 控制标志
5. 返回值
- 成功: 返回实际传输的字节数
- 失败: 返回-1,并设置errno
6. 相似函数,或关联函数
- tee: 在两个管道之间复制数据
- vmsplice: 将用户空间数据写入管道
- sendfile: 在文件描述符之间传输数据
- copy_file_range: 复制文件数据
7. 示例代码
示例1:基础splice使用
#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[2];
int input_fd, output_fd;
const char *input_file = "input.txt";
const char *output_file = "output.txt";
char test_data[] = "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[0], pipefd[1]);
// 打开输入文件用于读取
input_fd = open(input_file, O_RDONLY);
if (input_fd == -1) {
perror("打开输入文件失败");
close(pipefd[0]);
close(pipefd[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[0]);
close(pipefd[1]);
unlink(input_file);
return -1;
}
printf("开始splice操作:\n");
// 第一步:从文件读取到管道(使用SPLICE_F_MOVE标志)
bytes_transferred = splice(input_fd, NULL, pipefd[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[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[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[read_bytes] = '\0';
printf("输出文件内容: %s", buffer);
}
close(output_read_fd);
}
cleanup:
close(input_fd);
close(output_fd);
close(pipefd[0]);
close(pipefd[1]);
unlink(input_file);
unlink(output_file);
return 0;
}
int main() {
return demo_splice_basic();
}
示例2:管道到套接字的数据传输
#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[2];
char buffer[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[1], test_data, strlen(test_data));
if (bytes_written == -1) {
perror("写入管道失败");
close(pipefd[0]);
close(pipefd[1]);
close(client_fd);
close(server_fd);
return NULL;
}
printf("写入管道 %zd 字节数据\n", bytes_written);
// 使用splice将管道数据传输到套接字
ssize_t bytes_transferred = splice(pipefd[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[0]);
close(pipefd[1]);
close(client_fd);
close(server_fd);
printf("服务器线程结束\n");
return NULL;
}
/**
* 客户端函数
*/
int client_function() {
int client_fd;
struct sockaddr_in server_addr;
char buffer[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[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:文件到文件的零拷贝传输
#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[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[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[1], NULL,
to_transfer, SPLICE_F_MOVE | SPLICE_F_MORE);
if (bytes_transferred == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue; // 重试
}
perror("splice读取失败");
close(pipefd[0]);
close(pipefd[1]);
return -1;
}
if (bytes_transferred == 0) {
break; // 文件结束
}
// 从管道写入到目标文件
ssize_t bytes_written = splice(pipefd[0], NULL, dst_fd, NULL,
bytes_transferred, SPLICE_F_MOVE);
if (bytes_written == -1) {
perror("splice写入失败");
close(pipefd[0]);
close(pipefd[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[0]);
close(pipefd[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:网络代理服务器
#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[2];
int pipe_to_client[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[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[0].fd = conn->client_fd;
fds[0].events = POLLIN;
fds[1].fd = conn->target_fd;
fds[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[0].revents & POLLIN) {
bytes_transferred = splice(conn->client_fd, NULL, conn->pipe_to_target[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[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[1].revents & POLLIN) {
bytes_transferred = splice(conn->target_fd, NULL, conn->pipe_to_client[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[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[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
printf("客户端连接异常\n");
break;
}
if (fds[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[0]);
close(conn.pipe_to_target[1]);
close(conn.pipe_to_client[0]);
close(conn.pipe_to_client[1]);
close(client_fd);
close(target_fd);
close(server_fd);
printf("代理服务器演示完成\n");
return 0;
}
int main() {
return demo_proxy_server();
}
示例5:splice性能优化和最佳实践
#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[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[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[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[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[0]);
close(pipefd[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[] = {4096, 16384, 65536, 262144, 1048576}; // 4KB到1MB
const int num_chunk_sizes = sizeof(chunk_sizes) / sizeof(chunk_sizes[0]);
printf("=== splice性能优化和最佳实践演示 ===\n");
// 检查系统是否支持splice
printf("检查splice支持:\n");
int test_pipe[2];
if (pipe(test_pipe) == 0) {
printf(" ✓ 系统支持管道操作\n");
close(test_pipe[0]);
close(test_pipe[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[256];
snprintf(dst_file, sizeof(dst_file), "splice_chunk_%zu.dat", chunk_sizes[i]);
double elapsed = test_splice_performance(src_file, dst_file, chunk_sizes[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[i], elapsed, speed, "N/A");
}
unlink(dst_file);
}
// 测试传统方法性能(使用不同缓冲区大小)
const size_t buffer_sizes[] = {4096, 16384, 65536, 262144};
const int num_buffer_sizes = sizeof(buffer_sizes) / sizeof(buffer_sizes[0]);
for (int i = 0; i < num_buffer_sizes; i++) {
char dst_file[256];
snprintf(dst_file, sizeof(dst_file), "traditional_buf_%zu.dat", buffer_sizes[i]);
double elapsed = test_traditional_performance(src_file, dst_file, buffer_sizes[i]);
if (elapsed > 0) {
double speed = file_size / (1024.0 * 1024.0) / elapsed;
printf("%-12s %-12zu %-15.3f %-15.2f %-10s\n",
"传统方法", buffer_sizes[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或更高版本
- 架构支持: 支持所有主流架构
- 编译选项: 需要定义_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:
// 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:
// splice: 移动数据
splice(pipe1_read, NULL, pipe2_write, NULL, len, flags);
// tee: 复制数据(不消耗源数据)
tee(pipe1_read, pipe2_write, len, flags);
常见使用场景
1. 网络代理:
// 在代理服务器中高效转发数据
splice(client_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, target_fd, NULL, len, SPLICE_F_MOVE);
2. 文件复制:
// 高效的文件复制操作
splice(src_fd, NULL, pipe_write, NULL, len, SPLICE_F_MOVE);
splice(pipe_read, NULL, dst_fd, NULL, len, SPLICE_F_MOVE);
3. 日志处理:
// 实时日志转发和处理
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
,可以构建高性能的数据传输应用。在实际应用中,需要注意系统要求、错误处理和性能优化等关键问题。