linux 高级IO函数之sendfile splice tee
sendfile函數(shù)在兩個文件描述符之間傳遞數(shù)據(jù)(完全在內(nèi)核中操作),從而避免內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的數(shù)據(jù)拷貝,效率很高,這被稱為零拷貝。函數(shù)的定義如下:
#include<sys/sendfile.h>ssize_t sendfile(int out_fd,int in_fd , off_t* offset ,size_t count);in_fd參數(shù)是待讀出內(nèi)容的文件描述符,out_fd參數(shù)是待寫入內(nèi)容的文件描述符。offset參數(shù)執(zhí)行從讀入文件流的哪個位置開始讀,如果為空,則使用讀入文件流的默認起始位置。count參數(shù)指定在文件描述符in_fd和out_fd之間傳輸?shù)淖止?jié)數(shù)。sendfile成功時返回傳輸?shù)淖止?jié)數(shù),失敗則返回-1并設(shè)置errno。該函數(shù)的man手冊明確指出,in_fd必須是一個支持mmap函數(shù)的文件描述符,即它必須指向真實的文件,而不能是socket和管道;則out_fd則必須是一個socket。由此可見,sendfile幾乎是專門為在網(wǎng)絡(luò)上傳輸文件而設(shè)計的。
下面的例子利用sendfile函數(shù)將服務(wù)器上的一個文件傳送給客戶端
#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函數(shù)用于在兩個文件描述符之間移動數(shù)據(jù),也是零拷貝。
函數(shù)的定義如下
#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參數(shù)是待輸入數(shù)據(jù)的文件描述符。如果fd_in是一個管道文件描述符,那么off_in參數(shù)必須設(shè)置為NULL。如果fd_in不是一個管道文件(比如是一個socket),那么off_in表示從輸入數(shù)據(jù)流的何處開始讀取數(shù)據(jù)。此時,如果off_in被設(shè)置為NULL,則表示從輸入數(shù)據(jù)流的當前偏移位置讀入;若off_in不為NULL,則它將指出具體的偏移位置。fd_out/off_out參數(shù)的含義與fd_in/off_in相同,不過用于輸出數(shù)據(jù)流。len參數(shù)指定移動數(shù)據(jù)的長度;flag參數(shù)則控制數(shù)據(jù)如何移動,它可以設(shè)置為下表中的某些值的按位或。
使用splice函數(shù)時,fd_in 和fd_out必須至少有一個是管道文件描述符。splice函數(shù)調(diào)用成功時返回移動字節(jié)的數(shù)量,它可能返回0,表示沒有數(shù)據(jù)需要移動,這發(fā)生在從管道中讀取數(shù)據(jù),而該管道沒有被寫入任何數(shù)據(jù)時。spice函數(shù)失敗時返回-1并設(shè)置errno.常見的errno如下圖
下面的例子是利用splice函數(shù)來實現(xiàn)一個零拷貝的回射服務(wù)器,它將客戶端發(fā)送的數(shù)據(jù)原樣返回客戶端。
#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函數(shù)將客戶端的內(nèi)容讀取到pipefd[1]中,然后在使用splice函數(shù)從pipefd[0]中讀出該內(nèi)容到客戶端,從而實現(xiàn)了簡單高效的回射服務(wù)。整個過程未執(zhí)行recv/send操作,因此也未涉及用戶空間和內(nèi)核空間之間的拷貝。
tee函數(shù)在兩個管道文件描述符之間復(fù)制數(shù)據(jù),也是零拷貝操作。它不消耗數(shù)據(jù),因此源文件描述符上的數(shù)據(jù)仍然可以用于后續(xù)的讀操作。函數(shù)原型如下:
#include<fcntl.h>ssize_t tee(int fd_in ,int fd_out,size_t len ,unsigned int flags);該函數(shù)的參數(shù)的含義以splice相同(但fd_in 和fd_out都必須是管道文件描述符)。tee函數(shù)成功時返回在兩個文件描述符之間復(fù)制的數(shù)據(jù)數(shù)量(字節(jié)數(shù))。返回0表示沒有復(fù)制任何數(shù)據(jù),tee失敗時返回-1并設(shè)置errno。
如下代碼利用tee函數(shù)和splice函數(shù),實現(xiàn)了linux下的tee程序(同時輸出數(shù)據(jù)到終端和文件的程序)
#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; }總結(jié)
以上是生活随笔為你收集整理的linux 高级IO函数之sendfile splice tee的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VirtualBox + vagrant
- 下一篇: 栈溢出笔记1.2 覆盖EIP