其实看APUE时就想试着写些简单的stdio函数了,但是一直没实践,看到这里时发现书上写得不完整,便敲代码试了下。

第1个readline速度非常慢原因在于每次读取字符都执行了系统调用read(),而系统调用意味着内核态和用户态之间的切换,系统调用数量太多会导致切换过程非常费时。因此为了快速的进行I/O,往往会定义一个缓冲区,即第2个readline中的char read_buf[MAXLINE];以及记录读取数量的int read_cnt;和记录当前读取指针的char *read_ptr;这三个静态变量都是static类型,也就是当前文件可用,外部文件无法访问static变量。

static ssize_t my_read(int fd, char* ptr)
{
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}

关键在于用my_read来替换read。my_read是从缓冲区中(而不是从文件中)逐个读取数据。

由于一开始缓冲中什么都没有,对应的状态即read_cnt=0(读取数量为0),read_ptr指向read_buf首部。所以最开始需要填满缓冲区,读取MAXLINE(行缓冲区大小),将文件的数据读到缓冲区中。然后read_cnt初始化为read函数实际读取的数量。

比如我这里MAXLINE是4096,但是若文件实际总大小没有这么多,返回的read_cnt就小于MAXLINE。

然后read_cnt大于0就意味着缓冲区有未读的数据,于是每次调用my_read只需要从read_buf中读取1个字符,然后移动指针read_ptr到下个字符,并减少read_cnt。

由于read_cnt记录的是read函数实际读取的数量,所以归0时代表缓冲区元素已经读完了,需要再次调用read函数读取最多MAXLINE个字符。

这里采用了goto,原因就像书上所说的,在字节流套接字上调用read/write输入或输出的字节数可能比请求的数量少,此时并不一定是出错,而是因为内核中用于套接字的缓冲区已经到达了极限,需要再次(反复)调用read/write函数来输入或输出剩下的字节。

而errno被设置为EINTR的原因如下

EINTR  The call was interrupted by a signal before any data was read;

被信号打断,此时需要反复读取至成功为止。

原理说完了,测试代码如下

#define MAXLINE 4096

static int read_cnt = 0;
static char read_buf[MAXLINE];
static char* read_ptr = read_buf;
// ...
// UNPv3 P74-75的readline.c代码
// ...
int main(void)
{
char buf[MAXLINE];
for (int i = 0; i < 5; i++) {
ssize_t n = readline(STDIN_FILENO, buf, MAXLINE);
write(STDOUT_FILENO, buf, n);
}
return 0;
}

测试代码如上,从输入流中读取了5行并打印出来

用strace查看系统调用如下

可以发现只调用了1次read(不包含进入main前的部分),然后write了5次。

如果把if ((rc = my_read(fd, &c)) == 1) 改成if ((rc = read(fd, &c, 1)) == 1) 再调用strace

可见系统调用数量之多

Unix网络编程 3.9 readline函数的更多相关文章

  1. UNIX网络编程——getsockname和getpeername函数

    UNIX网络编程--getsockname和getpeername函数   来源:网络转载   http://www.educity.cn/linux/1241293.html     这两个函数或者 ...

  2. UNIX网络编程——UDP 的connect函数(改进版)

    上一篇我们提到,除非套接字已连接,否则异步错误是不会返回到UDP套接字的.我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三次握手.内核只是检查是否存在立即可 ...

  3. UNIX网络编程——send与recv函数详解

    #include <sys/socket.h> ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags); ssize_ ...

  4. UNIX网络编程学习指南--epoll函数

    epoll是select/poll的强化版,都是多路复用的函数,epoll有了很大的改进. epoll的功能 1.支持监听大数目的socket描述符 一个进程内,select能打开的fd是有限制的,有 ...

  5. UNIX网络编程——shutdown 与 close 函数 的区别

    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据 ...

  6. Unix 网络编程 dup和dup2函数

    dup和dup2也是两个很实用的调用,它们的作用都是用来复制一个文件的描写叙述符. 它们经经常使用来重定向进程的stdin.stdout和stderr.这两个函数的原形例如以下: #include & ...

  7. unix网络编程str_cli使用epoll实现

    unix网络编程str_cli使用epoll实现 unix环境高级编程中也有这个函数,都是为了讲解IO多路转接.从本质上来看epoll就是一个改善了的select和poll,本质没发生任何变化,对于构 ...

  8. UNIX网络编程——select函数的并发限制和 poll 函数应用举例

    一.用select实现的并发服务器,能达到的并发数,受两方面限制 1.一个进程能打开的最大文件描述符限制.这可以通过调整内核参数.可以通过ulimit -n来调整或者使用setrlimit函数设置,  ...

  9. 【unix网络编程第三版】阅读笔记(五):I/O复用:select和poll函数

    本博文主要针对UNP一书中的第六章内容来聊聊I/O复用技术以及其在网络编程中的实现 1. I/O复用技术 I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备就绪,它就通知该进程.I/O ...

随机推荐

  1. 004-对象——public protected private PHP封装的实例

    <?php /** *public protected private PHP封装的实例 */ /*class tv { private $shengyin; function __constr ...

  2. Yii ExtendedActiveRecord 增强版 ActiveRecord 增加多数据库连接绑定功能

    ExtendedActiveRecord 继承自 CActiveRecord,因此基础功能与 CActiveRecord 无异 为添加对多数据库连接的支持,增加了对 connectionName() ...

  3. 重温HTML

    1 <h1> </h1>标题标签 <p> </p>段落标签 <img src=“ ”>图片标签 2. <em>和<stro ...

  4. 嵌套类,PIMPL

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  5. Solr后台管理界面配置

    配置来源 https://stackoverflow.com/questions/28043957/how-to-set-apache-solr-admin-password 注意:配置用户名密码后 ...

  6. Qt出现QObject::connect: Cannot queue arguments of type '******'的解决方法

    一般出现这种情况都是自定义的类型进行型号槽连接的时候出现的,使用 假设自定义的类型是MyClass 使用qRegisterMetaType<MyClass>("MyClass&q ...

  7. LeetCode OJ:Search a 2D Matrix II(搜寻二维矩阵)

    Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...

  8. 使用jQuery操作DOM(2)

    1.获取设置属性值 attr({属性名1:属性值1,属性名2:属性值2}); //设置属性值 removeAttr(“属性名”); //删除属性 注意:如果元素没有此属性,会不起作用 2.获取同辈元素 ...

  9. Unity3D内存优化案例讲解

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解 ...

  10. phpstorm、webstorm配置less编译器

    1. node.js 安装包    https://nodejs.org/en/download/ 1) 安装js解析器node.js.直接下一步就ok了. 2) 将npm压缩包解压,找到里面的les ...