本博文介紹使用sendfile函數進行零拷貝發送文件,實現高效數據傳輸,并對其進行驗證。
那么什么是sendfile呢?
linux系統使用man sendfile,查看sendfile原型如下:
#include <sys/sendfile.h>
? ? ? ?ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
參數特別注意的是:in_fd必須是一個支持mmap函數的文件描述符,也就是說必須指向真實文件,不能使socket描述符和管道。
out_fd必須是一個socket描述符。
由此可見sendfile幾乎是專門為在網絡上傳輸文件而設計的。
Sendfile 函數在兩個文件描述符之間直接傳遞數據(完全在內核中操作,傳送),從而避免了內核緩沖區數據和用戶緩沖區數據之間的拷貝,操作效率很高,被稱之為零拷貝。
傳統方式read/write send/recv?
在傳統的文件傳輸里面(read/write方式),在實現上其實是比較復雜的,需要經過多次上下文的切換,我們看一下如下兩行代碼: ? ?
1. read(file, tmp_buf, len); ? ? ? ?
2. write(socket, tmp_buf, len); ??
以上兩行代碼是傳統的read/write方式進行文件到socket的傳輸。 ? ?
當需要對一個文件進行傳輸的時候,其具體流程細節如下: ??
1、調用read函數,文件數據被copy到內核緩沖區 ?
2、read函數返回,文件數據從內核緩沖區copy到用戶緩沖區?
3、write函數調用,將文件數據從用戶緩沖區copy到內核與socket相關的緩沖區。
?4、數據從socket緩沖區copy到相關協議引擎。 ? ?
以上細節是傳統read/write方式進行網絡文件傳輸的方式,我們可以看到,
在這個過程當中,文件數據實際上是經過了四次copy操作: ? ?硬盤—>內核buf—>用戶buf—>socket相關緩沖區(內核)—>協議引擎
新方式sendfile ?
sendfile系統調用則提供了一種減少以上多次copy,提升文件傳輸性能的方法。
1、系統調用 sendfile() 通過 DMA 把硬盤數據拷貝到 kernel buffer,然后數據被 kernel 直接拷貝到另外一個與 socket 相關的 kernel buffer。這里沒有 user mode 和 kernel mode 之間的切換,在 kernel 中直接完成了從一個 buffer 到另一個 buffer 的拷貝。
2、DMA 把數據從 kernel buffer 直接拷貝給協議棧,沒有切換,也不需要數據從 user mode 拷貝到 kernel mode,因為數據就在 kernel 里。
服務端:
[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;?? }??
測試環境:linux?Ubuntu 32位系統?CPU?Intel i5-4258U ?@ 2.40GHz *4 ?內存2G
根據以上對比,使用sendfile的系統負載要低一些,cpu使用率要低很多,整體速度和send基本差不多,估計是當今系統計算速度太快,看不出什么明顯區別。不過對比web服務器的話區別還是很大的。
踩
總結
以上是生活随笔為你收集整理的Linux网络编程--sendfile零拷贝高效率发送文件的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。