Linux网络编程 | 零拷贝 :sendfile、mmap、splice、tee
文章目錄
- 傳統文件傳輸的問題
- Linux中實現零拷貝的方法
傳統文件傳輸的問題
在網絡編程中,如果我們想要提供文件傳輸的功能,最簡單的方法就是用read將數據從磁盤上的文件中讀取出來,再將其用write寫入到socket中,通過網絡協議發送給客戶端。
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);但是就是這兩個簡單的操作,卻帶來了大量的性能丟失
例如我們的服務器需要為客戶端提供一個下載操作,此時的操作如下
從上圖可以看出,雖然僅僅只有這兩行代碼,但是卻在發生了四次用戶態和內核態的上下文切換,以及四次數據拷貝,也就是在這個地方產生了大量不必要的損耗。
那么為什么會發生這些操作呢?
上下文切換
由于read和recv是系統調用,所以每次調用該函數我們都需要從用戶態切換至內核態,等待內核完成任務后再從內核態切換回用戶態。
數據拷貝
上面也說了,由于數據的讀取與寫入都是由系統進行的,那么我們就得將數據從用戶的緩沖區中拷貝到內核,
- 第一次拷貝:將磁盤中的數據拷貝到內核的緩沖區中
- 第二次拷貝:內核將數據處理完,接著拷貝到用戶緩沖區中
- 第三次拷貝:此時需要通過socket將數據發送出去,將用戶緩沖區中的數據拷貝至內核中socket的緩沖區中
- 第四次拷貝:把內核中socket緩沖區的數據拷貝到網卡的緩沖區中,通過網卡將數據發送出去。
所以要想優化傳輸性能,就要從減少數據拷貝和用戶態內核態的上下文切換下手,這也就是零拷貝技術的由來。
什么是零拷貝呢?
零拷貝的主要任務就是避免CPU將數據從一塊存儲中拷貝到另一塊存儲,主要就是利用各種技術,避免讓CPU做大量的數據拷貝任務,以此減少不必要的拷貝。或者借助其他的一些組件來完成簡單的數據傳輸任務,讓CPU解脫出來專注別的任務,使得系統資源的利用更加有效
Linux中實現零拷貝的方法
Linux中實現零拷貝的方法主要有以下幾種,下面一一對其進行介紹
sendfile
sendfile函數的作用是直接在兩個文件描述符之間傳遞數據。由于整個操作完全在內核中(直接從內核緩沖區拷貝到socket緩沖區),從而避免了內核緩沖區和用戶緩沖區之間的數據拷貝。
需要注意的是,in_fd必須是一個支持類似mmap函數的文件描述符,不能是socket或者管道,而out_fd必須是一個socket,由此可見sendfile是專門為了在網絡上傳輸文件而實現的函數。
#include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);參數:
out_fd : 待寫入內容的文件描述符
in_fd : 待讀出內容的文件描述符
offset : 文件的偏移量
count : 需要傳輸的字節數
返回值:
成功:返回傳輸的字節數
失敗:返回-1并設置errno
mmap
mmap用于申請一段內存空間,也就是我們在進程間通信中提到過的共享內存,通過將內核緩沖區的數據映射到用戶空間中,兩者通過共享緩沖區直接訪問統一資源,此時內核與用戶空間就不需要再進行任何的數據拷貝操作了
其中mmap用于申請空間,額munmap用于釋放這段空間。
參數:
addr : 內存的起始地址,如果設置為空則系統會自動分配
length : 指定內存段的長度
prot : 內存段的訪問權限,通過按位與或可以取以下幾種值
flag : 選項
fd : 被映射文件對應的文件描述符
offset : 文件的偏移量
返回值:
成功:成功時返回指向內存區域的指針
失敗:返回MAP_FAILED并設置errno
splice
splice函數用于在兩個文件描述符之間移動數據,而不需要數據在內核空間和用戶空間中來回拷貝
需要注意的是,使用splice函數時fd_in和fd_out至少有一個是管道文件描述符,即
#include <fcntl.h> ssize_t splice(int fd_in, loff_t *off_in, int fd_out,loff_t *off_out, size_t len, unsigned int flags);參數:
out_fd : 待寫入內容的文件描述符
off_out : 待寫入文件描述符的偏移量,如果文件描述符為管道則必須為空
in_fd : 待讀出內容的文件描述符
off_in : 待讀出文件描述符的偏移量,如果文件描述符為管道則必須為空
len : 需要復制的字節數
flags : 選項
返回值:
成功:返回在兩個文件描述符之間復制的字節數
沒有數據:返回0
失敗:返回-1并設置errno
可能產生的errno
tee
tee函數用于在兩個管道文件描述符之間復制數據,并且它是直接復制,不會將數據讀出,所以源文件上的數據仍可以用于后面的讀操作
參數:
out_fd : 待寫入內容的文件描述符
in_fd : 待讀出內容的文件描述符
len : 需要復制的字節數
flags : 選項
返回值:
成功:返回在兩個文件描述符之間復制的字節數
沒有數據:返回0
失敗:返回-1并設置errno
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生
總結
以上是生活随笔為你收集整理的Linux网络编程 | 零拷贝 :sendfile、mmap、splice、tee的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux网络编程 | 高性能定时器 :
- 下一篇: Linux中进程间传递文件描述符的方法