Sendfile函數(shù)說明
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
sendfile()是作用于數(shù)據(jù)拷貝在兩個(gè)文件描述符之間的操作函數(shù).這個(gè)拷貝操作是內(nèi)核中操作的,所以稱為"零拷貝".sendfile函數(shù)比起read和write函數(shù)高效得多,因?yàn)閞ead和write是要把數(shù)據(jù)拷貝到用戶應(yīng)用層操作.
參數(shù)說明:
out_fd 是已經(jīng)打開了,用于寫操作(write)的文件描述符;
in_fd 是已經(jīng)打開了,用于讀操作(read)的文件描述符;
offset 偏移量;表示sendfile函數(shù)從in_fd中的哪一偏移量開始讀取數(shù)據(jù).如果是零表示從文件的開始讀,否則從相應(yīng)的便宜量讀取.如果是循環(huán)讀取的時(shí)候,下一次offset值應(yīng)為sendfile函數(shù)返回值加上本次的offset的值.
count是在兩個(gè)描述符之間拷貝的字節(jié)數(shù)(bytes)
返回值:
如果成功的拷貝,返回寫操作到out_fd的字節(jié)數(shù),錯(cuò)誤返回-1,并相應(yīng)的設(shè)置error信息.
EAGAIN 無阻塞I/O設(shè)置O_NONBLOCK時(shí),寫操作(write)阻塞了.
EBADF 輸出或者輸入的文件描述符沒有打開.
EFAULT 錯(cuò)誤的地址.
EINVAL 描述符不可用或者鎖定了,或者用mmap()函數(shù)操作的in_fd不可用.
EIO 當(dāng)讀取(read)in_fd時(shí)發(fā)生未知錯(cuò)誤.
ENOMEM 讀(read)in_fd時(shí)內(nèi)存不足.
------------------------------------------------------------------------------
由于想再提升原有系統(tǒng)中文件傳輸模塊的速度,并減少系統(tǒng)資源占用,進(jìn)行了一次sendfile()的性能測試,但失敗了.不過還是將它用在了模塊中.記錄一下這次失改的微調(diào)測試.
運(yùn)行平臺(tái): 客戶機(jī)與服務(wù)器均為P4計(jì)算機(jī),IDE硬盤; Fedora5發(fā)行版; 百兆局域網(wǎng);
接收端程序如下:
FILE *fp = fopen(FILENAME,"wb");
while((len = recv(sockfd, buff, sizeof(buff), 0)) > 0) { fwrite(buffer, 1, len, fp); } fclose(fp);
|
A. 發(fā)送端傳統(tǒng)方式代碼段如下:
fd = open(FILENAME, O_RDONLY); while((len =read(fd, buff, sizeof(buff))) >0) { send(sockfd, buff, len ,0); } close(fd); |
由于我磁盤分區(qū)時(shí)指定的塊大小為4096,為了最優(yōu)讀取磁盤數(shù)據(jù),buff大小設(shè)為4096字節(jié).但在測試中發(fā)現(xiàn)設(shè)為1024或8192不會(huì)對傳輸速度帶來影響.
文件大小:9M; 耗時(shí):0.71 - 0.76秒;
文件大小:32M; 耗時(shí):2.64 - 2.68秒;
文件大小:64M; 耗時(shí):5.36 - 5.43秒;
B. 使用sendfile()傳輸代碼段.
off_t offset = 0; stat(FILENAME, &filestat);
fd = open(FILENAME, O_RDONLY); sendfile(sockfd, fd, &offset, filestat.st_size) ); close(fd);
|
文件大小:9M; 耗時(shí):0.71 - 1.08秒;
文件大小:32M; 耗時(shí):2.66 - 2.74秒;
文件大小:64M; 耗時(shí):5.43 - 6.64秒;
似乎還略有下降.根據(jù)sendfile的man手冊,我在使用該函數(shù)前調(diào)用了
int no = 1; printf("%d\n", setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, (char*)&no, sizeof(int)) );
|
文件大小:9M; 耗時(shí):0.72 - 0.75秒;
文件大小:32M; 耗時(shí):2.66 - 2.68秒;
文件大小:64M; 耗時(shí):5.38 - 5.60秒;
這樣似乎達(dá)到了傳統(tǒng)方式的速度?!不管哪種環(huán)境下,我用ethereal抓包顯示每一個(gè)tcp包的playload部分最大也通常是1448字節(jié).
看來我的測試沒有體現(xiàn)出"應(yīng)用層數(shù)據(jù)的兩次拷貝帶來很大的消耗"這一說法.如果按照存在就是有理的說法的話,那我想sendfile()在兩種情況下才體現(xiàn)優(yōu)勢,但我卻沒有環(huán)境測試:
1. 大并發(fā)量的文件服務(wù)器或HTTP服務(wù)器;
2. 內(nèi)存資源緊張的嵌入式系統(tǒng);
另外,網(wǎng)絡(luò)上大量的關(guān)于tcp選項(xiàng)中的TCP_CORK描述已經(jīng)過時(shí).在man手冊中早已提到該參數(shù)可以與TCP_NODELAY結(jié)合使用了.只是,只要設(shè)置了TCP_NODELAY選項(xiàng)后,不管是否設(shè)置TCP_CORK,包都會(huì)立即發(fā)出.
----------------------------------------------------------------------
補(bǔ)充:
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在這里的含義是采用Nagle算法把較小的包組裝為更大的幀。 John Nagle是Nagle算法的發(fā)明人,后者就是用他的名字來命名的,他在1984年首次用這種方法來嘗試解決福特汽車公司的網(wǎng)絡(luò)擁塞問題(欲了解詳情請參看IETF RFC 896)。他解決的問題就是所謂的silly window syndrome ,中文稱“愚蠢窗口癥候群”,具體含義是,因?yàn)槠毡榻K端應(yīng)用程序每產(chǎn)生一次擊鍵操作就會(huì)發(fā)送一個(gè)包,而典型情況下一個(gè)包會(huì)擁有一個(gè)字節(jié)的數(shù)據(jù)載荷以及40個(gè)字節(jié)長的包頭,于是產(chǎn)生4000%的過載,很輕易地就能令網(wǎng)絡(luò)發(fā)生擁塞,。 Nagle化后來成了一種標(biāo)準(zhǔn)并且立即在因特網(wǎng)上得以實(shí)現(xiàn)。它現(xiàn)在已經(jīng)成為缺省配置了,但在我們看來,有些場合下把這一選項(xiàng)關(guān)掉也是合乎需要的。
現(xiàn)在讓我們假設(shè)某個(gè)應(yīng)用程序發(fā)出了一個(gè)請求,希望發(fā)送小塊數(shù)據(jù)。我們可以選擇立即發(fā)送數(shù)據(jù)或者等待產(chǎn)生更多的數(shù)據(jù)然后再一次發(fā)送兩種策略。如果我們馬上發(fā)送數(shù)據(jù),那么交互性的以及客戶/服務(wù)器型的應(yīng)用程序?qū)O大地受益。例如,當(dāng)我們正在發(fā)送一個(gè)較短的請求并且等候較大的響應(yīng)時(shí),相關(guān)過載與傳輸?shù)臄?shù)據(jù)總量相比就會(huì)比較低,而且,如果請求立即發(fā)出那么響應(yīng)時(shí)間也會(huì)快一些。以上操作可以通過設(shè)置套接字的TCP_NODELAY選項(xiàng)來完成,這樣就禁用了 Nagle算法。
另外一種情況則需要我們等到數(shù)據(jù)量達(dá)到最大時(shí)才通過網(wǎng)絡(luò)一次發(fā)送全部數(shù)據(jù),這種數(shù)據(jù)傳輸方式有益于大量數(shù)據(jù)的通信性能,典型的應(yīng)用就是文件服務(wù)器。應(yīng)用Nagle算法在這種情況下就會(huì)產(chǎn)生問題。但是,如果你正在發(fā)送大量數(shù)據(jù),你可以設(shè)置TCP_CORK選項(xiàng)禁用Nagle化,其方式正好同 TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就讓我們仔細(xì)分析下其工作原理。
假設(shè)應(yīng)用程序使用sendfile()函數(shù)來轉(zhuǎn)移大量數(shù)據(jù)。應(yīng)用協(xié)議通常要求發(fā)送某些信息來預(yù)先解釋數(shù)據(jù),這些信息其實(shí)就是報(bào)頭內(nèi)容。典型情況下報(bào)頭很小,而且套接字上設(shè)置了TCP_NODELAY。有報(bào)頭的包將被立即傳輸,在某些情況下(取決于內(nèi)部的包計(jì)數(shù)器),因?yàn)檫@個(gè)包成功地被對方收到后需要請求對方確認(rèn)。這樣,大量數(shù)據(jù)的傳輸就會(huì)被推遲而且產(chǎn)生了不必要的網(wǎng)絡(luò)流量交換。
但是,如果我們在套接字上設(shè)置了TCP_CORK(可以比喻為在管道上插入“塞子”)選項(xiàng),具有報(bào)頭的包就會(huì)填補(bǔ)大量的數(shù)據(jù),所有的數(shù)據(jù)都根據(jù)大小自動(dòng)地通過包傳輸出去。當(dāng)數(shù)據(jù)傳輸完成時(shí),最好取消TCP_CORK 選項(xiàng)設(shè)置給連接“拔去塞子”以便任一部分的幀都能發(fā)送出去。這同“塞住”網(wǎng)絡(luò)連接同等重要。
總而言之,如果你肯定能一起發(fā)送多個(gè)數(shù)據(jù)集合(例如HTTP響應(yīng)的頭和正文),那么我們建議你設(shè)置TCP_CORK選項(xiàng),這樣在這些數(shù)據(jù)之間不存在延遲。能極大地有益于WWW、FTP以及文件服務(wù)器的性能,同時(shí)也簡化了你的工作。
轉(zhuǎn)自:
http://blog.chinaunix.net/u2/76292/showart.php?id=2105375
posted on 2009-11-28 20:08
chatler 閱讀(732)
評論(0) 編輯 收藏 引用 所屬分類:
linux kernel