首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >初探sendfile「建议收藏」

初探sendfile「建议收藏」

作者头像
全栈程序员站长
发布2022-09-06 11:05:06
发布2022-09-06 11:05:06
41800
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

很早就知道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,修改注释即可
代码语言:javascript
代码运行次数:0
运行
复制
#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;
}

测试代码:服务器

服务端测试代码不变
代码语言:javascript
代码运行次数:0
运行
复制
#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;
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/134831.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年6月6,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 测试过程:
  • 测试结论:
  • 测试代码:客户端
    • 客户端两个版本,一个为read,write,一个为sendfile,修改注释即可
  • 测试代码:服务器
    • 服务端测试代码不变
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档