RIO(Robust I/O,健壮的I/O)
头文件说明
#include <unistd.h> //unistd.h 中所定义的接口通常都是大量针对系统调用的封装,如 fork、pipe 以及各种 I/O 原语(read、write、close 等)
#include <fcntl.h> //fcntl.h定义了很多宏和open、fcntl函数原型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> //dev_t 、pid_t、size_t等类型
#include <sys/stat.h> //文件的stat
#include <sys/socket.h> //涉及套接字通讯
#include <errno.h> //定义了通过错误码来回报错误资讯的宏
以下程序跟CSAPP源程序有一定的不同,CSAPP中的代码为阻塞读被我注释掉了。因为我在调试网络通讯阻塞读的时候,会由于通讯问题超时还是什么原因,具体是什么忘了,返回错误值。所以所有的读操作我改为非阻塞读。
需要注意的是程序接收到一些errno错误码后会退出,比如说阻塞读发生错误时,出现了程序退出的问题。
#define RIO_BUFSIZE ( 4096 )
typedef struct {
int rio_fd; //与内部缓冲区关联的描述符
int rio_cnt; //缓冲区中剩下的字节数
char *rio_bufptr; //指向缓冲区中下一个未读的字节
char rio_buf[RIO_BUFSIZE];
} rio_t;
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
无缓冲的输入输出函数
这些函数直接在存储器和文件之间传送数据,没有应用级缓冲。对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
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 */
} else {
return -1; /* errno set by read() */
}
}
else if (nread == 0) {
break;
}
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
}
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 ((nwritten = send(fd, bufp, nleft, 0)) <= MSG_DONTWAIT) { //非阻塞
if (errno == EINTR) { /* Interrupted by sig handler return */
nwritten = 0; /* and call write() again */
} else {
return -1; /* errno set by write() */
}
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
带缓冲的输入输出函数
这些函数高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区中,类似printf这种标准I/O提供的缓冲区。
这些函数是线程安全的。
这里引入线程安全和可重入概念的区别。
线程安全:如果一个函数在同一时刻可以被多个线程安全地调用,线程安全函数解决多个线程调用时访问共享资源的冲突问题。
使用全局变量的函数是非线程安全的,使用静态数据或其他共享数据必须加锁。
可重入:函数可以由多于一个线程并发使用。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。
可重入函数不能在内部使用,返回全局变量和静态数据,使用全局变量要用局部变量保护全局变量,不调用不可重入函数。
如果一个函数是可重入的,那么这个函数线程安全。如果一个函数线程安全不一定可重入。
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd;
rp->rio_cnt = 0;
rp->rio_bufptr = rp->rio_buf;
}
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
int 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)); //阻塞
rp->rio_cnt = recv(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf), MSG_DONTWAIT); //非阻塞
// rp->rio_cnt = recv(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf), 0); //非阻塞
if (rp->rio_cnt < 0) {
// if (errno != EINTR) { /* Interrupted by sig handler return */
// return -1;
// }
return 0;
}
else if (rp->rio_cnt == 0) { /* EOF */
return 0;
}
else {
rp->rio_bufptr = rp->rio_buf; /* Reset buffer ptr */
}
}
/* Copy min(n, rp->rio_cnt) bytes from internal buf to user buf */
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;
}
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') {
break;
}
} else if (rc == 0) {
if (n == 1) {
return 0; /* EOF, no data read */
}
else {
break; /* EOF, some data was read */
}
} else {
return -1; /* Error */
}
}
*bufp = 0;
return n;
}
摘自《深入理解计算机系统》