目录
1. 简述
在Linux系统中,零拷贝(Zero-Copy)技术是一种高效的数据传输技术,它允许数据直接在发送方和接收方之间传输,无需在内核和用户空间之间复制数据。这种技术可以显著提高文件传输、网络通信等操作的性能。
2. 实现原理
传统的数据传输方式通常涉及多次数据复制操作:
(1)从磁盘读取数据到内核缓冲区。
(2)从内核缓冲区复制到用户空间缓冲区。
(3)从用户空间缓冲区复制到网络缓冲区。
零拷贝技术通过减少或消除这些复制步骤来提高效率。在零拷贝模型中,数据直接从内核缓冲区传输到目标(如网络栈或另一个文件描述符)。
3. Linux的零拷贝实现
sendfile函数是Linux内核提供的一个系统调用,用于实现高效的文件传输。它允许将文件数据直接发送到套接字,无需在用户空间和内核空间之间复制数据。
其原型如下。
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
其中,
out_fd:输出文件描述符,即套接字描述符。
in_fd:输入文件描述符,即要发送的文件描述符。
offset:指向off_t类型的指针,指定从文件的哪个位置开始发送数据。如果为NULL,则从当前文件位置开始发送。
count:要发送的数据量,以字节为单位。
返回值:
成功时,sendfile返回已发送的字节数。
出错时,返回-1,并设置errno以指示错误。
4. 例程
#include <iostream>
#include <fstream>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <sys/sendfile.h>
int main(int argc, char* argv[])
{
/** 服务器地址和端口. */
const char* server_ip = "127.0.0.1";
int port = 3333;
/** 创建套接字. */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1){
perror("create socket failed!");
return 1;
}
/** 设置服务器地址. */
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
if (inet_pton(AF_INET, server_ip, &serv_addr.sin_addr) <= 0){
perror("inet_pton failed!");
close(sockfd);
return 1;
}
/** 连接到服务器. */
if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){
perror("connect failed!");
close(sockfd);
return 1;
}
/** 打开要发送的文件. */
const char* filename = "example.dat";
int file_fd = open(filename, O_RDONLY);
if (file_fd == -1) {
perror("open file failed!");
close(sockfd);
return 1;
}
/** 使用 sendfile 发送文件. */
off_t offset = 0;
ssize_t result;
while ((result = sendfile(sockfd, file_fd, &offset, 4096)) > 0){
std::cout << "Sent " << result << " bytes\n";
}
if (result == -1){
perror("sendfile failed!");
}
/** 关闭文件描述符. */
close(file_fd);
/** 关闭套接字. */
close(sockfd);
return 0;
}
5. 特别说明
sendfile 函数设计的初衷是用于将文件数据直接发送到网络套接字,从而实现高效的数据传输,减少数据复制的开销。在传统的使用场景中,sendfile 主要用于以下两种情况:
(1)将一个文件的内容发送到一个网络套接字(如TCP套接字)。
(2)在两个文件描述符之间传输数据,其中一个通常是文件描述符,另一个可以是管道(pipe)或其他类型的文件描述符。
sendfile 的一般用法
在 Linux 中,sendfile 函数通常用于将一个文件描述符(in_fd)中的数据发送到另一个文件描述符(out_fd)。这里的 out_fd 不一定非得是网络套接字,它也可以是其他类型的文件描述符,只要它支持 write 操作。
用在任意文件描述符之间
在某些Linux发行版中,sendfile 函数可以用于任意两个文件描述符之间的数据传输,只要它们都支持 splice 系统调用。splice 是一个用于在两个文件描述符之间传输数据的系统调用,它支持零拷贝操作。从 Linux 内核 2.6.17 开始,splice 函数被用来在内部实现 sendfile。
sendfile 的行为可能会因不同的Linux发行版和内核版本而异。在某些系统上,sendfile 可能只支持文件到套接字的传输。