【Linux】IO模型
目录一、五种IO模型1.1 关于IO的理解1.2 五种IO模型1.3 非阻塞IO个人主页矢望个人专栏C、Linux系统编程、Linux网络编程、C语言、数据结构、Coze-AI、MySQL一、五种IO模型1.1 关于IO的理解什么是IO呢Input或Output就是把数据从外设拷贝到内存或者把数据从内存拷贝到外设。网络通信的本质也是属于IO范畴的。一个客户端与一个服务端正在通信。它们在调用read、write进行读写的时候read是在等待接收缓冲区中有数据有数据read才能拷贝数据write是在等待发送缓冲区中有数据有数据write才能发送数据。所以Input等数据就绪拷贝Output等空间就绪拷贝。所以IO的本质就是等拷贝。高效的IO就是在IO的整个过程中单位时间内等待的比重越低IO就越高效。所以设计高效的IO就是减少单位时间内IO过程中等待占的比重1.2 五种IO模型以钓鱼为例子来谈这五种模型钓鱼等于等钓。阻塞IO应用程序发起IO调用后线程被挂起直到数据从内核拷贝到用户空间完成才返回。非阻塞IO应用程序发起IO调用后立即返回通过不断轮询检查数据是否就绪就绪后再阻塞拷贝数据。信号驱动IO应用程序发起IO调用后立即返回数据就绪时内核发送信号通知应用程序收到信号后再阻塞拷贝数据。IO多路复用应用程序将多个文件描述符注册到选择器阻塞在select/poll/epoll上等待任意一个就绪就绪后再逐个阻塞拷贝数据。异步IO应用程序发起IO调用后立即返回内核完成数据从内核空间到用户空间的完整拷贝后通过回调通知应用程序直接使用数据。阻塞IO和非阻塞IO之间它们的等待事件就绪的方式不同但是单论IO效率方面它们两个的IO效率是一样的但是非阻塞IO做了更多的事它把等待的时间利用起来了。同步IO是只要参与IO的过程就是同步IO异步IO是指负责发起IO不参与IO的任何细节。前四种的拷贝数据阶段均为阻塞属于同步IO只有异步IO的拷贝阶段由内核完成全程无阻塞。这五种IO模型中多路复用/多路转接的IO效率是最高的因为它同时检测多个fd单位时间内返回的就绪事件数大幅增加。所以它单位时间内等待的比重低IO效率高。阻塞IO在内核将数据准备好之前系统调用会一直等待。所有的套接字默认都是阻塞方式。非阻塞IO如果内核还未将数据准备好系统调用仍然会直接返回并且返回EWOULDBLOCK错误码。信号驱动IO内核将数据准备好的时候使用SIGIO信号通知应用程序进行IO操作。IO多路转接虽然从流程图上看起来和阻塞IO类似实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。异步IO由内核在数据拷贝完成时通知应用程序而信号驱动是告诉应用程序何时可以开始拷贝数据。1.3 非阻塞IO非阻塞IO的实现方式有很多种这里推荐使用fcntl。非阻塞IO通过fcntl系统调用将文件描述符的O_NONBLOCK标志位设为1使读写操作在数据未就绪时立即返回EAGAIN或EWOULDBLOCK错误而非阻塞线程。在非阻塞IO这里出错或者其它情况errno常被设置为表格下面的值errno值宏名称含义处理方式11EAGAIN数据未就绪/缓冲区满重试或加入等待队列35EWOULDBLOCK数据未就绪/缓冲区满重试或加入等待队列4EINTR被信号中断重新发起系统调用104ECONNRESET连接被对端重置关闭fd清理连接110ETIMEDOUT连接超时关闭fd重连或报错111ECONNREFUSED连接被拒绝关闭fd重试或报错32EPIPE向关闭连接写数据关闭fd忽略SIGPIPE9EBADFfd无效检查fd状态22EINVAL参数无效检查调用参数最常处理的三个EAGAIN重试、EINTR重试、ECONNRESET清理。创建文件描述符fd之后在struct file结构体中保存了一个f_flags标志字段它表示的是文件当前设置的标志位。fcntl系统调用就是通过获取文件的标志字段然后再通过这个标志字段向文件中增加非阻塞O_NONBLOCK标志位。fcntl操作f_flags本质是修改struct file中的状态位让内核在后续IO路径中据此决定是否阻塞。struct file结构体中的f_flags标志字段函数作用fcntl(fd, F_GETFL)获取当前f_flagsfcntl(fd, F_SETFL, flags)设置f_flags如添加O_NONBLOCK接下来就使用fcntl系统调用对标准输入文件描述符添加O_NONBLOCK标志位。正常情况下使用read读取标准输入如果没有数据输入它会阻塞等待但是添加了非阻塞标志位后我们应该看到在数据没有就绪时它应该可以做其它事而不是一直等待用户输入数据。voidSetNonBlock(intfd){intflfcntl(fd,F_GETFL);// 获取文件的标志字段f_flagsif(fl0){std::cerrfcntl errorstd::endl;return;}// 给标志字段添加 O_NONBLOCK 标志位fcntl(fd,F_SETFL,fl|O_NONBLOCK);}上面就是给相关文件设置O_NONBLOCK标志位的接口的实现。给标准输入0设置并进行读取测试。给任意一个fd设置标志位之后再调用read或write等系统调用读写fd就是以非阻塞的形式调用了。intmain(){SetNonBlock(0);// 给标准输入文件设置非阻塞charinbuffer[1024];while(true){ssize_t nread(0,inbuffer,sizeof(inbuffer)-1);if(n0)// 读到数据{inbuffer[n]0;std::coutecho #inbufferstd::endl;}elseif(n0)// 对方关闭连接{std::coutend of filestd::endl;}else{if(errnoEWOULDBLOCK||errnoEAGAIN)// 数据未就绪{// 进程就可以做自己的事情了std::coutdata is not ready!std::endl;sleep(1);continue;}elseif(errnoEINTR)// 被信号中断{sleep(1);continue;}else// 其它{std::coutread error, strerror(errno)std::endl;}}}return0;}补充在Windows上按下CtrlZ表示文件末尾而Linux上按下CtrlD表示文件末尾。编译测试如上设置完非阻塞标志位后再次调用read读取标准输入的数据此时就没有阻塞卡住而是一直在进行其它事的打印工作。总结以上就是本期博客分享的全部内容啦如果觉得文章还不错的话可以三连支持一下你的支持就是我前进最大的动力技术的探索永无止境! 道阻且长行则将至后续我会给大家带来更多优质博客内容欢迎关注我的CSDN账号我们一同成长(▽)