生活随笔
收集整理的這篇文章主要介紹了
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));?? ????????}?? ?????????? ?? ????????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ò),歡迎將生活随笔推薦給好友。