linux 高级IO函数之sendfile splice tee
sendfile函數在兩個文件描述符之間傳遞數據(完全在內核中操作),從而避免內核緩沖區和用戶緩沖區之間的數據拷貝,效率很高,這被稱為零拷貝。函數的定義如下:
#include<sys/sendfile.h>ssize_t sendfile(int out_fd,int in_fd , off_t* offset ,size_t count);in_fd參數是待讀出內容的文件描述符,out_fd參數是待寫入內容的文件描述符。offset參數執行從讀入文件流的哪個位置開始讀,如果為空,則使用讀入文件流的默認起始位置。count參數指定在文件描述符in_fd和out_fd之間傳輸的字節數。sendfile成功時返回傳輸的字節數,失敗則返回-1并設置errno。該函數的man手冊明確指出,in_fd必須是一個支持mmap函數的文件描述符,即它必須指向真實的文件,而不能是socket和管道;則out_fd則必須是一個socket。由此可見,sendfile幾乎是專門為在網絡上傳輸文件而設計的。
下面的例子利用sendfile函數將服務器上的一個文件傳送給客戶端
#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;}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 );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{sendfile( connfd, filefd, NULL, stat_buf.st_size );close( connfd );}close( sock );return 0; }splice函數用于在兩個文件描述符之間移動數據,也是零拷貝。
函數的定義如下
#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)fd_in參數是待輸入數據的文件描述符。如果fd_in是一個管道文件描述符,那么off_in參數必須設置為NULL。如果fd_in不是一個管道文件(比如是一個socket),那么off_in表示從輸入數據流的何處開始讀取數據。此時,如果off_in被設置為NULL,則表示從輸入數據流的當前偏移位置讀入;若off_in不為NULL,則它將指出具體的偏移位置。fd_out/off_out參數的含義與fd_in/off_in相同,不過用于輸出數據流。len參數指定移動數據的長度;flag參數則控制數據如何移動,它可以設置為下表中的某些值的按位或。
使用splice函數時,fd_in 和fd_out必須至少有一個是管道文件描述符。splice函數調用成功時返回移動字節的數量,它可能返回0,表示沒有數據需要移動,這發生在從管道中讀取數據,而該管道沒有被寫入任何數據時。spice函數失敗時返回-1并設置errno.常見的errno如下圖
下面的例子是利用splice函數來實現一個零拷貝的回射服務器,它將客戶端發送的數據原樣返回客戶端。
#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 <fcntl.h>int main( int argc, char* argv[] ) {if( argc <= 2 ){printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );return 1;}const char* ip = argv[1];int port = atoi( argv[2] );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{int pipefd[2];assert( ret != -1 );ret = pipe( pipefd );ret = splice( connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE ); assert( ret != -1 );ret = splice( pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );close( connfd );}close( sock );return 0; }我們通過splice函數將客戶端的內容讀取到pipefd[1]中,然后在使用splice函數從pipefd[0]中讀出該內容到客戶端,從而實現了簡單高效的回射服務。整個過程未執行recv/send操作,因此也未涉及用戶空間和內核空間之間的拷貝。
tee函數在兩個管道文件描述符之間復制數據,也是零拷貝操作。它不消耗數據,因此源文件描述符上的數據仍然可以用于后續的讀操作。函數原型如下:
#include<fcntl.h>ssize_t tee(int fd_in ,int fd_out,size_t len ,unsigned int flags);該函數的參數的含義以splice相同(但fd_in 和fd_out都必須是管道文件描述符)。tee函數成功時返回在兩個文件描述符之間復制的數據數量(字節數)。返回0表示沒有復制任何數據,tee失敗時返回-1并設置errno。
如下代碼利用tee函數和splice函數,實現了linux下的tee程序(同時輸出數據到終端和文件的程序)
#include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <fcntl.h>int main( int argc, char* argv[] ) {if ( argc != 2 ){printf( "usage: %s <file>\n", argv[0] );return 1;}int filefd = open( argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666 );assert( filefd > 0 );int pipefd_stdout[2];int ret = pipe( pipefd_stdout );assert( ret != -1 );int pipefd_file[2];ret = pipe( pipefd_file );assert( ret != -1 );//close( STDIN_FILENO );// dup2( pipefd_stdout[1], STDIN_FILENO );//write( pipefd_stdout[1], "abc\n", 4 );ret = splice( STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );ret = tee( pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK ); assert( ret != -1 );ret = splice( pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );ret = splice( pipefd_stdout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE );assert( ret != -1 );close( filefd );close( pipefd_stdout[0] );close( pipefd_stdout[1] );close( pipefd_file[0] );close( pipefd_file[1] );return 0; }總結
以上是生活随笔為你收集整理的linux 高级IO函数之sendfile splice tee的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VirtualBox + vagrant
- 下一篇: Linux下C程序进程地址空间布局