初探sendfile

Posted 夏天的技术博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初探sendfile相关的知识,希望对你有一定的参考价值。

很早就知道sendfile这个专门用来传输大文件的函数,也称为零拷贝,但一直没测试过,今天用了宿舍的网和小组内的网测试了下,发现结果和我预想的不一样。


为什么效率高,网上说的也很多了,看下man手册中的内容
sendfile() copies data between one file descriptor and another. Because this copying is done within the kernel, sendfile() is more efficient than the combination of read(2) and write(2), which would require transferring data to and from user space.
普通的read和write先会将数据拷贝到用户空间,然后再拷贝到内核空间,然后从tcp缓冲区发送出去。sendfile避免了多余的拷贝。


测试过程:

通过hdparm -Tt /dev/sda
测试了我的磁盘IO大约是110MB/sec

通过bmon
宿舍网大概150k/s,小组内网大概4.5~5.5MB/s。
只是测试了个大概。参考而已

服务端跑在学长借我的服务器上测试。


数据有限,我测试的分别为7.5M,90M,2G的数据。
起初宿舍网络有点慢,read,write版本和sendfile版本运行出来的时间都几乎差不多。然后我在小组测试网速4.5~5.5MB/s,跑出来的数据竟然也差不多,有时read,write版本竟然比sendfile版本的时间短
很奇怪,最后一想,磁盘IO的性能依然是远大于网络IO的性能,我测试的两种网络带宽远远不够。


测试结论:

最后询问了学长,得知4.5mb/s-5.5mb/s也就是带宽为36-44mib/s的网络IO是远远不够的,用ifconfig查看本机ip来测试,本机千兆网卡测试出来传送2.2G数据sendfile效率的确高于read,write。
那么像我们普通这种网络带宽根本不能满足且突出sendfile的性能。


测试代码:客户端

客户端两个版本,一个为read,write,一个为sendfile,修改注释即可

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

int main(int argc, char *argv[])

    if(argc < 3)
        printf("argument error\\n");
        exit(1);
    
    char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    inet_pton(AF_INET, ip, &server.sin_addr);

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
        printf("create socket error\\n");
        exit(1);
    
    int con_ret = connect(fd, (struct sockaddr*)&server, sizeof(server));
    if(con_ret == -1)
        printf("connect error\\n");
        exit(1);
    
    char *name = argv[3];
    int r_fd = open(name, O_RDONLY);
    if(r_fd == -1)
        printf("open file error\\n");
        exit(1);
    
    struct stat st;
    long int size = stat(name, &st);
    if(size < 0)
        printf("file stat error\\n");
        exit(1);
    
    printf("file size:%ld\\n", st.st_size);
    off_t pos = lseek(r_fd, 0, SEEK_SET);
    if(pos < 0)
        printf("obtain fileP error\\n");
        exit(1);
    
    char *cname = "newfile";
    int sn = send(fd, cname, sizeof(argv[3]), 0);
    struct timeval startTime, endTime;
    double timeuse;
    gettimeofday(&startTime, NULL);
    //sendfile测试版本
    //int n = sendfile(fd, r_fd, &pos, st.st_size);
    long int ssize = 0;
    char buffer[2048];
    long int n = 0;
    //read,write测试版本
    while(1)
        bzero(buffer, 2048);
        int rn = read(r_fd, buffer, 2048);
        int wn = write(fd, buffer, 2048);
        if(n >= st.st_size)
            printf("size:%ld\\n", n);
            printf("size:%ld\\n", st.st_size);
            break;
        
        n += rn;
    
    gettimeofday(&endTime, NULL);
    timeuse = 1000000*(endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec);
    timeuse /= 1000000;
    printf("timeuse = %lf\\n", timeuse);
    if(n == -1)
        printf("send file error\\n");
        exit(1);
    

    return EXIT_SUCCESS;

测试代码:服务器

服务端测试代码不变

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])

    if(argc < 2)
        printf("argument error\\n");
        exit(1);
    
    char *ip = argv[1];
    int port = atoi(argv[2]);

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    inet_pton(AF_INET, ip, &server.sin_addr);

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
        printf("create socket error\\n");
        exit(1);
    
    int b_ret = bind(fd, (struct sockaddr*)&server, sizeof(server));
    if(b_ret == -1)
        printf("bind error\\n");
        exit(1);
    
    int l_ret = listen(fd, 64);
    if(l_ret == -1)
        printf("listen error\\n");
        exit(1);
    
    socklen_t len = sizeof(server);
    int sockfd = accept(fd, (struct sockaddr*)&server, &len);
    if(sockfd == -1)
        printf("accept error\\n");
        exit(1);
    else
        printf("connect success\\n");
    
    char name[128];
    char buffer[2048];
    bzero(name, 128);
    bzero(buffer, 2048);
    int n = recv(sockfd, name, 2048, 0);
    if(n <= 0)
        printf("recv error\\n");
        exit(1);
    
    printf("name:%s\\n", name);
    int w_fd = open(name, O_WRONLY | O_CREAT, 777);
    if(w_fd == -1)
        printf("open file error\\n");
        exit(1);
    
    off_t pos = lseek(w_fd, 0, SEEK_CUR);
    if(pos < 0)
        printf("obtain file pointer error\\n");
        exit(1);
    
    while(1)
        bzero(buffer, 2048);
        int n = recv(sockfd, buffer, 2048, 0);
        if(n < 0)
            printf("recv error\\n");
            exit(1);
        else if(n == 0)
            break;
        
        ssize_t s_t = write(w_fd, buffer, n);
        if(s_t < 0)
            printf("write error\\n");
            exit(1);
        
    
    printf("recv file success!!!\\n");


    return EXIT_SUCCESS;

以上是关于初探sendfile的主要内容,如果未能解决你的问题,请参考以下文章

对各小组的建议汇总

小组项目工作分配,任务确认

7-4小组日报

DeepMind背后的人工智能:深度学习原理初探

nginx (linux)sendfile 参数解释

nginx (linux)sendfile 参数解释