高级I/O函数之sendfile函数

sendfile函数在两个文件描述符之间传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,被称为零拷贝。函数定义为:

#include<sys/sendfile.h>
ssize_t senfile(int out_fd,int in_fd,off_t* offset,size_t count);

in_fd参数是待读出内容的文件描述符,out_fd参数是待写入内容的文件描述符。offset参数指定从读入文件流的哪个位置开始读,如果为空,则使用读入文件流默认的起始位置。count参数指定文件描述符in_fd和out_fd之间传输的字节数。

in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道,而out_fd必须是一个socket

首先我们来看看传统的read/write方式进行socket的传输。
当需要对一个文件进行传输的时候,具体流程细节如下:

1:调用read函数,文件数据copy到内核缓冲区
2:read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3:write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区
4:数据从socket缓冲区copy到相关协议引擎。

在这个过程中发生了四次copy操作。

硬盘->内核->用户->socket缓冲区(内核)->协议引擎。

而sendfile的工作原理呢??

1、系统调用 sendfile() 通过 DMA 把硬盘数据拷贝到 kernel buffer,然后数据被 kernel 直接拷贝到另外一个与 socket 相关的 kernel buffer。这里没有 用户态和核心态 之间的切换,在内核中直接完成了从一个 buffer 到另一个 buffer 的拷贝。
2、DMA 把数据从 kernel buffer 直接拷贝给协议栈,没有切换,也不需要数据从用户态和核心态,因为数据就在 kernel 里。

测试代码:

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<string.h>
#include<sys/sendfile.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<errno.h>

int main(int argc,char *argv[])
{
    if(argc<=3)
    {
        printf("usage:%s ip_address port_number filename\n",basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    const char* file_name = argv[3];

    int filefd = open(file_name,O_RDONLY);
    assert(filefd>0);

    struct stat stat_buf;
    fstat(filefd,&stat_buf);

    struct sockaddr_in address;

    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port = htons(port);

    int sock = socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);

    int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
    assert(ret!=-1);

    ret = listen(sock,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("errno is %d\n",errno);
    }
    else 
    {
        sendfile(connfd,filefd,NULL,stat_buf.st_size);
        close(connfd);
    }
    close(sock);
    return 0;
}

然后进行
这里写图片描述

在另外一个虚拟机上telnet

这里写图片描述

`sendfile()` 是一个在类 Unix 系统(如 Linux)中提供的系统调用,用于高效地将文件从一个文件描述符传输到另一个文件描述符,而不需要将数据复制到用户空间。这在传输大文件时非常高效,因为它减少了内核空间与用户空间之间的数据复制次数。 ### 函数原型(Linux 系统): ```c #include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); ``` ### 参数说明: - `out_fd`:写入数据的文件描述符(必须是套接字或管道等支持写入的描述符)。 - `in_fd`:读取数据的文件描述符(必须是一个指向文件的描述符)。 - `offset`:从文件的哪个偏移量开始读取;如果为 `NULL`,则从当前文件指针位置开始读取,并在传输后更新该指针。 - `count`:要传输的数据量(以字节为单位)。 ### 返回值: 成功时返回传输的字节数,出错时返回 -1。 ### 示例代码(发送文件到 socket): ```c #include <sys/sendfile.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> int main() { int file_fd = open("example.txt", O_RDONLY); int socket_fd = /* 假设这里是一个有效的 socket 描述符 */; off_t offset = 0; size_t count = 1024 * 1024; // 传输 1MB 数据 ssize_t sent = sendfile(socket_fd, file_fd, &offset, count); if (sent == -1) { perror("sendfile"); } else { printf("Sent %ld bytes\n", sent); } close(file_fd); // close(socket_fd); // 如果 socket 不再使用,也应关闭 return 0; } ``` ### 特点: - **高效性**:避免了用户空间和内核空间之间的数据拷贝。 - **适合大文件传输**:常用于 Web 服务器、文件传输服务等场景。 - **限制**:只能用于类 Unix 系统,且 `in_fd` 必须是文件描述符,不能是套接字。 ---
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值