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;
}