网站的icp备案信息是什么,自媒体平台注册完怎么赚大钱,网页模板网站有哪些,合肥网站开发招聘文章目录 1. 什么是IO#xff1f;什么是高效 IO? 2. IO的五种模型五种IO模型的概念理解同步IO与异步IO整体理解 3. 阻塞IO4. 非阻塞IOsetnonblock函数为什么非阻塞IO会读取错误#xff1f;对错误码的进一步判断检测数据没有就绪时#xff0c;返回做一些其他事情完整代码myt… 文章目录 1. 什么是IO什么是高效 IO? 2. IO的五种模型五种IO模型的概念理解同步IO与异步IO整体理解 3. 阻塞IO4. 非阻塞IOsetnonblock函数为什么非阻塞IO会读取错误对错误码的进一步判断检测数据没有就绪时返回做一些其他事情完整代码mytest.ccmakefile 5. select为什么要有selectselect 接口第一个参数 nfds的理解什么是 输入 输出型参数最后一个参数 timeout 的理解readfds writefds exceptfds 参数的理解select的返回值 1. 什么是IO
IO表示 输入 输出 当对方把连接建立好但是不发数据 而我是一个线程正在调用 read 来读就会阻塞一直等数据发送过来 即读取条件不满足的情况下read或recv 只会等待 无论是有数据时的拷贝 还是没有数据时的等待 两者的时间成本全都算到了用户头上
在用户的角度IO 等数据拷贝
什么是高效 IO? 单位时间内 等的比重越低 IO效率越高 当IO条件满足时称为 IO事件就绪
2. IO的五种模型
五种IO模型的概念理解 如钓鱼假设分为两步 钓鱼 等 钓 在鱼竿的钩子上挂一个鱼漂浮在水面上用来恒定鱼竿下水的深度 当鱼漂上下摆动时就可以知道当前有鱼上钩了 1. 张三一般喜欢专注于一件事 所以当张三钓鱼等待时就会一直盯着鱼漂看 是否有鱼上钩 过了一段时间鱼漂动了张三拉动鱼竿将鱼钓上来了放入桶中 继续刚才钓鱼的动作 张三在钓鱼的过程中只盯着鱼漂看不干其他事情 2. 李四天生好动 所以当李四钓鱼时就不怎么看鱼漂会左顾右看的张望 当发现鱼咬钩后就把鱼钓上来放入桶中 继续刚才钓鱼的动作 李四在钓鱼的过程中除了看鱼漂还做其他事情 3. 王五比较特别 当王五钓鱼时在鱼竿的顶部放上一个铃铛等待鱼上钩 等待过程中王五做着自己的事情 当王五听到铃铛响了时就拉动鱼竿将鱼钓上来了放入桶中 继续刚才钓鱼的动作 王五在钓鱼的过程中不看鱼漂只听铃铛来判断是否有鱼上钩 4. 赵六是周围的首富开皮卡来钓鱼皮卡上装了10根鱼竿 (首富是来体验生活的) 使用10根鱼竿一起钓鱼赵六就从前往后 依次查看 是否有鱼漂在动 5. 田七是方圆500公里的首富 (田七比赵六有钱) 田七很忙每天都有各种会议要开而且田七并不是想钓鱼而是喜欢吃鱼 所以就 让司机小王帮忙去钓鱼 当鱼桶满了后给田七打电话就会来人把鱼带走 小王在钓鱼时田七也正在开会 张三和李四钓是一样的差别在等待鱼上钩的方式不同 张三为 鱼漂不动他不动 李四为 鱼漂不动会立马返回去做其他事情 张三的钓鱼方式 称为 阻塞IO (数据没有就绪调用的read接口不会返回) 李四的钓鱼方式 称为 非阻塞IO (检测一次若没有数据则会立马返回过一段时间可以再次检测) 王五在鱼还没有钓上来之前就知道当铃铛响了就应该拉动鱼竿 王五的钓鱼方式 称为 信号驱动IO 赵六一次管理多个鱼竿赵六的钓鱼方式称为 多路复用或多路转接
在这几个人中赵六的钓鱼效率比较高 因为赵六的鱼竿比较多所以鱼上钩的概率大 即等待时间比较短 所以赵六的钓鱼效率比较高 同步IO与异步IO
前四个人都要钓鱼的过程所以都称为 同步IO
田七没有参与钓鱼的过程没有等 也没有钓只是 发起钓鱼的过程 田七的钓鱼方式 称为 异步IO 整体理解 钓 可以看作 数据拷贝 张三 李四 等人 可以看作 进程 田七可以看作是一个进程司机小王可以看作是操作系统 鱼竿可以看作 文件描述符 鱼 可以看作是 数据 鱼咬钩 或 鱼漂动 、铃铛响 可以看作 IO事件就绪 一个进程 在文件描述符上读取数据时若数据没有就绪当前进程只能挂起等待 直到有IO时间就绪数据才可以拷贝到对应的上层
3. 阻塞IO
阻塞IO数据没有就绪调用的read接口不会返回 通过使用 read 函数 从键盘中读当代码写好时若什么也不输入则什么也不显示 则为阻塞IO
输入 man 2 read 从一个文件描述符 中 去读count个数据 到 buf缓冲区中 若获取成功则返回 字节数据 若获取 为0则表示读到文件结尾 若获取为-1则表示失败并设置错误码 0表示标准输入流 从标准输入流中 读buffer数组大小的数据 发送到 buffer中 运行可执行程序后一直不输入则导致read在等待直到有数据输入才进行数据拷贝
4. 非阻塞IO 非阻塞IO检测一次若没有数据则会立马返回 做其他事情过一段时间可以再次检测 通过使用 read 函数 从键盘中读当代码写好时就是不输入 通过这样的方式模拟读取条件不满足的情况下read只会等待的情况
在上述阻塞IO的代码的基础上 进行修改 setnonblock函数
输入 man fcntl 第一个参数为 文件描述符 第二个参数 表示 你要对文件描述符干什么 获得/设置文件状态标记(cmdF_GETFL或F_SETFL)
通过设置文件状态标记就可以将一个文件描述符 变为 非阻塞
使用 F_GETFL将当前文件描述符的属性取出来 使用 F_SETFL将文件描述符 状态进行设置并加上一个 O_NONBLOCK (非阻塞) 参数
若函数返回 -1则表示失败 创建一个函数 setnonblock将文件描述符设置为非阻塞状态 先使用F_GETFL获取对应文件描述符的属性 若获取失败则返回错误原因和错误码 若获取成功则使用 F_SETFL 将文件描述符状态设为非阻塞状态 为什么非阻塞IO会读取错误 在主函数main中将标准输入流改为非阻塞状态 并根据read的三种返回值分别设置 返回提示 读取成功、文件结尾 和 读取错误 当将标准输入流设置为非阻塞状态后 再次运行可执行程序直接就会读取失败 在调用read时发现数据没有就绪 (当前读取检测速度太快还没有输入就报错了)
所以一旦底层数据没有就绪就以出错的形式返回但是不算真正的出错 但这样就没办法区分是真正出错还是 底层没有数据了 所以就通过出错码 进行进一步判断
对错误码的进一步判断 EAGAIN 和 EWOULDBLOCK 都是系统设置的错误码都是11 用于判断没出错但是以出错的形式返回 的错误码 若为真则下次继续检测即可 若IO被信号中断则重新检测 检测数据没有就绪时返回做一些其他事情
非阻塞IO是可以做到 当检测数据没有就绪 时就返回做一些其他事情 定义一个 包装器 其参数为void 返回值为void 并将其重命名为 func_t 类型 定义一个vetcor数组 其类型为 func_t 设置三个任务分别为PrintLog OperMysql CheckNet 在创建LoadTask函数将任务分别插入到funcs数组中 在主函数main中调用 LoadTask函数 以此加载任务 创建一个 HandlerALLTask函数用于遍历 vector数组 数组元素为任务 当数据没有就绪时就返回 处理任务 完整代码
mytest.cc
#includeiostream
#includeunistd.h
#includefcntl.h
#includecstdio
#includecstring
#includevector
#includefunctional
using namespace std;//任务
void PrintLog()//打印日志
{cout这是一个打印日志例程endl;
}void OperMysql()
{cout这是一个操作数据库的例程endl;
}void CheckNet()
{cout这是一个检测网络状态的例程endl;
}using func_t functionvoid(void);
vectorfunc_t funcs;void LoadTask()
{funcs.push_back(PrintLog);funcs.push_back( OperMysql);funcs.push_back(CheckNet);
}void HandlerALLTask()
{//遍历vector数组for(auto func:funcs){func();}
}void SetNonBlock(int fd)//将文件描述符设为非阻塞
{int flfcntl(fd,F_GETFL);//获取当前文件描述符的指定状态标志位if(fl0)//获取失败{cerrerror string: strerror(errno)error code: errnoendl;}fcntl(fd,F_SETFL,fl | O_NONBLOCK);//将文件描述符状态设为非阻塞状态
}int main()
{char buffer[64];SetNonBlock(0);//将标准输入流 改为非阻塞状态LoadTask();//加载任务while(true){//0表示标准输入流ssize_t nread(0,buffer,sizeof(buffer)-1);//检测条件是否就绪if(n0)//读取成功{buffer[n-1]0; coutecho# bufferendl; }else if(n0)//读到文件结尾{coutend fileendl;}else//读取失败 {if(errnoEAGAIN || errno EWOULDBLOCK){//若为真说明没出错只是以出错返回//底层数据没有准备好下次继续检测HandlerALLTask();//遍历数组 处理任务sleep(1);coutdata not readyendl;continue;}else if(errno EINTR){//IO被信号中断需要重新检测continue;}else //真正的错误{coutread errorerror string: strerror(errno)error code: errnoendl;break;}}sleep(1);}return 0;
}
makefile
mytest:mytest.ccg -o $ $^ -stdc11.PHONY:clean
clean:rm -f mytest5. select
为什么要有select
read/recv 等 文件接口只有一个文件描述符 想要 让一个接口等待多个文件描述符而read等接口是不具备这个能力的 操作系统就设计一个接口 select用于多路复用 select 作用 1.等待多个文件描述符 2.只负责等(没有数据拷贝的能力) select 接口
输入 man select 由于select只负责等待不负责拷贝所以没有缓冲区
第一个参数 nfds的理解
第一个参数 nfds是一个输入型参数 表示 select等待的多个文件描述符(fd)数字层面 最大的1 (文件描述符的本质为 数组下标多个文件描述符中 数值最大的文件描述符值1 即nfds )
什么是 输入 输出型参数 用户把数据交给操作系统同样操作系统也要 通过这些输出型参数 把结果 交给用户 为了让 用户 和 操作系统之间进行信息传递就把参数设置为 输入 输出型参数
最后一个参数 timeout 的理解
timeout 是一个 输入 输出型参数 timeout的数据类型 为struct timeval 可以一个时间结构体tv_sec 表示 秒 tv_usec 表示 微秒 对于 struct timeval的对象 可设置三种值
第一种 对象被设为 NULL 对于select来说 表示 阻塞等待 (多个文件描述符任何一个都不就绪select就一直不返回)
第二种 struct timeval对象定义出来并将其中变量都设为0 对于select来说 表示 非阻塞等待 (多个文件描述符任何一个都不就绪select就会立马出错 并返回)
第三种 struct timeval对象定义出来并将其中变量设为 5 和 0 表示 5s以内 阻塞等待否则 就 timeout(非阻塞等待) 一次 若在第3s时 有一个文件描述符就绪则select就会返回 其中参数 timeout 表示 剩余的时间 2s(5-32) readfds writefds exceptfds 参数的理解
readfds writefds exceptfds 这三个参数 是同质的 readfds 表示 读事件 writefds 表示 写事件 excepttfds表示 异常事件
三者类型都为 fd_set fd_set是一个位图结构用其表示多个文件描述符 通过比特位的位置 就代表文件描述符数值是谁 位图结构想要使用 按位与、按位或 这些操作必须使用操作系统提供的接口
FD_CLR 将指定的文件描述符从 指定的集合中清除
FD_ISSET判断文件描述符是否在该集合中被添加
FD_SET 将一个文件描述符添加到对应的set集合中
FD_ZERO将文件描述符整体清空 以readfds 读事件为例
若放入 readfds 集合中用户告诉内核 那些文件描述符对应的读事件需要由 内核 来关心 返回时内核要告诉用户那些文件描述符的读事件已经就绪 假设想让操作系统去关心八个文件描述符对应的事件 用户想告诉内核时用户需 定义 fd_set 对象 rfds 其中八个比特位设置为1 比特位的位置表示几号文件描述符 比特位被置1则操作系统就需要关心 对应的几号文件描述符 如需要关心 1-8号文件描述符即查看是否就绪 当select返回时 内核会告诉用户rfds重置并将 就绪的文件描述符 对应 的 比特位位置 置1 如 3号和5号就绪则对应比特位 位置 置1 表示3号和5号文件描述符 对应的内容就绪 select的返回值
select的返回值 同样也有三种情况 第一种 大于0 表示有几个文件描述符 是就绪的
第二种 等于0 进入timeout状态 即 5s以内没有任何一个文件描述符 就绪
第三种 小于0 等待失败 返回-1 如想要等待下标为1 和2的文件描述符但是下标为2的文件描述符根本不存在就会等待失败