网络编程API-中 (高级I/O函数)

来源:互联网 发布:手术室小讲课知乎 编辑:程序博客网 时间:2024/05/02 00:28

Linux有很多高级I/O函数,不像基本的IO函数那样常用,但是在特定的场合下使用高级IO函数可以是程序来的更简洁,效率也更高。


pipe函数

#include <unistd.h>int pipe(int fd[2]);返回:成功返回0,并将一对打开的文件描述符值填入其参数指向的数组,出错-1

        通过pipe函数创建的两个文件描述符fd[0]和fd[1]构成了管道的两端,往fd[1]写入的数据通过fd[0]读出。并且,只能通过fd[0]读出数据,通过fd[1]写入数据,不能反过来使用。如果要使用双向的数据传输,需要创建两个管道。默认情况下,这一对文件描述符是阻塞的。此时我们用read系统调用来读取一个空的管道,则read会被阻塞,直到管道中有数据可读;如果我们用write系统调用来往一个满的管道中写数据,则write会被阻塞,直到管道有足够多的空间可写。如果程序将fd[0]和fd[1]设置为非阻塞的,则read和write会有不同的行为。


dup和dup2函数

#include <unistd.h>int dup(int filedes);int dup2(int filedes, int filedes2);返回:成功返回新的描述符,出错返回-1

        由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值,用dup2则可以用filedes2参数指定新描述的数值。如果filedes2已经打开,则先将其关闭,若filedes等于filedes2,则dup2返回filedes2,而不关闭它。通过dup和dup2创建的文件描述符并不继承原文件描述符的属性。

        有时把标准输入从定向到一个文件,或者把一个标准输出从定向到一个网络连接(比如CGI编程的基本原理就是这样的)

/** * CGI基本工作原理示例,用到了dup函数,这只是服务器端的代码*/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <netdb.h>#include <sys/types.h>#include <string.h>#include <fcntl.h>#define SA struct sockaddr#define SA_IN struct sockaddr_in#define err_sys(msg) \do { perror(msg); exit(-1); } while(0)#define err_exit(msg) \do { fprintf(stderr, msg); exit(-1); } while(0)int main(int argc, char *argv[]){if(argc != 3)err_exit("usage: ./a.out ip port\n");int port = atoi(argv[2]);int listenfd, connfd;SA_IN servaddr;if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)err_sys("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)err_sys("inet_pton error");if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)err_sys("bind error");if(listen(listenfd, 5) < 0)err_sys("listen error");SA_IN cliaddr;int clilen = sizeof(cliaddr);if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)err_sys("accept error");else{close(STDOUT_FILENO); //关闭标准输出dup(connfd); //dup返回的新文件描述符一定是当前可用文件描述符中最小值,所以为1(标准输出),此时标准输出就映射到了connfd上printf("hello!");close(connfd);}close(listenfd);return 0;}

上面的程序只是服务器端的代码,客户端的代码可以参考:http://blog.csdn.net/u012796139/article/details/44984879


readv和writev函数

#include <sys/uio.h>ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);ssize_t writev(int sockfd, const strut iovec *iov, int iovcnt);返回:成功为读入或写出的字节数,出错-1

        这两个函数类似于read和write,不过readv和writev允许单个系统调用读入或写出到一个或多个缓冲区,非别称为分散读和集中写,因为来自读操作的输入数据被分散到多个应用缓冲区中,而来自多个应用的缓冲区的输入数据则被集中提供单个写操作。

这两个函数第二个参数指针都指向iovec结构数组的一个指针,iovec在头文件<sys/uio.h>中定义:

struct iovec{void *iov_base;size_t iov_len;}
        web服务器上的集中写,用到了writev函数,这里只写了http的应答,省略了http请求的接受和解析。直接把目标文件发送给客户端,可以直接在浏览器中输入IP地址和端口后就可以看到目标文件了,比如在浏览器中输入:192.168.1.5:60000,192.168.1.5为运行该程序的主机IP地址,60000为相应的端口号。
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <fcntl.h>#include <string.h>#include <errno.h>#include <sys/stat.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#define SA struct sockaddr#define SA_IN struct sockaddr_in#define err_sys(msg) \do { perror(msg); exit(-1); } while(0)#define err_exit(msg) \do { printf(msg); exit(-1); } while(0)#define err_info(msg) \do { printf(msg); } while(0)#define BUFFSIZE 1024/* 定义的两这种http状态信息 */const char *status_line[2] = {"200 OK", "500 Internal Server Error"};int main(int argc, char *argv[]){if(argc != 4)err_exit("usgae: ./a.out ip port filename\n");int port = atoi(argv[2]);char *filename = argv[3]; //目标文件为第4个参数SA_IN servaddr;int listenfd, connfd;if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)err_sys("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)err_sys("inet_pton error");if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)err_sys("bind error");if(listen(listenfd, 5) < 0)err_sys("listen error");SA_IN cliaddr;socklen_t clilen = sizeof(cliaddr);while(1) //这里是死循环避免在浏览器中输入相应的IP和端口号后无法看到目标文件内容{if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)err_sys("accept error");/* 用于保存http应答的状态行 头部字段和一个空行的缓冲区 */char head_buf[BUFFSIZE];char *file_buf; //用于存放目标文件的缓冲区struct stat file_stat; //获取目标属性int len = 0; //缓冲区head_buf当前占用字节数int vaild = 1; //记录文件是否有效if(stat(filename, &file_stat) < 0){err_info("stat error");vaild = 0;}else{if(S_ISDIR(file_stat.st_mode)){err_info("the file is a director");vaild = 0;}else if(file_stat.st_mode & S_IROTH){file_buf = new char[file_stat.st_size + 1];memset(file_buf, 0, BUFFSIZE);int fd;if((fd = open(filename, O_RDONLY)) < 0){delete []file_buf;err_sys("open error");}if(read(fd, file_buf, file_stat.st_size) < 0){vaild = 0;}close(fd);}else{vaild = 0;}/* 目标文件存在则发送http正常应答 */int ret;if(vaild){ret = snprintf(head_buf, BUFFSIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[0]);len += ret;ret = snprintf(head_buf+len, BUFFSIZE-1-len, "Content-Length: %lu\r\n", file_stat.st_size);len += ret;ret = snprintf(head_buf+len, BUFFSIZE-1-len, "\r\n");struct iovec iv[2];iv[0].iov_base = head_buf;iv[0].iov_len = strlen(head_buf);iv[1].iov_base = file_buf;iv[1].iov_len = strlen(file_buf);writev(connfd, iv, 2); //writev将head_buf和file_buf内容一并写出delete []file_buf;}else //发生了错误{ret = snprintf(head_buf, BUFFSIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[1]);len += ret;snprintf(head_buf, BUFFSIZE-1-len, "\r\n");send(connfd, head_buf, strlen(head_buf), 0);}close(connfd);}}close(listenfd);return 0;}

sendfile函数

#include <sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);返回:成功返回写入out_fd的字节数,出错-1
        sendfile在两个文件描述符之间传递数据(完全在内核中操作),避免了内核缓冲区和用户缓冲区之间的数据复制,效率很高,称为零拷贝。in_fd是待读出数据的文件描述符,out_fd是待写入数据的文件描述符,off_t指定从流从哪个位置开始,为空则表示按照文件流默认的起始位置,count表示要传输的字节数。从该函数的man手册中可以知道, in_fd必须是一个支持mmap函数的文件描述符,即它必须指向真实的文件,不能指向socket和管道。在Linux2.6.33之前,out_fd必须指向一个socket,但是在这之后,out_fd可以指向任意文件。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <fcntl.h>#include <string.h>#include <errno.h>#include <sys/stat.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/sendfile.h>#define SA struct sockaddr#define SA_IN struct sockaddr_in#define err_sys(msg) \do { perror(msg); exit(-1); } while(0)#define err_exit(msg) \do { printf(msg); exit(-1); } while(0)#define err_info(msg) \do { printf(msg); } while(0)int main(int argc, char *argv[]){if(argc != 4)err_exit("usgae: ./a.out ip port filename\n");int port = atoi(argv[2]);char *filename = argv[3]; //目标文件为第4个参数SA_IN servaddr;int listenfd, connfd;if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)err_sys("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)err_sys("inet_pton error");if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)err_sys("bind error");if(listen(listenfd, 5) < 0)err_sys("listen error");int fd;struct stat file_stat;if((fd = open(argv[3], O_RDONLY)) < 0)err_sys("open error");if(stat(argv[3], &file_stat) < 0)err_sys("stat error");SA_IN cliaddr;socklen_t clilen = sizeof(cliaddr);if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)err_sys("accept error");if(sendfile(connfd, fd, NULL, file_stat.st_size) < 0)err_info("sendfile error");close(connfd);close(fd);close(listenfd);return 0;}

splice函数

#include <fcntl.h>ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* fd_out, size_t len, unsigned int flags);返回:成功返回复制的数据量(字节),出错-1

        splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。fd_in是待数据数据文件描述符,如果fd_in是一个管道,则off_in必须为NULL;若off_in不为NULL,则表示具体的偏移位置,len参数指定移动数据的长度,flags控制如何移动数据。

        splicefd_infd_out必须至少有一个是管道文件描述符,splice函数调用成功返回移动字节数量,可以能返回0,表示没有数据移动,发生在从管道读取数据(fd_in是管道文件描述符),而管道中并没有写入任何数据。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。

        在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。

更多管道资料参考:http://www.cnblogs.com/davidwang456/p/3839874.html

这是使用splice函数实现的一个零拷贝的回射服务器,它把客户端发送的数据原样发回给客户端。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/socket.h>#include <fcntl.h>#include <string.h>#include <errno.h>#include <sys/stat.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#define SA struct sockaddr#define SA_IN struct sockaddr_in#define err_sys(msg) \do { perror(msg); exit(-1); } while(0)#define err_exit(msg) \do { printf(msg); exit(-1); } while(0)#define err_info(msg) \do { printf(msg); } while(0)int main(int argc, char *argv[]){if(argc != 3)err_exit("usgae: ./a.out ip port\n");int port = atoi(argv[2]);SA_IN servaddr;int listenfd, connfd;if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)err_sys("socket error");bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port);if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)err_sys("inet_pton error");if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)err_sys("bind error");if(listen(listenfd, 5) < 0)err_sys("listen error");SA_IN cliaddr;socklen_t clilen = sizeof(cliaddr);if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)err_sys("accept error");int pipefd[2];if(pipe(pipefd) < 0)err_sys("pipe error");if(splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("splice error");if(splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("splice error");close(connfd);close(listenfd);return 0;}

tee函数

#include <fcntl.h>ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);返回:成功返回赋值的数据字节数,0为没有复制任何数据,出错返回-1

        tee在两个管道之间复制数据,也是零拷贝操作。

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <errno.h>#define err_sys(msg) \do { perror(msg); exit(-1); } while(0)#define err_exit(msg) \do { printf(msg); exit(-1); } while(0)#define err_info(msg) \do { printf(msg); } while(0)int main(int argc, char *argv[]){if(argc != 2)err_exit("usage: ./a.out new_filename\n");int fd;if((fd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666)) < 0)err_sys("open error");int pipefd_file[2], pipefd_inout[2];if(pipe(pipefd_file) < 0 || pipe(pipefd_inout) < 0)err_sys("pipe error");if(splice(STDIN_FILENO, NULL, pipefd_inout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("splice error");if(tee(pipefd_inout[0], pipefd_file[1], 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("tee error");if(splice(pipefd_inout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("splice error");if(splice(pipefd_file[0], NULL, fd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)err_sys("splice error");close(pipefd_inout[0]);close(pipefd_inout[1]);close(pipefd_file[0]);close(pipefd_file[1]);close(fd);return 0;}

参考资料:

1、《Linux高性能服务器编程》第6章 高级IO函数

2、《UNIX网络编程》的相关章节

3、网络编程API-上 (基本API)

4、服务器、客户端简单交互程序


0 0
原创粉丝点击