高级I/O函数(一)

发布时间:2026/8/20 10:20:46
高级I/O函数(一) 引言本篇文章是开始深入了解一些Linux的高级API目的就是为了解决一些特别的问题参考书目为《Linux高性能服务器编程》所有的代码我会在github上面进行提交我github的仓库地址是fengyue05/Linux-: 本仓库里面会有关于《Linux高性能服务器编程》的一些主要代码实现pipeint pipe(int fd[2])函数pipe创建的两个文件描述符fd[0]和fd[1]分别构成管道的读端和写端往管道里面写入数据可以从fd[0]里面读出来。并且f[0]只能用来读取数据这些都不可以反过来使用。如果要实现双向传输那么就需要两个管道。默认情况下这两个文件描述符都是阻塞的所以如果我们使用read系统将会被阻塞直到管道里面有数据注意read/write是不是阻塞完全取决于文件描述符是不是阻塞如果我们使用write来向管道内写入数据如果管道已经满那么write就会被阻塞直到管道有足够多的空闲空间来写。那么管道可以写多少数据呢我们一般来说默认65536字节。但是如果我们设置读和写的文件描述符为非阻塞那么read和write就会有不同的行为。如果管道写端文件描述符fd[1]引用计数为0即没有任何进程可以往管道里面写入数据则读取到了文件结束表示EOF。如果读端的文件描述符fd[0]引用计数为0那么没有任何进程需要读取管道里面的数据所以针对管道写端的文件描述符fd[1]的操作write将失败并且引发SIGPIPE信号。管道内部传输的是字节流。dup和dup2函数我们有时候需要把标准输入重定向到一个文件里面或者把标准输出重定向到一个网络连接如CGI编程可以通过dup或者dup2函数来实现。CGI编程是指web调用外部程序来处理HTTP请求。dup是创建了一个新的文件描述符该新文件描述符和原本的文件描述符指向了同一个文件并且返回的文件描述符总是取系统当前可用最小的整数值。dup2和这个类似只是返回的文件描述符是自己指定的也就是newfd。int dup(int oldfd); int dup2(int oldfd, int newfd);我们实现一个基本的CGI服务器其中有几行关键的代码close(STDOUT_FILDNO); // 关闭标准输入 dup(connfd) // 复制一个socket文件描述符 printf(hello world); // 打印到了客户连接的fd上 close(connfd);我们来解释一下首先关闭标准输入fd也就是1然后复制客户的fd因为dup总是取系统当前可用最小的整数值所以就是1这么一来服务器标准输出的内容这里是“hello world”就会直接发送到客户连接的socket上因此printf调用的输出将被客户端获得。这个就是CGI服务器的原理~~~~集中写和分散读readv函数将数据从文件描述符读到分散的内存之中这个在陈硕老师的moduo库里面涉及到过就是buffer那个部分如果大家感兴趣可以看一下我之前的那个文章目的就是我们希望用更加节省的空间来处理那些动态的数据大小不确定可能有时候需要的空间比较少有的空间比较大。writev函数就是将多块分散的内存数据一并写入文件描述符内也就是集中写。其中有一个参数是count也就是有多少个内存数据需要从fd中读出或者写到fd。我们接下来就是写了一个web服务器的应答因为一个web服务器解析完一个HTTP请求之后如果目标文档存在且客户具有读取该文档的权限那么它就需要发送一个HTTP应答来传输该文档。这个HTTP应答包含1个状态行多个头部字段1个空行和文档的内容。其中前三个部分可能被web服务器放置在一块内存中而文档的内容一般都被读入到另一个单独的内存我们并不需要把这两个部分内容拼接到一起再发送而是可以使用writev将他们同时写出。代码有一些很好的写法我们可以学习一下在里面我们涉及到了一个对于目标文件的读取也就是stat结构体这个结构体我们通过stat函数来对其进行赋值然后文件的长度我们也可以就此获取。如果我们直接传入sizeof(file_buf)因为我们最初并不知道这个缓冲区的大小所以我们定义的是一个指针而指针的大小永远是4字节所以我们得到的sizeof永远是4字节而用stat就可以很好的得到这个缓冲区的大小。然后再写http头文件的时候大家一定要记得它的格式在拼接的时候我们要不断的对于buffer减去其已经加了的len所以当缓存空间不够的时候就会报错。最后在集中写的过程中就把对应的首地址传入iov_base里面最后writev就直接传入connfd里面。#define BUFFER_SIZE 1024 static const char* status_line[2] {200 OK, 500 Internal server error}; int main (int argc, char* argv[]) { if (argc 2) { return -1; } const char* ip argv[1]; int port atoi(argv[2]); const char* file_name argv[3]; sockaddr_in address; bzero(address, sizeof(address)); address.sin_family AF_INET; address.sin_port htons(port); inet_pton(AF_INET, ip, address.sin_addr); int sockfd ::socket(AF_INET, SOCK_STREAM, 0); assert(sockfd 0); int ret listen(sockfd, 128); assert(ret ! -1); sockaddr_in client; socklen_t len sizeof(client); int connfd accept(sockfd, (sockaddr*)client, len); if (connfd 0) { std::cout failed std::endl; } else { // 用于保存HTTP应答的状态行头部字段和一个空行的缓冲区 char header_buf[BUFFER_SIZE]; memset(header_buf, \0, BUFFER_SIZE); // 用于存放目标文件内容的应用程序缓存 char* file_buf; // 用于获取目标文件的属性比如目录文件大小 struct stat file_stat; // 记录目标文件是否是有效文件 bool valid true; // 缓冲区header_buf目前已经使用了多少字节的空间 int len_ret 0; // 第一个参数是文件名路径第二个参数是把文件的属性全部传入这个结构体里面 if (stat(file_name, file_stat) 0) { // 目标文件不存在 valid false; } else { // file_stat.st_mode 这个里面保存了文件的类型和权限 if (S_ISDIR(file_stat.st_mode)) { // 目标文件是一个目录 valid false; } else if (file_stat.st_mode S_IROTH) { // 当前用户有权利读取 // 动态分配缓存区file_buf并指定其大小为目标文件的大小file_stat.st_size 加1然后将目标文件读入缓存区file_buf中 int fd open(file_name, O_RDONLY); file_buf new char[file_stat.st_size 1]; memset(file_buf, \0, file_stat.st_size 1); if (read(fd, file_buf, file_stat.st_size 1) 0) { valid false; } } else { valid false; } } ret 0; // 如果目标文件有效那么发送正常的HTTP应答 if (valid) { // 下面这个部分将HTTP应答的状态行“Content-Length”头部字段和一个空行依次加入header_buf中 ret snprintf(header_buf, BUFFER_SIZE - 1, %s %s\r\n, HTTP/1.1, status_line[0]); len_ret ret; ret snprintf(header_buf len, BUFFER_SIZE - 1 - len, Content-Length: %d\r\n, static_castint(file_stat.st_size)); len ret; ret snprintf(header_buf, BUFFER_SIZE - len - 1, %s, \r\n); struct iovec iov[2]; iov[0].iov_base header_buf; iov[0].iov_len strlen(header_buf); iov[1].iov_base file_buf; iov[1].iov_len file_stat.st_size; ret ::writev(connfd, iov, 2); } else { ret snprintf(header_buf, BUFFER_SIZE - 1, %s %s\r\n, HTTP/1.1, status_line[1]); len ret; ret snprintf(header_buf len, BUFFER_SIZE - 1 - len, %s, \r\n); send(connfd, header_buf, strlen(header_buf), 0); } close(connfd); delete [] file_buf; } close(sockfd); return 0; }总结本篇文章到这里就结束了希望可以帮助大家理解~~~