zoukankan      html  css  js  c++  java
  • 高级I/O函数之sendfile()函数

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

    #include<sys/sendfile.h>
    ssize_t sendfile(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

    sendfile系统调用函数体在fs/read_write.c文件中

    SYSCALL_DEFINE4(sendfile, int, out_fd, int, in_fd, off_t __user *, offset, size_t, count)
    {
    	loff_t pos;
    	off_t off;
    	ssize_t ret;
    
    	if (offset) {
    		if (unlikely(get_user(off, offset)))
    			return -EFAULT;
    		pos = off;
    		ret = do_sendfile(out_fd, in_fd, &pos, count, MAX_NON_LFS);
    		if (unlikely(put_user(pos, offset)))
    			return -EFAULT;
    		return ret;
    	}
    
    	return do_sendfile(out_fd, in_fd, NULL, count, 0);
    }
    

    首先我们来看看传统的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

    [参考]:https://blog.csdn.net/u014303647/article/details/82081451?spm=1035.2023.3001.6557&utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-2

  • 相关阅读:
    很实用的jQuery事件
    移动端touchstart,touchmove,touchend
    Django的学习(二)————Templates
    Django的学习(一)————初入django
    Tkinter添加图片
    HierSort(希尔)————Java
    Bubble(冒泡排序)————Java
    类+进程池的方法爬取喜马拉雅
    Ajax的爬取心得
    python中将两个数组压缩成一个数组
  • 原文地址:https://www.cnblogs.com/1dan/p/15650798.html
Copyright © 2011-2022 走看看