Robust I/O

本文最后更新于:2021年3月13日 晚上

概览:不足值、RIO、带缓存的读函数,错误处理包装函数,以及RIO代码下载。

源自CSAPP 第3版

不足值

在某些情况之下,linux函数readwrite传送的字节比实际参数的要求要少,这种情况就是不足值,short count,它不表示错误。

  • 读时遇到EOF。假设我们准备读一个文件,该文件从当前文件位置开始只含有20多个字节,而我们以50个字节的片进行读取。这样一来,下一个read返回的不足值为20,此后的read将通过返回不足值0来发出EOF信号。
  • 从终端读文本行。如果打开文件是与终端相关联的(如键盘和显示器),那么每个read函数将一次传送一个文本行,返回的不足值等于文本行的大小。
  • 读和写网络套接字(socket)。 如果打开的文件对应于网络套接字,那么内部缓冲约束和较长的网络延迟会引起read和write返回不足值。对Linux管道(pipe)调用read和write时,也有可能出现不足值。

实际上,除了EOF当你在读磁盘文件时,将不会遇到不足值,而且在写磁盘文件时,也不会遇到不足值。

然而,如果你想创建健壮的(可靠的)诸如Web服务器这样的网络应用,就必须通过反复调用read和write处理不足值,直到所有需要的字节都传送完毕。

RIO – Robust I/O 健壮的I/O包

RIO包可以可以自动处理上面描述的不足值。

RIO提供了两类不同的函数:

  • 无缓冲的输入输出函数。这些函数直接在内存和文件之间传送数据,没有应用级缓冲。它们对将二进制数据读写到网络和从网络读写二进制数据尤其有用。
  • 带缓冲的输入函数。这些函数允许你高效地从文件中读取文本行和二进制数据,这些文件的内容缓存在应用级缓冲区内,类似于为printf这样的标准I/O函数提供的缓冲区。带缓冲的RIO输人函数是线程安全的,它在同一个描述符上可以被交错地调用。例如,你可以从一个描述符中读一些文本行,然后读取一些二进制数据,接着再多读取一些文本行。

size_t 和 ssize_t

  • x86-64 size_t -- unsigned long,无符号数,最小为0.
  • x86-64 ssize_t -- longread返回为此,

RIO的无缓冲的输入输出函数

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

返回:若成功则返回传送的字节数,若为EOF则返回0(之对于rio_readn),若出错则返回-1.

实现:循环 + 中断处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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) /*errno -4 EINTR 中断系统调用*/
nread = 0; /* 重启read */
else
return -1; /* errno set by read() */
}
else if (nread == 0)
break; /* EOF */
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 (errno == EINTR) /*errno -4 EINTR 中断系统调用*/
nwritten = 0; /* 重启write */
else
return -1; /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}

rio_readn函数从描述符fd的当前文件位置最多传送n个字节到内存位置usrbuf。类似地,rio_ writen函数从位置usrbuf传送n个字节到描述符fd。

rio_ read函数在遇到EOF时只能返回一个不足值。rio_ writen 函数决不会返回不足值。对同一个描述符,可以任意交错地调用rio_ readnrio writen

注意,如果rio_readnrio_writen函数被一个从应用信号处理程序的返回中断,那么每个函数都会手动地重启read或write。为了尽可能有较好的可移植性,我们允许被中断的系统调用,且在必要时重启它们。

RIO的带缓冲的输入函数

1
2
3
4
void rio_readinitb(rio_t *rp, int fd) ;

ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n) ;
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen) ;

返回:若成功则返回传送的字节数,若为EOF则返回0,若出错则返回-1.

rio_readinitb函数的作用是将描述符fd和地址rp处的一个类型rio_t的读缓冲区联系起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define RIO_BUFSIZE 8192	//缓冲区8kb
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;

//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;
}

RIO读的核心函数rio_read

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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));
if (rp->rio_cnt < 0) { //当<0时说明read发生错误,若不是EINTR,则返回错误,若是则循环重启read
if (errno != EINTR) /* Interrupted by sig handler return */
return -1;
}
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;
}

rio_read是带缓冲的版本,rp->rio_cnt是表示缓冲区的未读字节数。如果缓冲区为空,则调用read来填充它,当然不一定能够填满。

一旦缓冲区不空的时候,就从缓冲区复制min(n, rp->rio_cnt)个字节到用户缓冲区,并返回最终复制的字节数。

rio_readread函数对于应用程序来说具有相同的语义:

  • 出错时,返回-1,并设置errno
  • EOF时,返回0
  • 若要求的字节数超出了都缓冲区内未读的字节数量时,返回不足值。

两者相似,但rio_read自带缓冲区,下述的程序基于rio_read函数创建。

rio_read函数引申

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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) //调用rio_read
return -1; /* errno set by read() */
else if (nread == 0)
break; /* EOF */

nleft -= nread;
bufp += nread;
}
return (n - nleft); /* return >= 0 */
}

//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 */
else
break; /* EOF, some data was read */
}
else
return -1; /* Error */
}
*bufp = 0;
return n - 1;//减去换行符
}

rio_ readlineb 程序最多调用maxlen-1rio_ read。每次调用都从读缓冲区返回一个字节,然后检查这个字节是否是结尾的换行符。

rio_ readnb 函数从文件rp最多读n个字节到内存位置usrbuf.对同一描述符,对rio_ readlinebrio_ readnb的调用可以任意交叉进行。然而,对这些带缓冲的函数的调用却不应和无缓冲的rio_ readn函数交叉使用。

  • 上述两个函数是线程安全的。

错误处理包装函数

当Unix系统级函数遇到错误时,它们通常会返回一1,并设置全局整数变量errno来表示什么出错了。程序员应该总是检查错误,但是不幸的是,许多人都忽略了错误检查,因为它使代码变得臃肿,而且难以读懂。

比如:

1
2
3
4
5
6
//打开文件
int fd = open("a.txt",O_RDONLY);

if(fd == -1){ //若没有打开
perror("open a.txt");
}

这样写比较繁琐,可以通过下面的错误报告函数以及错误处理包装函数来简化代码。

1
2
3
4
5
6
//错误报告函数
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}

错误处理包装函数,与基本函数具有相同参数,并调用基本函数,有任何错误就输出,并终止。

来自 CSAPP第三版 中文版,第8章 异常控制流 512页

1
2
3
4
5
6
7
8
9
int Open(const char *pathname, int flags)
{
int fd = open(pathname,flags);

if(fd == -1)
unix_error("open file");

return fd;
}

这样最上面打开文件的代码只需要一行调用:

1
int fd = Open("a.txt",O_RDONLY);

RIO代码

使用上面的错误处理包装函数,同时为了刨去CSAPP代码库中暂时用不到的代码,这里重新创建头文件和源文件,方便使用。

rio_csapp.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

/* Persistent state for the robust I/O (Rio) package */
/* $begin rio_t */
#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;
/* $end rio_t */

/* Our own error-handling functions */
void unix_error(char *msg);

/* Rio (Robust I/O) package */
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_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

/* Wrappers for Rio package */
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd);
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);


/* Unix I/O wrappers */
int Open(const char *pathname, int flags, mode_t mode);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
off_t Lseek(int fildes, off_t offset, int whence);
void Close(int fd);
int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
int Dup2(int fd1, int fd2);
void Stat(const char *filename, struct stat *buf);
void Fstat(int fd, struct stat *buf) ;

/* Directory wrappers */
DIR *Opendir(const char *name);
struct dirent *Readdir(DIR *dirp);
int Closedir(DIR *dirp);

rio_csapp.c

点击查看/关闭代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#include "rio_csapp.h"

/**************************
* Error-handling functions
**************************/
/* $begin unixerror */
void unix_error(char *msg) /* Unix-style error */
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
/* $end unixerror */


/****************************************
* The Rio package - Robust I/O functions
****************************************/

/*
* rio_readn - Robustly read n bytes (unbuffered)
*/
/* $begin rio_readn */
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 */ //errno -4 EINTR 中断系统调用
nread = 0; /* and call read() again */
else
return -1; /* errno set by read() */
}
else if (nread == 0)
break; /* EOF */
nleft -= nread;
bufp += nread;
}
return (n - nleft); /* Return >= 0 */
}
/* $end rio_readn */

/*
* rio_writen - Robustly write n bytes (unbuffered)
*/
/* $begin rio_writen */
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 */
else
return -1; /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
/* $end rio_writen */

/*
* 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.
*/
/* $begin rio_read */
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));
if (rp->rio_cnt < 0)
{
if (errno != EINTR) /* Interrupted by sig handler return */
return -1;
}
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;
}
/* $end rio_read */

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

/*
* rio_readnb - Robustly read n bytes (buffered)
*/
/* $begin rio_readnb */
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 */
}
/* $end rio_readnb */

/*
* rio_readlineb - Robustly read a text line (buffered)
*/
/* $begin rio_readlineb */
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 */
else
break; /* EOF, some data was read */
}
else
return -1; /* Error */
}
*bufp = 0;
return n - 1;
}
/* $end rio_readlineb */

/**********************************
* Wrappers for robust I/O routines
**********************************/
ssize_t Rio_readn(int fd, void *ptr, size_t nbytes)
{
ssize_t n;

if ((n = rio_readn(fd, ptr, nbytes)) < 0)
unix_error("Rio_readn error");
return n;
}

void Rio_writen(int fd, void *usrbuf, size_t n)
{
if (rio_writen(fd, usrbuf, n) != n)
unix_error("Rio_writen error");
}

void Rio_readinitb(rio_t *rp, int fd)
{
rio_readinitb(rp, fd);
}

ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
ssize_t rc;

if ((rc = rio_readnb(rp, usrbuf, n)) < 0)
unix_error("Rio_readnb error");
return rc;
}

ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
ssize_t rc;

if ((rc = rio_readlineb(rp, usrbuf, maxlen)) < 0)
unix_error("Rio_readlineb error");
return rc;
}

/********************************
* Wrappers for Unix I/O routines
********************************/

int Open(const char *pathname, int flags, mode_t mode)
{
int rc;

if ((rc = open(pathname, flags, mode)) < 0)
unix_error("Open error");
return rc;
}

ssize_t Read(int fd, void *buf, size_t count)
{
ssize_t rc;

if ((rc = read(fd, buf, count)) < 0)
unix_error("Read error");
return rc;
}

ssize_t Write(int fd, const void *buf, size_t count)
{
ssize_t rc;

if ((rc = write(fd, buf, count)) < 0)
unix_error("Write error");
return rc;
}

off_t Lseek(int fildes, off_t offset, int whence)
{
off_t rc;

if ((rc = lseek(fildes, offset, whence)) < 0)
unix_error("Lseek error");
return rc;
}

void Close(int fd)
{
int rc;

if ((rc = close(fd)) < 0)
unix_error("Close error");
}

int Select(int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
int rc;

if ((rc = select(n, readfds, writefds, exceptfds, timeout)) < 0)
unix_error("Select error");
return rc;
}

int Dup2(int fd1, int fd2)
{
int rc;

if ((rc = dup2(fd1, fd2)) < 0)
unix_error("Dup2 error");
return rc;
}

void Stat(const char *filename, struct stat *buf)
{
if (stat(filename, buf) < 0)
unix_error("Stat error");
}

void Fstat(int fd, struct stat *buf)
{
if (fstat(fd, buf) < 0)
unix_error("Fstat error");
}

/*********************************
* Wrappers for directory function
*********************************/

DIR *Opendir(const char *name)
{
DIR *dirp = opendir(name);

if (!dirp)
unix_error("opendir error");
return dirp;
}

struct dirent *Readdir(DIR *dirp)
{
struct dirent *dep;

errno = 0;
dep = readdir(dirp);
if ((dep == NULL) && (errno != 0))
unix_error("readdir error");
return dep;
}

int Closedir(DIR *dirp)
{
int rc;

if ((rc = closedir(dirp)) < 0)
unix_error("closedir error");
return rc;
}

案例–复制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "rio_csapp.h"
#include <time.h>

int main(){

/*打开文件*/
int fd =Open("english.txt",O_RDONLY,0);

/*读取第一行数据并输出*/
rio_t rio;
Rio_readinitb(&rio,fd);//结构体绑定

int n;//每一行的数量

char usrbuf[RIO_BUFSIZE];
n = Rio_readlineb(&rio,usrbuf,RIO_BUFSIZE);

printf("english.txt first line: %s \n",usrbuf);

/*复制整个文件到另一个文件中*/
int newfd = Open("englishcopy.txt",O_RDWR|O_CREAT,0664);

//将第一行内容先写入
Rio_writen(newfd,usrbuf,n);

while((n = Rio_readlineb(&rio,usrbuf,RIO_BUFSIZE))!=0)
Rio_writen(newfd,usrbuf,n);

/*向第二个文件额外输入内容--当前时间*/
time_t nowtime;
time(&nowtime);

char buf[256];
sprintf(buf,"Copy file time: %s",ctime(&nowtime));
Rio_writen(newfd,buf,strlen(buf));

//关闭文件
Close(fd);
Close(newfd);

}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!