用RIO包健壮地读写

RIO 包全名为 Robust IO 函数包。包中函数是对 Linux 基本 I/O 函数的封装,使其更加健壮、高效,更适用于网络编程。具体来说,它会自动处理读写中的不足值情况。这种情况在网络应用中经常出现,因此编写网络程序时,我们经常用到它。

CSAPP 中实现的 RIO 提供了两类不同的函数:

  • 无缓冲的 I/O 函数。无应用级的缓冲,对二进制数据读写到网络和从网络读写到二进制数据尤为有用。
  • 有缓冲的 I/O 函数。从文本读取文本行和二进制,并会被缓存在应用级缓冲区中,该缓冲区是线性安全的。即同一个描述符上可以被交错地调用

此外,RIO 包还有一个重要的数据结构:rio_t

无缓冲区的 I/O 函数

rio_readnrio_writen,通常用于内存和文件之间直接传输数据。

ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);

rio_readn() 函数尝试从 fd 中读取 n 个字符到 usrbuf 中,与 read() 函数相比,它被信号处理函数中断后会再次尝试读取。因此,除了可读字符数小于 n 情况下,该函数可以保证读取 n 个字节。而若提取遇到 EOF ,就返回一个不足值。而 rio_writen() 绝不会返回不足值,对同一个描述符,交错使用 rio_readnrio_writen 是可以的。

下面是他们的具体实现。注意到,当中断产生后,rio_readn() 函数会自动开始下一轮循环,因为 errno == EINTRnread = 0,随后就直接开始下一轮循环,变量值都未受影响。它没有缓冲区,直接写入了目标地址,返回值是实际读取的字节数,若出错返回-1。

// rio_readn - Robustly read n bytes (unbuffered)
ssize_t rio_readn(int fd, void *usrbuf, size_t n) {size_t nleft = n;ssize_t nread;char *bufp = usrbuf;while (nleft > 0) {if ((nread = read(fd, bufp, nleft)) < 0) {if (errno == EINTR) /* Interrupted by sig handler return */nread = 0;        /* and call read() again */elsereturn -1; /* errno set by read() */} else if (nread == 0)break; /* EOF */nleft -= nread;bufp += nread;}return (n - nleft); /* Return >= 0 */
}

接下来看 rio_writen() 函数,若在写入时被中断,则 nwritten = 0,也会自动下一轮循环。除非出错返回 -1,否则 n 个字节一定会被全部写入,故一直返回 n

// rio_writen - Robustly write n bytes (unbuffered)
ssize_t rio_writen(int fd, void *usrbuf, size_t n) {size_t nleft = n;ssize_t nwritten;char *bufp = usrbuf;while (nleft > 0) {if ((nwritten = write(fd, bufp, nleft)) <= 0) {if (errno == EINTR) /* Interrupted by sig handler return */nwritten = 0;     /* and call write() again */elsereturn -1; /* errno set by write() */}nleft -= nwritten;bufp += nwritten;}return n;
}

注意一下出错信息 EINTR :通常,当一个进程在某个慢系统调用(可能是 readwrite 函数)中阻塞时,在捕获到某个信号,且该信号处理函数返回时,那么该慢系统调用会被中断,并返回错误 EINTR。在 GNU C 的文档中有非常棒的解释:

A signal can arrive and be handled while an I/O primitive such as open or read is waiting for an I/O device. If the signal handler returns, the system faces the question: what should happen next?

POSIX specifies one approach: make the primitive fail right away. The error code for this kind of failure is EINTR. This is flexible, but usually inconvenient. Typically, POSIX applications that use signal handlers must check for EINTR after each library function that can return it, in order to try the call again. Often programmers forget to check, which is a common source of error.

The GNU C Library provides a convenient way to retry a call after a temporary failure, with the macro TEMP_FAILURE_RETRY:

BSD avoids EINTR entirely and provides a more convenient approach: to restart the interrupted primitive, instead of making it fail. If you choose this approach, you need not be concerned with EINTR.


带有缓冲区的 RIO 函数

加入应用级缓冲区,其一目的是减少调用系统级 I/O 函数的次数,降低陷入内核态而带来的额外开销。在 Linux 中,readwrite 是系统级 I/O 函数。在使用系统级 I/O 函数时,进程会从用户态进入内核态,并读取文件中的数据。而从用户态切换到内核态,需要一定的性能开销:处理器要将返回地址(当前指令的下一条指令地址)和额外的寄存器等压入到内核栈中。此外,进程使用系统调用还可能会被抢占。当内核态中系统调用被阻塞,那么相应地,进程就会进入休眠状态,延长了等待时间;而若在系统调用期间出现了一个优先级更高的进程,则该进程可能会抢占系统调用,内核态会优先处理优先级高的进程,也增加了该进程的等待时间。

rio_t 数据结构

rio_t 是 RIO 包中最常用也是最基础的数据结构。它与一个文件描述符一一对应,即 rio_fd ,但在读/写该文件时,还加上了一个缓冲区 rio_buf ,并用指针 rio_bufptr 记录缓冲区当前读到的位置,用 rio_cnt 记录缓冲区中未读到的字节数。总的来说,它就是给文件描述符加一个缓冲区,所以我们在上一节不会用到它。

#define RIO_BUFSIZE 8192
typedef struct {int rio_fd;                /* Descriptor for this internal buf */int rio_cnt;               /* Unread bytes in internal buf */char *rio_bufptr;          /* Next unread byte in internal buf */char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;

在使用 rio_t 之前,需要先使用 rio_initb 函数,与文件描述符相连。

// rio_readinitb - Associate a descriptor with a read buffer and reset buffer
void rio_readinitb(rio_t *rp, int fd) {rp->rio_fd = fd;rp->rio_cnt = 0;rp->rio_bufptr = rp->rio_buf;
}

带缓冲区的读 API 函数

rio_read() 函数是对 Unix read() 函数的包装,读入的字节内容先存储在 rio_t 内部的缓冲区,然后根据需要一点点拿出,当缓冲区为空时,程序会一次性将缓冲区补满。若缓冲区不为空,那就使用 memcpy 移动 min(n, rio_cnt) 个字节给用户。该函数是

/***    rio_read - This is a wrapper for the Unix read() function that*    transfers min(n, rio_cnt) bytes from an internal buffer to a user*    buffer, where n is the number of bytes requested by the user and*    rio_cnt is the number of unread bytes in the internal buffer. On*    entry, rio_read() refills the internal buffer via a call to*    read() if the internal buffer is empty.*/
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n) {int cnt;/* 填充缓冲区,bufptr 指示数据用到哪了,cnt指示还剩多少 */while (rp->rio_cnt <= 0) { /* Refill if buf is empty */rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));if (rp->rio_cnt < 0) {if (errno != EINTR) /* Interrupted by sig handler return */return -1;} else if (rp->rio_cnt == 0) /* EOF */return 0;elserp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */}/* 如果缓冲区中还有内容, 那么直接运行下面代码,将 *//* min(n, rp->rio_cnt)个字节从内部缓冲区传给用户 */cnt = n;if (rp->rio_cnt < n)cnt = rp->rio_cnt;memcpy(usrbuf, rp->rio_bufptr, cnt);rp->rio_bufptr += cnt;rp->rio_cnt -= cnt;return cnt;
}

rio_readnb() 是带缓冲区的 read(),最多读取 n 个字节,读到 EOF 后退出,注意到该函数调用了 rio_read() 而不是 read()

// rio_readnb - Robustly read n bytes (buffered)
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) {size_t nleft = n;ssize_t nread;char *bufp = usrbuf;while (nleft > 0) {if ((nread = rio_read(rp, bufp, nleft)) < 0)return -1; /* errno set by read() */else if (nread == 0)break; /* EOF */nleft -= nread;bufp += nread;}return (n - nleft); /* return >= 0 */
}

rio_readlineb 函数用来读取每一行的文本,从实现看,它看似非常“低效”,因为它调用了 rio_read() 但每次却只读一个字节,直到读到 \n
才跳出循环,结束函数。但因为有缓冲区的存在,很多情况下其实不需要直接调用系统级 I/O,因此总体效率是可以接受的。rio_readlinebrio_readnb 都使用了 rio_t 带的缓冲区,因此就算这两个函数对一个 rio_t 交错调用,最终面对的都是同一个缓冲区,因此它们相互兼容,且线程安全。

// rio_readlineb - Robustly read a text line (buffered)
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) {int n, rc;char c, *bufp = usrbuf;for (n = 1; n < maxlen; n++) {if ((rc = rio_read(rp, &c, 1)) == 1) {*bufp++ = c;if (c == '\n') {n++;break;}} else if (rc == 0) {if (n == 1)return 0; /* EOF, no data read */elsebreak; /* EOF, some data was read */} elsereturn -1; /* Error */}*bufp = 0;return n - 1;
}

总的来说,对于一个应用程序,rio_read() 函数代替了 read() 函数,并具有与 read() 函数一样的语义。在出错时,会返回 -1,并设置 errno,在 EOF 时,返回 0,如果要求读的字节数超过了缓冲区的字计数,会返回不足值。

用RIO包健壮地读写相关推荐

  1. 健壮的网络编程IO函数-RIO包

    RIO包 简介 Rio包即为Robust io函数包.包中函数是对Linux基本I/O函数的封装,使其更加健壮.高效,更适用于网络编程. 分析 Rio包由rio_t结构体和系列函数组成. 首先是两个不 ...

  2. Linux IO操作——RIO包

    1.linux基本I/O接口介绍 ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, void *buf, siz ...

  3. 网络编程使用标准I/O的危险以及正确使用RIO包

    一.各IO包的关系 Unix I/O模型是在操作系统内核中实现的,应用程序可以通过诸如open.close.lseek.read.write和stat这样的函数来访问Unix I/O.较高级别的RIO ...

  4. linux的rio包在哪个头文件,[Linux] RIO C++封装

    RIO包是CSAPP中一个基于read和write函数实现的能够处理终端并提供了带缓冲区读取的IO包.代码很简单,但很巧妙. 缩减了原程序的初始化等函数之后,主要有以下四个对外函数: 1.ssize_ ...

  5. 健壮I/O读写函数——RIO包

    RIO(Robust I/O,健壮的I/O) 头文件说明 #include <unistd.h> //unistd.h 中所定义的接口通常都是大量针对系统调用的封装,如 fork.pipe ...

  6. PYTHON h5py库包安装及读写

    一.h5py库包安装 1. 在线安装 基本方法: cmd安装:pip install h5py pycharm环境:file-->setting-->PROJECT-->Projec ...

  7. 玩机搞机----mtk芯片机型 另类制作备份线刷包的方式 读写分区等等

    前面分享了几期高通和mtk芯片机型备份字库的几种方法教程.这些针对与很多没有线刷包资源的手机机型玩机操作.前面对接一个友商的mtk芯片杂牌机.和另外一个国外mtk芯片级都是来制作线刷包.因为,这些机型 ...

  8. 深入理解计算机系统——系统级I/O

    一.UNIX I/O     在UNIX系统中有一个说法,一切皆文件.所有的I/O设备,如网络.磁盘都被模型化为文件,而所有的输入和输出都被当做对相应文件的读和写来执行.这种将设备映射为文件的方式,允 ...

  9. 20145234黄斐《信息安全系统设计基础》第九周学习总结(课本部分)

    课本知识概述 I/O设备(输入input与输出output)是在主存和外部设备之间拷贝数据的过程 1.UNIX I/O 一个UNIX文件就是一个m个字节的序列:B0,B1,--,Bm-1: 执行方式: ...

最新文章

  1. 每日英语:Losing It at the Movies: Silly Beats Sober in China's Box Office
  2. SQL Server数据库重命名、数据导出等若干问题
  3. RxJS/Cycle.js 与 React/Vue 相比更适用于什么样的应用场景?
  4. 创建一个QT for Android的传感器应用应用程序(摘自笔者2015年将出的《QT5权威指南》,本文为试读篇)
  5. java list按照某个字段排序_java相关:List对象去重和按照某个字段排序的实现方法...
  6. 30个你必须记住的CSS选择符
  7. python实习目的_python爬虫系列---为什么要学习爬虫
  8. 多传感器数据标定融合完整教程:时间同步+空间同步(Camera+Lidar+IMU+Radar)
  9. gitlab查看word文档_gitlab迁移
  10. 注册电气工程师考试考取事宜与考试大纲
  11. vs201中debug和release两个版本的区别
  12. 计算机应用选购哪些电脑,如何选购笔记本电脑 购买笔记本的8个技巧
  13. 【笔记】一些Attention 方面的网络
  14. 新版标准日本语中级_第二十六课
  15. 王建农老师昆笛 + 简谱
  16. 牙菌斑、牙垢、牙结石、龋齿需要怎么来清洁
  17. 【志强课堂】每天晒图有什么作用?
  18. 东野圭吾梦幻花读后感_《梦幻花》小说在线阅读-东野圭吾
  19. html渐变颜色填充,在画布形状中填充渐变色
  20. 惠普179fnw打印机使用说明_惠普179fnw驱动下载-hp179fnw打印机驱动v1.14 官方版 - 极光下载站...

热门文章

  1. Linux命令行修改配置(待续)
  2. 不同区域多机房、多台设备、微信云平台集中监控管理方案
  3. 刷机时代落幕——刷机现状、原理及方法步骤
  4. elemtype到底是个啥?
  5. c语言上机考试指导,全国计算机二级C语言上机考试指导资料.doc
  6. thingsboard集群docker swarm部署报getsockopt failed strangely: Operation not permitted
  7. 分析Adobe Illustrator CC(AI)中的橡皮擦和直线工具
  8. STM32F103 CAN通信波特率的计算方法
  9. CSP-J2019第二轮 解题分析
  10. 所有网站都通用的自定义弹出框alert