网络编程基础(3):Linux网络编程高级I/O函数 包含如下几个重点函数:pipe、dup/dup2函数 ,readv/writev、sendfile、mmap/munmap、splice和tee函数 ,fcntl 等 注意:这一章节主要是对,文件描述符的操作 ,有利于传输数据效率的提高。
总体框架 三类函数
创建文件描述符的函数pipe、dup/dup2函数
用于控制读写数据的函数readv/writev、sendfile、mmap/munmap、splice和tee函数。
用于控制I/O行为和属性的函数fcntl函数
pipe函数 用于创建一个管道,用以实现进程间的通讯。 定义如下
1 2 3 4 5 6 7 #include <unistd.h> int pipe (int fd[2 ]) ;
关键点
fd[0]只能用于读取管道数据, fd[1]只能用于向管道写入数据。如果想要实现双向通信,应该使用两个管道。
若调用read读取时,为0意味着读到了文件结束标志(EOF)。同时若针对该管道的读取端fd[0]引用计数为0(意味着没有需要管道数据的进程),则针对写fd[1]进行write操作将失败,并引发SIGPIPE信号。
管道默认大小是65536字节,可通过fcntl函数进行后期更改。
1 2 3 4 5 6 7 8 9 #include <sys/types.h> #include <sys/socket.h> int socketpair (int domain, int type, int protocol, int fd[2 ]) ;
dup/dup2函数 用于复制文件描述符
1 2 3 4 5 6 7 8 9 10 #include <unistd.h> int dup (int fd) ;int dup2 (int fd1, int fd2) ;
readv&&writev函数 readv函数是从文件描述符读到分散的内存块中,即分散读。
writev函数则将多块分散的内存数据一并写入文件描述符,即集中写。
1 2 3 #include <sys/uio.h> ssize_t readv (int fd, const struct iovec* vector, int count) ;ssize_t writev (int fd, const struct iovec* vector, int count) ;
sendfile函数 sendfile函数在两个文件描述符之间直接传递数据(完全在内核中操作),从而避免数据拷贝,效率更高。
1 2 3 4 5 6 7 8 9 #include <sys/sendfile.h> ssize_t sendfile (int out_fd, int in_fd, off_t * offsert, size_t count) ;
关键点
in_fd必须指向真实的文件,不能是socket和管道。
out_fd则必须是socket。
程序测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <errno.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 backlog\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); int sock = socket (PF_INET, SOCK_STREAM, 0 ); assert (sock >= 0 ); 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 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 ; }
服务器端运行命令
1 2 gao@gao-VirtualBox:~/桌面/net_code$ gcc SendFile.c -o send_file gao@gao-VirtualBox:~/桌面/net_code$ ./send_file 127.0 .0 .1 9090 context.txt
利用telnet客户端进行测试
1 2 3 4 5 6 7 gao@gao-VirtualBox:~/桌面/net_code$ telnet 127.0 .0 .1 9090 Trying 127.0 .0 .1 ... Connected to 127.0 .0 .1 . Escape character is '^]' . 须知少时凌云志 曾许人间第一流 Connection closed by foreign host.
mmap&&munmap函数 mmap函数用于申请一段内存空间,可以将这段内存作为进程间通信的共享内存 ,也可以将文件直接映射到其中。
munmap函数则释放由mmap函数创建的这段内存空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <sys/mman.h> void * mmap (void *start, size_t length, int prot, int flags, int fd, off_t offset) ;int munmap (void *start, size_t length) ;
splice函数 splice函数用于在两个文件描述符之间移动数据。也是零拷贝操作。
定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <fcntl.h> ssize_t splice (int fd_src, loff_t *off_src, int fd_des, loff_t *off_des, size_t len, unsigned int flags) ;
程序测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #define _GNU_SOURCE 1 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/sendfile.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 ]); int sock = socket (PF_INET, SOCK_STREAM, 0 ); assert (sock >= 0 ); 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 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_MORE); assert (ret != -1 ); ret = splice (pipefd[0 ], NULL , connfd, NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MORE); assert (ret != -1 ); close (connfd); } close (sock); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 gao@gao-VirtualBox:~/桌面/net_code$ gcc Splice.c -o Splice gao@gao-VirtualBox:~/桌面/net_code$ ./Splice 127.0.0.1 9090 gao@gao-VirtualBox:~/桌面/net_code$ telnet 127.0.0.1 9090 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' . 须知少时凌云志 曾许人间第一流 须知少时凌云志 曾许人间第一流 Connection closed by foreign host.
tee函数 tee函数在两个管道文件描述符 之间复制数据,也是零拷贝操作。
定义如下
1 2 3 4 5 6 #include <fcntl.h> ssize_t tee (int fd_in, int fd_out, size_t len, unsigned int flags) ;
程序测试(利用splice和tee函数实现同时输出数据到终端和文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #define _GNU_SOURCE 1 #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <errno.h> #include <fcntl.h> int main (int argc, char * argv[]) { if ( argc != 2 ) { printf ("usage: %s ip_address port_number backlog\n" , basename (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 ); ret = splice (STDIN_FILENO, NULL , pipefd_stdout[1 ], NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MORE); 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_MORE); assert (ret != -1 ); ret = splice (pipefd_stdout[0 ], NULL , STDOUT_FILENO, NULL , 32768 , SPLICE_F_MORE | SPLICE_F_MORE); assert (ret != -1 ); close (filefd); close (pipefd_stdout[0 ]); close (pipefd_stdout[1 ]); close (pipefd_file[0 ]); close (pipefd_file[1 ]); return 0 ; }
1 2 3 4 5 6 7 gao@gao-VirtualBox:~/桌面/net_code/第六章$ gcc Tee.c -o Tee gao@gao-VirtualBox:~/桌面/net_code/第六章$ ./Tee context.txt 须知少时凌云志 曾许人间第一流 须知少时凌云志 曾许人间第一流 gao@gao-VirtualBox:~/桌面/net_code/第六章$ cat context.txt 须知少时凌云志 曾许人间第一流
fcntl函数 fcnti函数执行各种描述符的控制操作
定义如下
1 2 #include <fcntl.h> int fcntl (int fd, int cmd, ....) ;
常用选项
经典的使用案例(设置文件描述符为非阻塞)
1 2 3 4 5 6 7 8 9 int setnonblocking (int fd) { int old_option = fcntl (fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl (fd, F_SETFL, new_option); return old_option; }
1 2 3 4 5 6 int flag = fcntl (fd, F_GETFL, 0 );flag |= O_NONBLOCK; fcntl (fd, F_SETFL, flag);