zoukankan      html  css  js  c++  java
  • sendfile函数--零拷贝(转)

    零拷贝:零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除通信数据在存储器之间不必要的中间拷贝过程,有效地提高通信效率,是设计高速接口通道、实现高速服务器和路由器的关键技术之一。
    sendfile

    #include <sys/sendfile.h>
    ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);
    1
    2
    参数特别注意的是:in_fd必须是一个支持mmap函数的文件描述符,也就是说必须指向真实文件,不能使socket描述符和管道。
    out_fd必须是一个socket描述符.
    由此可见sendfile几乎是专门为在网络上传输文件而设计的。
    out_fd:已经打开了,用于写操作的文件描述符
    in_fd:已经打开了,用于读操作的文件描述符
    offset:偏移量:表示sendfile函数从in_fd中的哪一偏移量开始读取数据,如果是0表示从文件的开始读,否则从相应的偏移量读取,如果是循环读取的时候,下一次offset值应为sendfile函数返回值加上本次的offset的值。
    count:是在两个描述符之间拷贝的字节数
    返回值:
    如果成功的拷贝,返回写操作到out_fd的字节数,错误返回-1,并相应的设置error信息。

    关于sendfile与read和write的比较
    服务器响应一个http请求的步骤如下:
    1.把磁盘文件读入内核缓冲区
    2.从内核缓冲区读到内存
    3.处理(静态资源不需要处理)
    4.发送到网卡的内核缓冲区(发送缓存)
    5.网卡发送数据
    而sendfile系统调用,省略了2,3步,磁盘文件被直接发送到了网卡的内存缓冲区,减少了数据复制和内核态切换的开销。
    sendfile一直都在核心态进行

    普通的read和write的传统网络传输过程的步骤

    read(file, tmp_buf, len);
    write(socket, tmp_buf, len);
    硬盘 >> kernel buffer >> user buffer >> kernel socket buffer >> 协议栈
    1
    2
    3
    一般的网络应用是通过读硬盘数据,然后写数据到socket来完成网络传输的。
    底层实现如下:
    1.系统调用read()产生一个上下文切换:从用户模式切换到内核模式,然后DMA(直接内存存取)执行拷贝,把文件数据从硬盘读到一个内核缓冲区里面去。
    2.数据从内核缓冲区拷贝到用户态缓冲区,然后系统调用read()返回,这时又产生一个上下文切换:从内核状态切换到用户态。
    3.系统调用write()产生一个上下文切换:从用户态切换到内核态,然后把步骤2中读到用户缓冲区的数据拷贝到核心态缓冲区(数据第二次拷贝到核心态缓冲区),不过这次是个不同的核心态缓冲区,这个缓冲区和socket相关联。
    4.系统调用write()返回,产生一个上下文切换:从内核态切换到用户态(第4次切换),然后DMA从内核缓冲区拷贝数据到协议栈(第四次拷贝)。

    上述4个步骤,4次上下文切换,4次拷贝,我们发现减少他的切换和拷贝次数,将有效提高性能。这也是sendfile提高性能的方法。

    关于sendfile进行网络传输的过程

    sendfile(socket, file, len);
    硬盘 >> kernel buffer (快速拷贝到kernel socket buffer) >> 协议栈
    1
    2
    1.系统调用sendfile()通过DMA把硬盘数据拷贝到内核缓冲区,然后数据直接拷贝到另一个与socket相关的内核缓冲区。(区别)这里没有用户态和核心态之间的切换,在核心态中直接完成了从一个缓冲区到另一个缓冲区的拷贝。
    2.DMA把数据从内核缓冲区直接拷贝给协议栈,没有切换,也不需要数据从用户态拷贝到核心态,因为数据就在内核里面。
    由此比较,sendfile远比read和write方式在进行数据拷贝时高效。

    关于sendfile的应用代码–代码作用是把a文件(和客户端程序在同一目录下)传递给服务器端
    服务端

    #include <netinet/in.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <assert.h>
    int main(int argc,char* argv[])
    {
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);

    struct sockaddr_in saddr,caddr;

    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6500);
    saddr.sin_addr.s_addr = inet_addr("192.168.1.11");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);

    listen(sockfd,5);

    int len = sizeof(caddr);
    int c = accept(sockfd,(struct sockaddr*)&caddr,&len);

    while(1)
    {
    if(c<0)
    {
    continue;
    }
    char buff[128] = {0};
    recv(c,buff,127,0);
    printf("%s",buff);
    }
    close(c);
    return 0;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    客户端

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

    int main(int argc,char* argv[])
    {
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6500);
    saddr.sin_addr.s_addr = inet_addr("192.168.1.11");

    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);

    int fd1 = open("./a",O_RDONLY);
    int len = 1;
    while(len)
    {
    len = sendfile(sockfd,fd1,0,1024);
    if(len==0)
    {
    break;
    }
    printf("发出了%d个字节 ",len);
    }
    close(sockfd);
    return 0;

    }

  • 相关阅读:
    《C# to IL》第一章 IL入门
    multiple users to one ec2 instance setup
    Route53 health check与 Cloudwatch alarm 没法绑定
    rsync aws ec2 pem
    通过jvm 查看死锁
    wait, notify 使用清晰讲解
    for aws associate exam
    docker 容器不能联网
    本地运行aws lambda credential 配置 (missing credential config error)
    Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?
  • 原文地址:https://www.cnblogs.com/wangbin/p/9923043.html
Copyright © 2011-2022 走看看