dw做的网站要多大,网站建设的组织机构,济南网站建设工作,青岛市北区网站制作公司恐惧让你沦为囚犯#xff0c; 希望让你重获自由。 --- 《肖申克的救赎》--- 五种IO模型与阻塞IO 1 前言2 五种IO模型3 非阻塞IO 1 前言
通过网络通信的学习#xff0c;我们能够理解网络通信的本质是进程间通信#xff0c;而进程间通信的本质就是IO。
IO就是input与outp… 恐惧让你沦为囚犯 希望让你重获自由。 --- 《肖申克的救赎》--- 五种IO模型与阻塞IO 1 前言2 五种IO模型3 非阻塞IO 1 前言
通过网络通信的学习我们能够理解网络通信的本质是进程间通信而进程间通信的本质就是IO。
IO就是input与output站在进程角度IO就是将数据从进程间输入输出数据站在软件层面IO就是与操作系统进行交互以IO中常用的接口read为例当底层没有数据时就会阻塞 。这种阻塞的本质是等待事件就绪。write写入数据时会将数据拷贝到缓冲区中当缓冲区满了之后也会进行阻塞等待。
所以我们平时使用的IO都可以总结为等 拷贝所以什么叫做高效的IO呢IO中可以理解为等 拷贝拷贝的效率是很快的那么高效的IO就是等的效率高!如果调用readwrite不需要等待那么效率自然而然的就高了
针对这个高效的IO我们介绍一下五种IO模型
2 五种IO模型
五种IO模型是程序员们经过长时间的使用总结出来的常用情况。接下来我们使用钓鱼的例子来介绍这五种IO模型
张三今天去钓鱼他带着一根鱼竿鱼漂小马扎。走到河边扎好座椅甩杆钓鱼。张三目不转睛的盯着鱼漂一有动静就收杆其他事情一概不考虑只盯着鱼漂看李四今天也去钓鱼他也带着一根鱼竿鱼漂小马扎。他也扎好座椅甩杆钓鱼。李四布置好之后不时刷手机聊天看书。间断性的看一看检测鱼漂有没有动鱼漂动了就需要收杆。王五今天也来钓鱼他是个聪明人他布置好杆子后拿出来一个神器铃铛放在鱼竿上。之后就开始耍手机看书聊天一有鱼上钩铃铛就会响就可以进行收杆赵六也来钓鱼了他是附近一带的首富带着一车鱼竿来钓鱼了放好100根鱼竿后赵六就开始巡逻式的检查鱼竿情况。有鱼的鱼竿就进行收杆。田七是整个城镇的首富他带着管家来钓鱼。田七知道自己想要的是河流里鱼而不是钓鱼。所以他就安排司机钓鱼他回家等待鱼小王怎么钓鱼田七并不关心。
上面五个人就是经典的五种IO模型每个人都代表一种系统调用鱼竿就是文件描述符鱼就是数据河是操作系统内部鱼漂浮动就代表数据就绪收杆就代表进行拷贝
张三的方式称之为阻塞IO李四的方式称之为非阻塞IO王五的方式称之为信号驱动IO赵六的方式称之为多路复用/多路转接IO田七的方式叫做异步IO
阻塞IO和非阻塞IO的区别就是等待的方式不同拷贝数据时一模一样的上面五种钓鱼效率最高的是赵六多路复用IO毕竟人家鱼竿多成功等到数据的概率就高
而非阻塞IO的高效是与阻塞IO进行对比的张三李四一天钓的鱼最终可能差不多但李四看完一本书追了4集电视剧…非阻塞IO的高效体现在可以在等待IO的同时处理其他事情
异步IO就是两件事情互不影响等待数据与获取数据就是异步进行同步IO与异步IO的区别就是是否自身参与IO。
阻塞 IO 是最常见的 IO 模型 阻塞 IO在内核将数据准备好之前系统调用会一直等待。所有的套接字默认都是阻塞方式. 非阻塞 IO如果内核还未将数据准备好系统调用仍然会直接返回, 并且返回EWOULDBLOCK 错误码。 非阻塞 IO往往需要程序员循环的方式反复尝试读写文件描述符这个过程称为轮询。这对 CPU 来说是较大的浪费一般只有特定场景下才使用 信号驱动 IO: 内核将数据准备好的时候使用 SIGIO 信号通知应用程序进行 IO操作。 IO 多路转接虽然从流程图上看起来和阻塞 IO 类似。实际上最核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。select就是一个专门用来等的接口 异步 IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)
任何 IO 过程中, 都包含两个步骤 第一是等待第二是拷贝。 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间。让 IO 更高效, 最核心的办法就是让等待的时间尽量少。 这里强调两组概念 同步通信 vs 异步通信(synchronous communication / asynchronouscommunication)同步和异步关注的是消息通信机制。 所谓同步 就是在发出一个调用时 在没有得到结果之前 该调用就不返回.但是一旦调用返回 就得到返回值了换句话说 就是由调用者主动等待这个调用的结果异步则是相反 调用在发出之后 这个调用就直接返回了 所以没有返回结果换句话说 当一个异步过程调用发出后 调用者不会立刻得到结果而是在调用发出后 被调用者通过状态通知来通知调用者 或通过回调函数处理这个调用。另外我们回忆在讲多进程多线程的时候也提到同步和互斥。这里的同步通信和进程之间的同步是完全不相干的概念。进程/线程同步也是进程/线程之间直接的制约关系 是为完成某种任务而建立的两个或多个线程 这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系. 尤其是在访问临界资源的时候。 以后在看到 “同步” 这个词 一定要先搞清楚大背景是什么。 这个同步, 是同步通信异步通信的同步, 还是线程同步与互斥的同步. 阻塞 vs 非阻塞 阻塞和非阻塞关注的是程序在等待调用结果消息 返回值 时的状态。阻塞调用是指调用结果返回之前 当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前 该调用不会阻塞当前线程。 3 非阻塞IO
实现非阻塞IO的方式有很多种 当使用open打开一个文件时可以传入一个标志位O_NONBLOCK or O_NDELAY int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);这样读取时就是非阻塞的进行读取。
同样的recv和send系列接口也有对应的非阻塞标志位MSG_DONTWAIT
ssize_t recv(int sockfd, void *buf, size_t len, int flags);但是这些方式都不太通用我们可以通过fcntl接口将文件描述符设置为非阻塞的文件描述符
Linux Programmers Manual
FCNTL(2)NAMEfcntl - manipulate file descriptorSYNOPSIS#include unistd.h#include fcntl.hint fcntl(int fd, int cmd, ... /* arg */ );fcntl 函数有 5 种功能:
复制一个现有的描述符cmdF_DUPFD .获得/设置文件描述符标记(cmdF_GETFD 或 F_SETFD).获得/设置文件状态标记位(cmdF_GETFL 或 F_SETFL).获得/设置异步 I/O 所有权(cmdF_GETOWN 或 F_SETOWN).获得/设置记录锁(cmdF_GETLK,F_SETLK 或 F_SETLKW).
通过这个系统调用可以写一个demo来看看效果。 这是j经典的阻塞IO:
#include iostream
#include unistd.h
#includecstdio
// 阻塞IO
int main()
{char buffer[1024];// 读取标准输入中的数据while (true){printf(Enter#);fflush(stdout);int n read(0, buffer, sizeof(buffer) - 1);if (n 0){// 说明没有读取到continue;}else if(n 0){//读取到了数据buffer[n] 0;std::cout buffer ;}else{//读取出现错误了perror(error!\n);}}return 0;
}读取效果是这样的
如果没有数据输入就会阻塞等待数据输入。 那么如何更改为非阻塞IO呢我们设置一个接口函数
#pragma once
#include unistd.h
#include fcntl.h
#include iostream//int fcntl(int fd, int cmd, ... /* arg */);void SetNonBlock(int fd)
{//首先获取原来标志位int fl ::fcntl(fd , F_GETFL);if(fl 0){std::cout fcntl error std::endl;return ;} //设置非阻塞标志位int n ::fcntl(fd , F_SETFL , fl | O_NONBLOCK);if(n 0){perror(fcntl error \n);return ;}return ;
}通过这个接口可以快速将文件描述符设置为非阻塞。我们将标准输入设置为非阻塞我们再来运行一下
如果是非阻塞 底层数据没有就绪IO 接口会以出错形式返回。
那么如何区分是真的出错了还是底层不就绪的非阻塞IO返回呢仅仅通过返回值是无法区分的了但是read接口中的返回值是这么描述的
RETURN VALUEOn success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not an error if this number issmaller than the number of bytes requested; this may happen for example because fewer bytes are actually available right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was interrupted by a signal. See also NOTES.On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes.
出错时read接口会设置全局的errno同样的recvsend…IO系列接口都是会设置errno 可以看到errno被设置为了11那11代表什么呢11就是EWOULDBLOCK错误
#define EAGAIN 11 /* Try again */
...
#define EWOULDBLOCK EAGAIN /* Operation would block */这就表示底层数据不就绪可以try again如果真的出错了就会被设置成其他格式。
// 非阻塞IO
int main()
{char buffer[1024];SetNonBlock(0);// 读取标准输入中的数据while (true){printf(Enter#);fflush(stdout);ssize_t n read(0, buffer, sizeof(buffer) - 1);if (n 0){printf(read done\n);break;}else if (n 0){// 读取到了数据buffer[n] 0;printf(echo# %s , buffer);}else{// 读取出现错误了if(errno EWOULDBLOCK){std::cout 底层数据没有就绪开始轮询检测 std::endl;//do other thing//可以做其他事情continue;}else{//真的出错了perror(read error!\n);break;}}//sleep(1);}return 0;
}这样就可以进行正常的非阻塞轮询了 注意操作系统的两个缓冲区输入与输出在我们键盘进行输入时会到操作系统的输入缓冲区然后再到进程的输入缓冲区。而键盘输入时操作系统会判断是否需要回显回显就会将输入缓冲区的数据拷贝到输出缓冲区一份这里就是可以回显的原因。 当我们进行IO拷贝时如果突然接收到一个信号导致IO拷贝中断了那么这个读取的返回可能并没有读取完毕这种情况的错误码是EINTR我们可以进行判断!