C Linux read write function extension
前言 - 赠送 readn / writen
Linux 上默认的 read 和 write 函数会被信号软中断. 且 read 和 write 函数中第三个参数 count
#include <unistd.h> extern ssize_t read(int fd, void * buf, size_t count); extern ssize_t write(int fd, const void * buf, size_t count);
也会因内部缓冲机制, 不一定保证读取或写入到指定 count 大小数据.
这里将 read 和 write 拓展成 readn 和 writen
// // readn - 力求读取 n 个字节 // fd : 文件描述符 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // ssize_t readn(int fd, void * buf, size_t n) { size_t div = n; char * ptr = buf; while (div > 0) { ssize_t ret = read(fd, ptr, div); if (ret < 0) { if (errno == EINTR) continue; return -1; } if (ret == 0) break; ptr += ret; div -= ret; } return n - div; } // // writen - 力求写入 n 个字节 // fd : 文件描述符 // buf : 缓冲区 // n : 读取长度 // return : 返回写入长度, -1 标识错误, 默认 n // ssize_t writen(int fd, const void * buf, size_t n) { size_t div = n; const char * ptr = buf; while (div > 0) { ssize_t ret = write(fd, ptr, div); if (ret <= 0) { if (errno == EINTR) continue; return -1; } ptr += ret; div -= ret; } return n; }
有了这些收获, 不妨写个小测试
#include <stdio.h> #include <errno.h> #include <assert.h> #include <unistd.h> // // readn - 力求读取 n 个字节 // fd : 文件描述符 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // extern ssize_t readn(int fd, void * buf, size_t n); // // writen - 力求写入 n 个字节 // fd : 文件描述符 // buf : 缓冲区 // n : 读取长度 // return : 返回写入长度, -1 标识错误, 默认 n // extern ssize_t writen(int fd, const void * buf, size_t n); /* _oo0oo_ o8888888o 88" . "88 (| -_- |) 0\ = /0 ___/`---'\___ .' \\| |// '. / \\||| : |||// \ / _||||| -:- |||||- \ | | \\\ - /// | | | \_| ''\---/'' |_/ | \ .-\__ '-' ___/-. / ___'. .' /--.--\ `. .'___ ."" '< `.___\_<|>_/___.' >' "". | | : `- \`.;`\ _ /`;.`/ - ` : | | \ \ `_. \_ __\ /__ _/ .-` / / =====`-.____`.___ \_____/___.-`___.-'===== `=---=' */ int main(int argc, char * argv[]) { ssize_t ret = writen(STDOUT_FILENO, "12345\n1", 6); printf("ret = %ld\n", ret); char buf[4]; ret = readn(STDIN_FILENO, buf, 3); buf[3] = '\0'; printf("ret = %ld, buf = %s\n", ret, buf); return 0; }
一忧一喜皆心火,一荣一枯皆眼尘,静心看透炎凉事,千古不做梦里人。
聪明人,一味向前看;智慧人,事事向后看;聪明人,是战胜别人的人;智慧人,是战胜自己的人。
修心当以净心为要,修道当以无我为基。
过去事,过去心,不可记得;现在事,现在心,随缘即可;未来事,未来心,不必劳心。
正文 - 缓冲读
在了解 readn 套路基础上, 你是否有所想过那缓冲读写的实现思路呢. 这里不妨借用深入理解计算机系统
书中的思路实现一番.
struct rio { int fd; // 文件描述符 char * ptr; // 下一次读取缓冲池 buf 起点 ssize_t cnt; // 缓冲池 buf 字符数量 char buf[BUFSIZ]; // 缓冲池 }; // rio_init - rio 初始化 inline void rio_init(struct rio * r, int fd) { assert(r && fd >= 0); r->fd = fd; r->cnt = 0; r->ptr = r->buf; } // // readn - 力求读取 n 个字节 // r : 缓冲读取对象 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // extern ssize_t rio_readn(struct rio * r, void * buf, size_t n); // // rio_readline - 力求读取一行数据 // r : 缓冲读取对象 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // extern ssize_t rio_readline(struct rio * r, void * buf, size_t n);
实现了缓冲读固定字符和缓冲读一行. 额外的缓冲写也是相似的思路, 简单点写不了会进入写缓冲区,
可以当课外作业自行实现.
// rio_read - 带缓冲版本的 read static ssize_t rio_read(struct rio * r, void * buf, size_t n) { // 当缓冲区中没有数据, 我们重新填充缓冲区 while (r->cnt <= 0) { r->cnt = read(r->fd, r->buf, sizeof r->buf); if (r->cnt < 0) { if (errno == EINTR) continue; return -1; } // EOF 直接返回 if (r->cnt == 0) return 0; // 重新设置 buffer ptr r->ptr = r->buf; } // 尝试读取数据并返回 ssize_t cnt = r->cnt < n ? r->cnt : n; memcpy(buf, r->ptr, cnt); r->cnt -= cnt; r->ptr += cnt; return cnt; } // // readn - 力求读取 n 个字节 // r : 缓冲读取对象 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // ssize_t rio_readn(struct rio * r, void * buf, size_t n) { size_t div = n; char * ptr = buf; while (div > 0) { ssize_t ret = rio_read(r, ptr, div); if (ret < 0) return -1; if (ret == 0) break; ptr += ret; div -= ret; } return n - div; } // // rio_readline - 力求读取一行数据, 会吃掉最后一个 \n 字符 // r : 缓冲读取对象 // buf : 缓冲区 // n : 读取长度 // return : 返回读取的长度, -1 标识错误, < n 标识关闭, 默认 n // ssize_t rio_readline(struct rio * r, void * buf, size_t n) { size_t i; char * ptr = buf, c; for (i = 1; i < n; ++i) { ssize_t ret = rio_read(r, &c, 1); if (ret < 0) return -1; if (c == '\n' || ret == 0) break; *ptr++ = c; } *ptr = '\0'; return i - 1; }
缓冲写实战包装要复杂一点. 和业务绑定重(或者实现多策略的). 例如缓冲区满了这时候的策略就由业务
决定, 是缓冲区扩容, 还是等待下次写事件触发. 等等, 真实战场的缓冲读写需要具体场景和机器打配合,
来构造满意的读写策略.
后记 - 让水倒流