splice系统调用及示例

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 使用注意事项

系统要求:

  1. 内核版本: 需要Linux 2.6.17或更高版本
  2. 架构支持: 支持所有主流架构
  3. 编译选项: 需要定义_GNU_SOURCE

文件描述符要求:

  1. 管道支持: 至少一个文件描述符必须是管道
  2. 类型限制: 某些文件类型可能不支持splice
  3. 权限要求: 需要适当的文件访问权限

标志参数:

  1. SPLICE_F_MOVE: 尝试移动页面而不是复制
  2. SPLICE_F_NONBLOCK: 非阻塞操作
  3. SPLICE_F_MORE: 提示还有更多数据
  4. SPLICE_F_GIFT: 释放用户页面给内核

错误处理:

  1. EINVAL: 参数无效
  2. ENOMEM: 内存不足
  3. ESPIPE: 文件描述符不支持lseek
  4. EAGAIN: 非阻塞操作时资源不可用

性能考虑:

  1. 块大小: 选择合适的传输块大小
  2. 管道大小: 调整管道缓冲区大小
  3. 系统调用: 减少不必要的系统调用
  4. 内存对齐: 考虑内存对齐优化

安全考虑:

  1. 权限检查: 确保有适当的文件访问权限
  2. 资源限制: 避免消耗过多系统资源
  3. 错误恢复: 妥善处理错误情况

最佳实践:

  1. 适当使用: 在合适的场景下使用splice
  2. 性能测试: 在实际环境中测试性能
  3. 错误处理: 完善的错误处理机制
  4. 资源管理: 及时释放分配的资源

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操作函数,提供了:

  1. 高效传输: 避免用户空间和内核空间的数据拷贝
  2. 灵活使用: 支持多种文件描述符类型
  3. 性能优化: 显著提高大文件和网络传输性能
  4. 系统集成: 与Linux内核深度集成

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

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注