网站规划建设与管理维护课后答案6,织梦网站文章相互调用,优易网络公司员工发展,wordpress静态化占内存么Unix下可用的5种I/O模型#xff1a;阻塞I/O非阻塞I/OI/O复用(select和poll)信号驱动I/O(SIGIO)异步I/O(POSIX的aio_系列函数)一个输入操作通常包括两个不同的阶段#xff1a;1#xff09;等待数据准备好#xff1b;2#xff09;从内核向进程复制数据#xff1b;对于一个套…Unix下可用的5种I/O模型阻塞I/O非阻塞I/OI/O复用(select和poll)信号驱动I/O(SIGIO)异步I/O(POSIX的aio_系列函数)一个输入操作通常包括两个不同的阶段1等待数据准备好2从内核向进程复制数据对于一个套接字的输入操作第一步通常涉及等待数据从网络中到达。当所等待分组到达时它被复制到内核中某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。阻塞I/O最流行的I/O模型是阻塞式I/O(blocking I/O) 模型默认情况下所有的套接字都是阻塞的。阻塞调用是指调用结果返回之前当前线程会被挂起线程进入非可执行状态在这个状态下cpu不会给线程分配时间片即线程暂停运行。函数只有在得到结果之后才会返回。以数据包套接字为例如图进程调用recvfrom其系统调用直到数据报到达且被拷贝到应用进程的缓冲区或者发生错误才返回。最常见的错误是系统调用被信号中断。我们说进程从调用recvfrom开始到它返回的整段时间内是被阻塞的recvfrom成功返回后进程开始处理数据报。非阻塞I/O非阻塞和阻塞的概念相对应指在不能立刻得到结果之前该函数不会阻塞当前线程而会立刻返回。进程把一个套接口设置成非阻塞是在通知内核当所请求的I/O操作非得把本进程投入睡眠才能完成时不要把本进程投入睡眠而是返回一个错误。前三次调用recvfrom 时没有数据可返回因此内核转而立即返回一个EWOULDBLOCK 错误。第四次调用 recvfrom 时已有一个数据报准备好它被复制到应用程序缓冲区于是recvfrom 成功返回。我们接着处理数据。当一个应用进程像这样对一个非阻塞描述符循环调用 recvfrom 时我们称之为轮询polling。应用程序持续轮询内核以查看某个操作是否就绪。这样做往往耗费大量CPU 时间。I/O复用主要可以调用select和epoll对一个IO端口两次调用两次返回比阻塞IO并没有什么优越性关键是能实现同时对多个IO端口进行监听可以等待多个描述符就绪I/O复用模型会用到select、poll、epoll函数这几个函数也会使进程阻塞但是和阻塞I/O所不同的的这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作多个写操作的I/O函数进行检测直到有数据可读或可写时才真正调用I/O操作函数信号驱动I/O模型 我们也可以用信号让内核在描述字就绪时发送SIGIO信号通知我们。我们称这种模型为信号驱动I/Osignal-driven I/O。 我们首先开启套接口的信号驱动I/O功能并通过sigaction系统调用安装一个信号处理函数。该系统调用立即发回我们的进程继续工作也就是说它没有被阻塞。当数据报准备好时内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报并通知主循环数据已经准备好待处理也可以立即通知主循环让它读取数据报。 无论如何处理SIGIO信号这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行只要不时等待来自信号处理函数的通知既可以是数据已经准备好被处理也可以是数据报已准备好被读取。 异步I/O模型 异步I/Oasynchronous I/O有POSIX规范定义。后来演变成当前POSIX规范的各种早期标准定义的实时函数中存在的差异已经取得一致。一般地说这些函数的工作机制是告知内核启动某个操作并让内核在整个操作包括将数据从内核拷贝到我们自己的缓冲区完成后通知我们。这种模型与前与前面介绍的信号驱动模型的主要区别在于信号驱动I/O是由内核通知我们何时可以启动一个I/O操作而异步I/O模型是由内核通知我们I/O操作何时完成。 各种模型的比较 可以看出前4种模型的主要区别在于第一阶段因为它们的第二阶段是一样的在数据从内核复制到调用者的缓冲区起见进程阻塞与recvfrom 调用相反。异步I/O模型在这两个阶段都需要处理从而不同于其他四种模型。同步I/O与异步I/O对比 POSIX把这两个术语定义如下 ·同步I/O操作synchronous I/O operation导致请求进程阻塞直到I/O操作完成。 ·异步I/Oasynchronous I/O operation不导致请求进程阻塞。 根据上述定义我们前4种模型----阻塞I/O模型、非阻塞I/O模型、I/O复用模型和信号去驱动I/O模型都是同步I/O模型因为其中真正的I/O操作recvfrom将阻塞进程。只有异步I/O模型与POSIX定义的异步I/O相匹配。 select 函数该函数允许进程指示内核等待多个事件中的任何一个发生并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。作为一个例子我们可以调用select告知内核仅在下列情况发生时才返回1集合{ 1, 4, 5 } 中任何描述符准备好读2集合{ 2, 7 } 中任何描述符准备好写3集合{ 1, 4 } 中任何描述符有异常条件待处理也就是说我们调用 select 告知内核对哪些描述符就读、写或异常条件感兴趣以及等待多长时间。我们感兴趣的描述符不局限于套接字任何描述符都可以用select 来测试。函数描述如下[cpp] view plaincopy #include sys/select.h #include sys/time.h int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout); 从最后一个参数timeout 开始介绍它告知内核等待所指定描述符中任何一个就绪可花多长时间。其timeval结构用于指定这段时间的秒数和微妙数。[cpp] view plaincopy struct timeval { long tv_sec; //seconds long tv_usec; //mircoseconds } 这个参数有以下三种可能1永远的等待下去仅在有一个描述符准备好I/O时才返回。为此我们把这个参数设置为空指针2等待一段固定时间在有一个描述符准备好I/O时返回但是不超过由该参数所指向的timeval 结构中指定的秒数和微秒数3根本不等待检查描述符后立即反悔这称为轮询polling。为此该参数必须指向一个timeval结构而且其中的定时器值由该结构指定的秒数和微秒数必须为0中间的三个参数 readset 、writeset 和 exceptset 指定我们要让内核测试读、写和异常条件的描述符。select 使用描述符集通常是同一个整数数组其中每个整数中的每一位对于一个描述符。举例来说假设使用32位整数那么该数组的每一个元素对应于描述符0~31第二位元素对应于描述符32~63依次类推 它们隐藏 为 fd_set 的数据类型和以下四个宏中[cpp] view plaincopy void FD_ZERO(fd_set *fdset); //从fdset中清除所有的文件描述符 void FD_SET(int fd, fd_set *fdset); //将fd加入到fdset void FD_CLR(int fd, fd_set *fdset); //将fd从fdset里面清除 int FD_ISSET(int fd, fd_set *fdset); //判断fd是否在fdset集合中 举个例子以下代码用于定义一个fd_set 类型的变量然后打开描述符 1、4 和 5 的对应位[cpp] view plaincopy fd_set rset; FD_ZERO(rset); FD_SET(1, rset); FD_SET(4 rset); FD_SET(5, rset); 描述符集的初始化非常重要因为作为自动变量分配的一个描述符集如果没有初始化那么可能发生不可预期的后果。select 函数修改由指针 readset 、writeset 和 exceptset 所指向的描述符集因而这三个参数都是值-结果参数。调用该函数时我们指定所关心的描述符的值该函数返回时结果将指示哪些描述符就绪。该函数返回后我们使用FD_ISSET宏测试 fd_set 数据类型中的描述符。描述符集内任何与未就绪描述符对应的位返回时均清0。为此每次重新调用select函数时我们都得再次把所以描述符集内所关心的为均置一。数的返回值表示跨所有描述符集的已就绪的总位数。如果任何描述符就绪之前定时器到时那么返回0.返回-1表示出错。描述符就绪条件对于可读文件描述符集以下四种情况会导致置位: 1、socket接收缓冲区中的数据量大于或等于当前缓冲区的低水位线.此时对于read操作不会被阻塞并且返回一个正值(读取的字节数).低水位线可以通过SO_RCVLOWAT选项设定对于Tcp和Udp来说其默认值为1. 2、socket连接的读端被关闭如shutdown(socket, SHUT_RD)或者close(socket).对应底层此时会接到一个FIN包,read不会被阻塞但会返回0.代表读到socket末端. 3、socket是一个监听socket并且有新连接等待.此时accept操作不会被阻塞. 4、发生socket错误.此时read操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息. 对于可写文件描述符集以下四种情况会导致置位: 1、socket发送缓冲区中的可用缓冲大小大于或等于发送缓冲区中的低水位线并且满足以下条件之一 (1)、socket已连接 (2)、socket本身不要求连接典型如Udp 低水位线可以通过SO_SNDLOWAT选项设置.对于Tcp和Udp来说一般为2048. 2、socket连接的写端被关闭如shutdown(socket, SHUT_WR)或者close(socket).在一个已经被关闭写端的句柄上写数据会得到SIGPIPE的信号(errno). 3、一个非阻塞的connect操作连接成功 或者 connect操作失败. 4、发生socket错误.此时write操作会返回SOCKET_ERROR(-1).可以通过errno来获取具体错误信息. 对于异常文件描述符集只有一种情况(针对带外数据): 当收到带外数据(out-of-band)时或者socket的带外数据标志未被清除. 下面看个具体例子 server [cpp] view plaincopy #include stdio.h #include string.h #include stdlib.h #include unistd.h #include sys/types.h #include sys/socket.h #include sys/select.h #include netinet/in.h #include arpa/inet.h #define PORT 8888 #define MAXSIZE 128 int main() { int i,nbyte; int listenfd, confd, maxfd; char buffer[MAXSIZE]; fd_set global_rdfs, current_rdfs; struct sockaddr_in addr,clientaddr; int addrlen sizeof(struct sockaddr_in); int caddrlen sizeof(struct sockaddr_in); if((listenfd socket(AF_INET, SOCK_STREAM, 0)) -1) { perror(socket error); exit(-1); } else { printf(socket successfully!\n); printf(listenfd : %d\n,listenfd); } memset(addr, 0 ,addrlen); addr.sin_family AF_INET; addr.sin_port htons(PORT); addr.sin_addr.s_addr htonl(INADDR_ANY); if(bind(listenfd,(struct sockaddr *)addr,addrlen) -1) { perror(bind error); exit(-1); } else { printf(bind successfully!\n); printf(listen port:%d\n,PORT); } if(listen(listenfd,5) -1) { perror(listen error); exit(-1); } else { printf(listening...\n); } maxfd listenfd; FD_ZERO(global_rdfs); FD_SET(listenfd,global_rdfs); while(1) { current_rdfs global_rdfs; if(select(maxfd 1,¤t_rdfs, NULL, NULL,0) 0) { perror(select error); exit(-1); } for(i 0; i listenfd 1; i) { if(FD_ISSET(i, ¤t_rdfs)) { if(i listenfd) { if((confd accept(listenfd,(struct sockaddr *)clientaddr,caddrlen)) -1) { perror(accept error); exit(-1); } else { printf(Connect from [IP:%s PORT:%d]\n, inet_ntoa(clientaddr.sin_addr),clientaddr.sin_port); FD_SET(confd,global_rdfs); maxfd (maxfd confd ? maxfd : confd); } } else { if((nbyte recv(i, buffer, sizeof(buffer),0)) 0) { perror(recv error); exit(-1); } else if(nbyte 0) { close(i); FD_CLR(i,global_rdfs); } else { printf(recv:%s\n,buffer); send(i, buffer, sizeof(buffer),0); } } } } } return 0; } 执行结果如下 [cpp] view plaincopy fsubuntu:~$ cd qiang/select/ fsubuntu:~/qiang/select$ ./select2 socket successfully! listenfd : 3 bind successfully! listen port:8888 listening... Connect from [IP:192.168.3.51 PORT:1992] recv:hello Connect from [IP:192.168.3.53 PORT:2248]