c++服务器学习从零开始:第二期使用linux的splice和tee高效传输数据(附带源码)

splice和tee都是零拷贝操作直接在内核操作没有内核和用户缓冲区的数据拷贝

splice的服务器源码

/* 包含网络编程所需头文件 */
#include <sys/socket.h>     // 套接字相关系统调用
#include <netinet/in.h>     // IPv4地址结构体定义
#include <arpa/inet.h>      // IP地址转换函数
#include <assert.h>         // 断言宏
#include <stdio.h>          // 标准输入输出
#include <unistd.h>         // POSIX系统调用(close等)
#include <stdlib.h>         // 系统函数(atoi等)
#include <errno.h>          // 错误码定义
#include <string.h>         // 字符串操作
#include <fcntl.h>          // 文件控制选项

int main(int argc, char* argv[])
{
    /* 参数校验:需要至少2个参数(程序名+IP+端口) */
    if(argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }

    /* 解析命令行参数 */
    const char* ip = argv[1];        // 绑定的IP地址
    int port = atoi(argv[2]);        // 绑定的端口号

    /* 初始化服务器地址结构 */
    struct sockaddr_in address;
    bzero(&address, sizeof(address));            // 清空结构体
    address.sin_family = AF_INET;                 // 使用IPv4协议
    inet_pton(AF_INET, ip, &address.sin_addr);    // 转换字符串IP为二进制格式
    address.sin_port = htons(port);               // 端口号转网络字节序

    /* 创建TCP套接字 */
    int sock = socket(PF_INET, SOCK_STREAM, 0);   // PF_INET=IPv4, SOCK_STREAM=TCP
    assert(sock >= 0);                            // 确保套接字创建成功

    /* 绑定套接字到指定地址 */
    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);  // 绑定失败时终止程序

    /* 开始监听连接请求 */
    ret = listen(sock, 5);  // 监听队列最大长度为5
    assert(ret != -1);

    /* 接受客户端连接 */
    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof(client);
    int connfd = accept(sock, (struct sockaddr*)&client, &client_addrlength);
    
    if(connfd < 0)
    {
        printf("accept error: %d\n", errno);  // 显示错误码
    }
    else
    {
        /* 创建匿名管道用于零拷贝数据传输 */
        int pipefd[2];                     // pipefd[0]读端,pipefd[1]写端
        ret = pipe(pipefd);                // 创建管道
        assert(ret != -1);

        /* 使用splice实现零拷贝双向转发(内核空间直接传输数据)*/
        // 第一阶段:从客户端套接字读取数据到管道写端
        ret = splice(
            connfd,                     // 输入文件描述符(客户端套接字)
            NULL,                       // 输入偏移量(保持当前偏移)
            pipefd[1],                  // 输出文件描述符(管道写端)
            NULL,                       // 输出偏移量
            32768,                      // 最大传输字节数(32KB)
            SPLICE_F_MORE | SPLICE_F_MOVE // 标志位:提示更多数据/尝试移动页面
        );
        assert(ret != -1);

        /* 第二阶段:从管道读端写回客户端套接字 */
        ret = splice(
            pipefd[0],                  // 输入文件描述符(管道读端)
            NULL,
            connfd,                     // 输出文件描述符(客户端套接字)
            NULL,
            32768,
            SPLICE_F_MORE | SPLICE_F_MOVE
        );
        assert(ret != -1);

        close(connfd);  // 关闭客户端连接
    }

    close(sock);    // 关闭监听套接字
    return 0;
}

tee的服务器源码

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char* argv[])
{
    /* 参数验证:必须指定输出文件名 */
    if (argc != 2) {
        printf("usage: %s <file>\n", argv[0]);
        return 1;
    }

    /* 创建/截断目标文件(权限:rw-rw-rw-) */
    int filefd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666);
    assert(filefd > 0); // 确保文件成功打开

    /* 创建两个管道用于数据分流 */
    int pipefd_stdout[2]; // 管道1:用于标准输出
    int ret = pipe(pipefd_stdout);
    assert(ret != -1);

    int pipefd_file[2];   // 管道2:用于文件写入
    ret = pipe(pipefd_file);
    assert(ret != -1);

    /* 数据流转阶段(零拷贝操作)*/
    
    // 阶段1:将标准输入数据转移到管道1的写端
    // splice(fd_in, off_in, fd_out, off_out, len, flags)
    ret = splice(STDIN_FILENO, NULL,         // 输入:标准输入
                 pipefd_stdout[1], NULL,     // 输出:管道1写端
                 32768,                      // 传输大小:32KB
                 SPLICE_F_MORE | SPLICE_F_MOVE); // 标志:提示更多数据/尝试移动页面
    assert(ret != -1);

    // 阶段2:复制管道1数据到管道2(TEE操作)
    // tee(fd_in, fd_out, len, flags)
    ret = tee(pipefd_stdout[0],              // 输入:管道1读端
              pipefd_file[1],                // 输出:管道2写端
              32768,                         // 传输大小
              SPLICE_F_NONBLOCK);            // 非阻塞模式
    assert(ret != -1);

    // 阶段3:将管道2数据写入文件
    ret = splice(pipefd_file[0], NULL,      // 输入:管道2读端
                 filefd, NULL,              // 输出:目标文件
                 32768,                     
                 SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);

    // 阶段4:将管道1数据输出到标准输出
    ret = splice(pipefd_stdout[0], NULL,    // 输入:管道1读端
                 STDOUT_FILENO, NULL,       // 输出:标准输出
                 32768,
                 SPLICE_F_MORE | SPLICE_F_MOVE);
    assert(ret != -1);

    /* 资源清理 */
    close(filefd);
    close(pipefd_stdout[0]);
    close(pipefd_stdout[1]);
    close(pipefd_file[0]);
    close(pipefd_file[1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值