网络编程使用标准I/O的危险以及正确使用RIO包
一、各IO包的关系
Unix I/O模型是在操作系统内核中实现的,应用程序可以通过诸如open、close、lseek、read、write和stat这样的函数来访问Unix I/O。较高级别的RIO和标准I/O函数都是基于Unix I/O函数来实现的。
RIO函数它们自动处理不足值,并且为读文本行提供了一种高效的带缓冲的方法。标准I/O函数提供了Unix I/O函数的一个更加完整的带缓冲的替代品,包括格式化的I/O例程,如printf和scanf。
二、网络编程不应该使用标准I/O
通过一个例子可以看到在网络编程中使用标准I/O的危险性。
简而言之,问题出在标准I/O的函数自动执行的缓冲上面。
三、RIO包
RIO,全称 Robust I/O,即健壮的IO包。它提供了与系统I/O相似的函数接口,在读取操作时,RIO包添加了读缓冲区,一定程度上添加了程序的读取效率。另外,带缓冲的输入函数是线程安全的。这与Stevens的 UNP 3rd Edition(中文版) P74 中介绍的那个输入函数不同。UNP的那个版本号的带缓冲的输入函数的缓冲区是以静态全局变量存在。所以对于多线程来说是不可重入的。RIO包中有专门的数据结构为每个文件描写叙述符都分配了相应的独立的读缓冲区,这样不同线程对不同文件描写叙述符的读訪问也就不会出现并发问题(然而若多线程同一时候读同一个文件描写叙述符则有可能发生并发访问问题。须要利用锁机制封锁临界区)。
另外,RIO还帮助我们处理了可修复的错误类型:EINTR。考虑read和write在堵塞时被某个信号中断,在中断前它们还未读取/写入不论什么字节,则这两个系统调用便会返回-1表示错误,并将errno置为EINTR。这个错误是能够修复的。而且应该是对用户透明的。用户无需在意read 和 write有没有被中断。他们仅仅须要直到read 和 write成功读取/写入了多少字节,所以在RIO的rio_read()和rio_write()中便对中断进行了处理。
#define RIO_BUFSIZE 4096
typedef struct
{int rio_fd; //与缓冲区绑定的文件描写叙述符的编号int rio_cnt; //缓冲区中还未读取的字节数char *rio_bufptr; //当前下一个未读取字符的地址char rio_buf[RIO_BUFSIZE];
}rio_t;
这个是rio的数据结构,通过rio_readinitb(rio_t *, int)能够将文件描写叙述符与rio数据结构绑定起来。注意到这里的rio_buf的大小是4096,为linux中文件的块大小。
void rio_readinitb(rio_t *rp, int fd)
/*** @brief rio_readinitb rio_t 结构体初始化,并绑定文件描写叙述符与缓冲区** @param rp rio_t结构体* @param fd 文件描写叙述符*/
{rp->rio_fd = fd;rp->rio_cnt = 0;rp->rio_bufptr = rp->rio_buf;return;
}
static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
/*** @brief rio_read RIO--Robust I/O包 底层读取函数。当缓冲区数据充足时,此函数直接拷贝缓* 冲区的数据给上层读取函数。当缓冲区不足时,该函数通过系统调用* 从文件里读取最大数量的字节到缓冲区,再拷贝缓冲区数据给上层函数** @param rp rio_t,里面包括了文件描写叙述符和其相应的缓冲区数据* @param usrbuf 读取的目的地址* @param n 读取的字节数量** @returns 返回真正读取到的字节数(<=n)*/
{int cnt;while(rp->rio_cnt <= 0) {rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));if(rp->rio_cnt < 0){if(errno != EINTR) //遇到中断类型错误的话应该进行读取,否则就返回错误return -1;}else if(rp->rio_cnt == 0) //读取到了EOFreturn 0;elserp->rio_bufptr = rp->rio_buf; //重置bufptr指针,令其指向第一个未读取字节,然后便退出循环}cnt = n;if((size_t)rp->rio_cnt < n) cnt = rp->rio_cnt;memcpy(usrbuf, rp->rio_bufptr, n);rp->rio_bufptr += cnt; //读取后须要更新指针rp->rio_cnt -= cnt; //未读取字节也会降低return cnt;
}ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
/*** @brief rio_readnb 供用户使用的读取函数。从缓冲区中读取最大maxlen字节数据** @param rp rio_t,文件描写叙述符与其相应的缓冲区* @param usrbuf void *, 目的地址* @param n size_t, 用户想要读取的字节数量** @returns 真正读取到的字节数。读到EOF返回0,读取失败返回-1。*/
{size_t leftcnt = n;ssize_t nread;char *buf = (char *)usrbuf;while(leftcnt > 0){if((nread = rio_read(rp, buf, n)) < 0){if(errno == EINTR) //事实上这里能够不用推断EINTR,rio_read()中已经对其处理了nread = 0;else return -1;}leftcnt -= nread;buf += nread;}return n-leftcnt;
}ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
/*** @brief rio_readlineb 读取一行的数据,遇到'\n'结尾代表一行** @param rp rio_t包* @param usrbuf 用户地址,即目的地址* @param maxlen size_t, 一行最大的长度。若一行数据超过最大长度,则以'\0'截断** @returns 真正读取到的字符数量*/
{size_t n;int rd;char c, *bufp = (char *)usrbuf;for(n=1; n<maxlen; n++) //n代表已接收字符的数量{if((rd=rio_read(rp, &c, 1)) == 1){*bufp++ = c;if(c == '\n')break;}else if(rd == 0) //没有接收到数据{if(n == 1) //假设第一次循环就没接收到数据。则代表无数据可接收return 0;elsebreak;}else return -1;}*bufp = 0;return n;
}ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{size_t nleft = n;ssize_t nwritten;char *bufp = (char *)usrbuf;while(nleft > 0){if((nwritten = write(fd, bufp, nleft)) <= 0){if(errno == EINTR)nwritten = 0;elsereturn -1;}bufp += nwritten;nleft -= nwritten;}return n;
}
以上便是rio的基本输入输出函数。
注意到rio_writen(int fd, void *, size_t)代表文件描写叙述符的參数是int类型。而不是rio_t类型。
由于rio_writen不须要写缓冲。
这是为什么呢?按道理来说,既然我们为read封装的rio_readn提供了缓冲区,为什么不也为write提供一个有缓冲的rio_writen函数呢?
试想一个场景。你正在写一个http的请求报文,然后将这个报文写入了相应socket的文件描写叙述符的缓冲区。假设缓冲区大小为8K。该请求报文大小为1K。那么,假设缓冲区被设置为被填满才会自己主动将其真正写入文件(而且一般也是这样做的)。那就是说假设没有提供一个刷新缓冲区的函数手动刷新,我还须要额外发送7K的数据将缓冲区填满。这个请求报文才干真正被写入到socket其中。所以。一般带有缓冲区的函数库都会一个刷新缓冲区的函数,用于将在缓冲区的数据真正写入文件其中。即使缓冲区没有被填满,而这也是C标准库的做法。然而,假设一个程序猿一不小心忘记在写入操作完毕后手动刷新。那么该数据(请求报文)便一直驻留在缓冲区,而你的进程还在傻傻地等待响应。
RIO函数提供了带缓冲的读操作,与无缓冲的写操作(对于套接字来说不须要),且是线程安全的。
通过RIO包的学习,理解底层Unix I/O的实现也能更好避免在使用上层IO接口中犯错。
参考文献
《深入理解计算机系统》
《Unix网络编程卷1第三版》
https://www.cnblogs.com/wzzkaifa/p/7281005.html
网络编程使用标准I/O的危险以及正确使用RIO包相关推荐
- Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象
一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字server.bind() #把地址绑定到套接字,网络地址加端口server.liste ...
- 【Linux网络编程】原始套接字实例:发送 UDP 数据包
以太网报文格式: 详细的说明,请看<MAC 头部报文分析>. IP 报文格式: 详细的说明,请看<IP 数据报格式详解>. UDP 报文格式: 详细的说明,请看<UDP ...
- 【Java 网络编程】UDP 服务器 客户端 通信 ( DatagramSocket | DatagramPacket | UDP 发送数据包 | UDP 接收数据包 | 端口号分配使用机制 )
文章目录 I UDP 信息发送接收原理 II UDP 发送和接收端口相同 III UDP 发送信息代码示例 IV UDP 接收信息代码示例 V UDP 服务器端代码示例 VI UDP 客户端代码示例 ...
- C++ 网络编程下的socket编程(TCP\UDP),连接下位机
正常情况下我们需要对下位机进行通信需要使用Socket进行连接操作,而在网络编程中又分为面向连接(TCP)和面向无连接(UDP)这两种,针对这两种方式,我们不做具体的原理解释,只说各自的特点和各自的应 ...
- java基于http协议编程_Java中基于HTTP协议网络编程
java中为我们的网络支持提供了java.net包,能够使我们以编程的方式来访问Web服务功能,这篇博客,就跟大家分享一下,Java中的网络编程的知识,主要是学习下该java.net包下的API. U ...
- java学习-网络编程
一.前期配置:安装tomcat服务器 现代的应用程序都离不开网络,网络编程是非常重要的技术.Java SE提供java.net包,其中包含了网络 编程所需要的最基础一些类和接口.这些类和接口面向两个不 ...
- Web网络编程第一次试验:ASP.NET4.5标准控件和窗体验证
Web网络编程第一次试验:ASP.NET4.5标准控件和窗体验证 前言 一.实验目的 二.实验环境 三.实验内容 四.代码及截图 五.实验总结 前言 为了帮助同学们完成痛苦的实验课程设计,本作者将其作 ...
- android xml java混合编程_Java学习中注解与多线程,网络编程与XML技术
本部分内容主要有集合框架及泛型,实用类,输入和输出处理,注解与多线程,网络编程与XML技术.初次学习这部分会感觉很难,主要是概念难于理解,最好是多看看例子,多练习.下面是个人的总结 拉勾IT课小编为大 ...
- 手把手叫你玩转网络编程系列之三 完成端口(Completion Port)详解
2019独角兽企业重金招聘Python工程师标准>>> 前 言 本系列里完成端口的代码在两年前就已经写好了,但是由于许久没有写东西了,不知该如何提笔,所以这篇文档总是在酝酿之中--酝 ...
最新文章
- kafka 异步发送阻塞_Kafka学习一
- 跨站请求伪造CSRF
- zabbix 监控CDN带宽
- 苹果:AMD yes!官方开售显卡模块,价格1.8万起
- MySQL 数据库利用alter语句修改表字段属性实例演示,如何拓展表字段长度,sql语句修改表字段名称和类型
- 大学必须掌握的计算机技巧,大学生必须掌握的电脑办公技巧
- 老鼠走迷宫php算法,C语言经典算法 - 老鼠走迷官(一)
- windows server 2019 服务器搭建的方法步骤(图文)
- 同一解决方案内的多个项目之间如何引用?
- 1 Oracle数据库常用命令
- 什么是「数独」,简单介绍
- vue2+vuex+vue-router 快速入门(三) vue 实例介绍
- Android setBackgroundDrawable()过时解决办法
- 计算机操作基本技能知识,计算机基本操作技能考核知识点
- canvas实现圆饼图,柱状图,折线图(满满的干货)
- 左耳朵耗子:程序员练级攻略
- 使用JavaScript获取设备屏幕的宽度
- 华为ensp 和secureCRT 连接
- 嵌入式开发—C语言面试题
- java getmethods_java中Class.getMethods()和Class.getDeclaredMethods()方法的区别
热门文章
- 机械与计算机大一学的一样吗,机械设计制造及其自动化专业各校大一新生学的课程一样吗...
- hiper – web_Web设计行业分析–专业人士与业余者[信息图]
- 索尼sony xperia xc刷安卓7、8,解锁root教程
- m35c android 4.4,手机实时动态:评测:索尼M35c的整体性能简介及性价比如何
- ClickHouse副本表ReplicatedMergeTree实操
- 输入一个日期判断是当年的第几天(C语言)
- 站内搜索 迅搜xunsearch 中小型网站的福音
- PAT A 1128 1129 1130 1131
- 用UltraISO轻松制作CentOS 6.7 U盘引导安装盘
- foc学习笔记3——电流环