- 6.1 pipe函数
- dup函数和dup2函数
- 6.3 readv函数和writev函数
- 6.4 sendfile函数
- 6.5 mmap函数和munmap函数
- 6.6 splice函数
- 6.7 tee函数
- 6.8 fcntl函数
pipe函数可用于创建一个管道,以实现进程间通信。其定义如下:
#includeint pipe( int fd[2] ); // 函数成功时返回0,并将一对打开的文件描述符值填入其参数指向的数组 // 函数失败返回-1,并设置errno
- pipe函数创建的两个文件描述符fd[0]和fd[1]分别构成管道的两端,往fd[1]写入的数据可以从fd[0]读出,且fd[0]只能从管道读出数据,fd[1]只能往管道写入数据。若想要实现双向的数据传输,应该使用两个管道。
- 默认情况下,这一对文件描述符都是阻塞的。
- 管道内部传输的数据是字节流,其本身拥有一个容量限制,其大小默认是65536字节,可使用fcntl函数来修改管道容量。
- 双向管道创建可使用sockpair函数:
#include#include // domain只能使用UNIX本地协议族AF_UNIX int socketpair(int domain, int type, int protocol, int fd[2]); // 创建成功返回0,失败返回-1并设置errno
dup函数和dup2函数
当我们希望把标准输入重定向到一个文件,或定向到一个网络连接,可通过复制文件描述符的dup和dup2来实现:
#include// 创建一个新的文件描述符,和原有文件描述符指向相同的文件、管道或网络连接 int dup(int file_descriptor); int dup2(int file_descriptor_one, int file_descriptor_two); // 调用失败返回-1,并设置errno
实例:利用dup函数实现一个基本的CGI服务器
#include#include #include #include #include #include #include #include #include int main(int argc, char* argv[]){ if(argc<=2){ printf("usage: %s ip_address port_numbern", 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 : %dn", errno); } else{ // 关闭标注文件描述符 close(STDOUT_FILENO); dup(connfd); printf("abcdn"); close(connfd); } close(sock); return 0; }
6.3 readv函数和writev函数
readv函数将数据从文件描述符读到分块的内存中,即分散读;writev函数将多块分散的内存一并写入文件描述符中,即集中写。
#include// fd是被操作的目标文件描述符,vector是iovec结构数组,count是vector数组长度 ssize_t readv(int fd, const struct iovec* vector, int count); ssize_t writev(int fd, const struct iover* vector, int count); // 成功时返回读出或写入的fd字节数,失败返回-1并设置errno
实例:Web服务器上的集中写
#include#include #include #include #include #include #include #include #include #include #include #include #define BUFFER_SIZE 1024 // 定义两种HTTP状态码和状态信息 static const char* status_line[2] = {"200 OK", "500 Internal server error"}; int main(int argc, char* argv[]){ if(argc<=3){ printf("usage: %s ip_address port_number filenamen", basename(argv[0])); return 1; } const char* ip = argv[1]; int port = atoi(argv[2]); const char* file_name = argv[3]; 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 %dn", errno); } else{ // 用于保存HTTP应答的状态行、头部字段和一个空行缓存区 char header_buf[BUFFER_SIZE]; memset(header_buf, ' ', BUFFER_SIZE); // 用于存放目标文件内容的应用程序缓存 char* file_buf; // 用于获取目标文件的属性,如目录、大小等 struct stat file_stat; // 记录目标文件是否是有效文件 bool valid = true; // 缓存区header_buf目前已经使用了多少字节空间 int len = 0; if(stat(file_name, &file_stat)<0){ // 目标文件不存在 valid = false; } else{ if(S_ISDIR(file_stat.st_mode)){ // 目标文件是目录 valid = false; } else if(file_stat.st_mode & S_IROTH){ //当前用户有读取目标文件的权限 // 动态分配缓存区file_buf int fd = open(file_name, O_RDONLY); file_buf = new char[file_stat.st_size+1]; memset(file_buf, ' ', file_stat.st_size+1); if(read(fd, file_buf, file_stat.st_size)<0){ valid = false; } } else{ valid = false; } } // 若目标文件有效,发送正常HTTP应答 if(valid){ // 将HTTP应答的状态行、头部字段、空行依次加入header_buf中 ret = snprintf(header_buf, BUFFER_SIZE-1, "%s %srn", "HTTP/1.1", status_line[0]); len += ret; ret = snprintf(header_buf+len, BUFFER_SIZE-1-len, "Content-Length: %drn",file_stat.st_size); len += ret; ret = snprintf(header_buf+len, BUFFER_SIZE-1-len, "%s", "rn"); // 利用writev将header_buf和file_buf内容一并写出 struct iovec iv[2]; iv[0].iov_base = header_buf; iv[0].iov_len = strlen(header_buf); iv[1].iov_base = file_buf; iv[1].iov_len = file_stat.st_size; ret = writev(connfd, iv, 2); } else{ // 目标文件无效 ret = snprintf(header_buf, BUFFER_SIZE-1, "%s %srn", "HTTP/1.1", status_line[1]); len += ret; ret = snprinrf(header_buf+len, BUFFER_SIZE-1-len, "%s", "rn"); send(connfd, header_buf, strlen(header_buf), 0); } close(connfd); delete [] file_buf; } close(sock); return 0; }
6.4 sendfile函数
sendfile函数在两个文件描述符之间传递数据,其完全在内核中操作,避免了内核缓冲区和用户缓冲区的数据拷贝,被称为零拷贝。
#include// in_fd:待读出内容文件描述符;out_fd:待写入内容文件描述符 // offset:指定从读入文件流那个位置开始读,默认未起始位置 // count:in_fd和out_fd之间传输的字节数 ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count); // 成功是返回传输的字节数,失败返回-1并设置额errno
实例:利用sendfile函数传输文件
#include#include #include #include #include #include #include #include #include #include int main(int argc, char* argv[]){ if(argc<=3){ printf("usage: %s ip_address port_number filenamen", 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(fildfd>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 : %dn", errno); } else{ sendfile(connfd, filefd, NULL, stat_buf.st_size); close(connfd); } close(sock); return 0; }
6.5 mmap函数和munmap函数
mmap函数用于申请一段内存空间,可将其作为进程间通信的共享内存,也可将文件直接映射到其中。munmap函数释放mmap创建的内存空间
#include// start:某个特定地址作为起始地址,若为NULL则自动分配 // length:指定内存段的长度 // prot:设置内存段的访问权限:PROT_READ(内存段可读)、PROT_WRITE(内存段可写)、PROT_EXEC(内存段可执行)、PROT_NONO(内存段不可被访问) // flags参数控制内存段内容被修改后的行为,如调为私有、共享等 // fd参数是被映射文件对应的文件描述符,通过open系统调用获得 // offset设置从文件何处开始映射 void* mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); // 成功返回指向目标内存区域的指针,失败返回MAP_FAILED((void*)-1)并设置errno int munmap(void *start, size_t length); // 成功返回0,失败返回-1并设置errno
6.6 splice函数
splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。
#include// fd_in:待输入数据的文件描述符; // off_in:当fd_in是管道文件描述符时,必须设为NULL;否则设置从何处开始读取数据,为NULL表示从当前偏移位置读取; // len:移动数据的长度 // flags:控制数据如何移动(SPLICE_F_MOVE, SPLICE_F_NONBLOCK, SPLICE_F_MORE, SPLICE_F_GIFT) ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags);
实例:使用splice函数实现的回射映射器
#include#include #include #include #include #include #include #include #include #include int main(int argc, char* argv[]){ if(argc<=2){ printf("usage: %s ip_address port_numbern", 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 : %dn", errno); } else{ int pipefd[2]; assert(ret!=-1); ret = pipe(pipefd); // 创建管道 // 将connfd上流入客户都数据定向到管道中 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; }
6.7 tee函数
tee函数在两个管道文件描述符之间复制数据,也是零零拷贝操作。
#includessize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
实例:同时输出数据到终端和文件的程序
#include #include#include #include #include #include int main(int argc, char* argv[]){ if(argc!=2){ printf("usage: %s 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); // 将标注输入内容输入管道pipefd_stdout ret = splice(STDIN_FILENO, NULL, pipefd_stdout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE); assert(ret != -1); // 将管道pipefd_stdout的输入复制到管道pipefd_file的输入端 ret = tee(pipefd_stdout[0], pipefd_file[1], 32768, SPLICE_F_NONBLOCK); assert(ret != -1); // 将管道pipefd_file的输入定向到文件描述符filefd上,将标准输入内容写入文件 ret = splice(pipefd_file[0], NULL, filefd, NULL, 32768, SPLICE_F_MORE|SPLICE_F_MOVE); assert(ret!=-1); // 将管道pipefd_stdout的输出定向到表专户出 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; }
6.8 fcntl函数
fcntl函数提供了对文件描述符的控制操作。
#includeint fcntl(int fd, int cmd, ...); // 将文件描述设置为阻塞 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; }



