日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux网络编程--sendfile零拷贝高效率发送文件

發(fā)布時(shí)間:2025/3/15 linux 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux网络编程--sendfile零拷贝高效率发送文件 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本博文介紹使用sendfile函數(shù)進(jìn)行零拷貝發(fā)送文件,實(shí)現(xiàn)高效數(shù)據(jù)傳輸,并對(duì)其進(jìn)行驗(yàn)證。

那么什么是sendfile呢?

linux系統(tǒng)使用man sendfile,查看sendfile原型如下:

#include <sys/sendfile.h>

? ? ? ?ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

參數(shù)特別注意的是:in_fd必須是一個(gè)支持mmap函數(shù)的文件描述符,也就是說(shuō)必須指向真實(shí)文件,不能使socket描述符和管道。

out_fd必須是一個(gè)socket描述符。

由此可見(jiàn)sendfile幾乎是專門為在網(wǎng)絡(luò)上傳輸文件而設(shè)計(jì)的。

Sendfile 函數(shù)在兩個(gè)文件描述符之間直接傳遞數(shù)據(jù)(完全在內(nèi)核中操作,傳送),從而避免了內(nèi)核緩沖區(qū)數(shù)據(jù)和用戶緩沖區(qū)數(shù)據(jù)之間的拷貝,操作效率很高,被稱之為零拷貝。

傳統(tǒng)方式read/write send/recv?
在傳統(tǒng)的文件傳輸里面(read/write方式),在實(shí)現(xiàn)上其實(shí)是比較復(fù)雜的,需要經(jīng)過(guò)多次上下文的切換,我們看一下如下兩行代碼: ? ?
1. read(file, tmp_buf, len); ? ? ? ?

2. write(socket, tmp_buf, len); ??

以上兩行代碼是傳統(tǒng)的read/write方式進(jìn)行文件到socket的傳輸。 ? ?

當(dāng)需要對(duì)一個(gè)文件進(jìn)行傳輸?shù)臅r(shí)候,其具體流程細(xì)節(jié)如下: ??

1、調(diào)用read函數(shù),文件數(shù)據(jù)被copy到內(nèi)核緩沖區(qū) ?

2、read函數(shù)返回,文件數(shù)據(jù)從內(nèi)核緩沖區(qū)copy到用戶緩沖區(qū)?

3、write函數(shù)調(diào)用,將文件數(shù)據(jù)從用戶緩沖區(qū)copy到內(nèi)核與socket相關(guān)的緩沖區(qū)。

?4、數(shù)據(jù)從socket緩沖區(qū)copy到相關(guān)協(xié)議引擎。 ? ?

以上細(xì)節(jié)是傳統(tǒng)read/write方式進(jìn)行網(wǎng)絡(luò)文件傳輸?shù)姆绞?#xff0c;我們可以看到,

在這個(gè)過(guò)程當(dāng)中,文件數(shù)據(jù)實(shí)際上是經(jīng)過(guò)了四次copy操作: ? ?硬盤—>內(nèi)核buf—>用戶buf—>socket相關(guān)緩沖區(qū)(內(nèi)核)—>協(xié)議引擎


新方式sendfile ?

sendfile系統(tǒng)調(diào)用則提供了一種減少以上多次copy,提升文件傳輸性能的方法。

1、系統(tǒng)調(diào)用 sendfile() 通過(guò) DMA 把硬盤數(shù)據(jù)拷貝到 kernel buffer,然后數(shù)據(jù)被 kernel 直接拷貝到另外一個(gè)與 socket 相關(guān)的 kernel buffer。這里沒(méi)有 user mode 和 kernel mode 之間的切換,在 kernel 中直接完成了從一個(gè) buffer 到另一個(gè) buffer 的拷貝。
2、DMA 把數(shù)據(jù)從 kernel buffer 直接拷貝給協(xié)議棧,沒(méi)有切換,也不需要數(shù)據(jù)從 user mode 拷貝到 kernel mode,因?yàn)閿?shù)據(jù)就在 kernel 里。


服務(wù)端:

[cpp]?view plaincopy print?
  • #include?<sys/socket.h>??
  • #include?<netinet/in.h>??
  • #include?<arpa/inet.h>??
  • #include?<assert.h>??
  • #include?<stdio.h>??
  • #include?<unistd.h>??
  • #include?<stdlib.h>??
  • #include?<errno.h>??
  • #include?<string.h>??
  • #include?<sys/types.h>??
  • #include?<sys/stat.h>??
  • #include?<fcntl.h>??
  • #include?<sys/sendfile.h>??
  • ??
  • int?main(?int?argc,?char*?argv[]?)??
  • {??
  • ????if(?argc?<=?3?)??
  • ????{??
  • ????????printf(?"usage:?%s?ip_address?port_number?filename\n",?basename(?argv[0]?)?);??
  • ????????return?1;??
  • ????}??
  • ????longnum=0,sum=0;??
  • ????static?char?buf[1024];??
  • ????memset(buf,'\0',sizeof(buf));??
  • ????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?);??
  • ??????????
  • ????????FILE?*fp=fdopen(filefd,"r");??
  • ??????????
  • ????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??
  • ????{??
  • ????????time_t?begintime=time(NULL);??
  • ??????????
  • ????????while((fgets(buf,1024,fp))!=NULL){??
  • ????????????num=send(connfd,buf,sizeof(buf),0);??
  • ????????????sum+=num;??
  • ????????????memset(buf,'\0',sizeof(buf));??
  • ????????}??
  • ??????????
  • //????????sendfile(?connfd,?filefd,?NULL,?stat_buf.st_size?);??
  • ????????time_t?endtime=time(NULL);??
  • ????????printf("sum:%ld\n",sum);??
  • ????????printf("need?time:%d\n",endtime-begintime);??
  • ????????close(?connfd?);??
  • ????}??
  • ??
  • ????close(?sock?);??
  • ????return?0;??
  • }??

  • 客戶端:

    [cpp]?view plaincopy print?
  • #include?<sys/socket.h>??
  • #include?<netinet/in.h>??
  • #include?<arpa/inet.h>??
  • #include?<assert.h>??
  • #include?<stdio.h>??
  • #include?<unistd.h>??
  • #include?<stdlib.h>??
  • #include?<errno.h>??
  • #include?<string.h>??
  • #include?<sys/types.h>??
  • #include?<sys/stat.h>??
  • #include?<fcntl.h>??
  • #include?<sys/sendfile.h>??
  • ??
  • int?main(?int?argc,?char*?argv[]?)??
  • {??
  • ????if(?argc?<=?3?)??
  • ????{??
  • ????????printf(?"usage:?%s?ip_address?port_number?filename\n",?basename(?argv[0]?)?);??
  • ????????return?1;??
  • ????}??
  • ????static?char?buf[1024];??
  • ????memset(buf,'\0',sizeof(buf));??
  • ????const?char*?ip?=?argv[1];??
  • ????int?port?=?atoi(?argv[2]?);??
  • ????const?char*?file_name?=?argv[3];??
  • ??
  • ????int?filefd?=?open(?file_name,?O_WRONLY?);??
  • ????if(filefd<=0)??
  • ????????printf("open?error:%s",strerror(errno));??
  • ????assert(?filefd?>?0?);??
  • ??????????
  • ????????FILE?*fp=fdopen(filefd,"w");??
  • ??????????
  • ????struct?sockaddr_in?address;??
  • ????socklen_t?len=sizeof(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?num;??
  • ????????int?ret=connect(sock,(struct?sockaddr*)&address,len);??
  • ????if?(?ret?<?0?)??
  • ????{??
  • ????????printf(?"connect?errno:?%s\n",?strerror(errno)?);??
  • ????}??
  • ????else??
  • ????{??
  • ????????while(?(num=recv(sock,buf,sizeof(buf),0))>0?){??
  • ????????????fprintf(fp,"%s",buf);??
  • ????????????memset(buf,'\0',sizeof(buf));??
  • ????????}??
  • ??????????
  • ????????close(?sock?);??
  • ????}??
  • ??
  • ????close(?sock?);??
  • ????return?0;??
  • }??

  • 測(cè)試環(huán)境:linux?Ubuntu 32位系統(tǒng)?CPU?Intel i5-4258U ?@ 2.40GHz *4 ?內(nèi)存2G



    根據(jù)以上對(duì)比,使用sendfile的系統(tǒng)負(fù)載要低一些,cpu使用率要低很多,整體速度和send基本差不多,估計(jì)是當(dāng)今系統(tǒng)計(jì)算速度太快,看不出什么明顯區(qū)別。不過(guò)對(duì)比web服務(wù)器的話區(qū)別還是很大的。

    總結(jié)

    以上是生活随笔為你收集整理的Linux网络编程--sendfile零拷贝高效率发送文件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。