Unix下可用的5种IO模型
一、Unix可用的5种IO模型和区别:
1.阻塞式IO
2.非阻塞式IO
3.IO复用(select和poll)
4.信号驱动式IO(SIGIO)
5.异步IO(POSIX的aio_系列函数)
二、1.阻塞式IO模型:
最流行的IO模型是阻塞式IO模型
应用进程 内核
(recvfrom)------>系统调用---------> 无数据报准备好
|
等待数据
|
数据报准备好
|
将数据从内核复制到用户空间
|
处理数据报<-----返回成功指示<----- 复制完成
进程调用recvform,其系统调用直到数据报到达且被复制到应用进程的缓冲区中或者发生错误才返回。最常见的错误是系统调用被信号中断,我们说进程在从调用recvform开始到它返回的整段时间内是被阻塞的。recvfrom成功返回后,应用进程开始处理数据报。
2.非阻塞式IO模型
应用进程 内核
recvfrom----->系统调用---------> 无数据准备好
<---EWOULDBLOCK----
recvfrom----->系统调用---------> 无数据准备好
<---EWOULDBLOCK----
... ...
recvfrom------->系统调用-------> 数据准备好
复制数据报
|
处理数据报<------返回成功指示------完成复制
进程把一个套接字设置成非阻塞:当所有请求的IO操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。
当一个一个应用程序对一个非阻塞描述符循环调用recvfrom时,我们称之为轮训polling。应用程序持续轮训内核,以查看某个操作是否就绪。
这么做往往消耗大量CPU时间。
3、IO复用模型
有了IO复用,我们就可以调用select或poll,阻塞在这2个系统调用中的某一个之上,而不是阻塞在真正的IO系统调用上。
应用进程 内核
select ------系统调用-----------> 无数据准备好
|
等待数据
|
<-----返回可读条件----- 数据准备好
recvfrom ----->系统调用------------> 复制数据报
|
将用户从内核复制到用户控件
|
处理数据报<----------返回成功指示-----复制完成
我们阻塞于select调用,等待数据报套接字变为可读。当selsect返回套接字可读这一条件时,我们调用recvfrom把所读数据报复制到应用进程缓冲区。
和上面模型比较IO复用并不显得有什么优势,事实上由于使用select需要两个而不是单个系统调用,IO复用还稍有劣势。
不过使用select的优势在于我们可以等待多个描述符就绪。
select 代码演示:
#include "unp.h" //里面包含了所有关于网络变成的头文件 #include <vector> void main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { printf("创建监听套接字失败\n"); return; } //创建sockaddr sockaddr_in listenAddr; listenAddr.sin_family = AF_INET; listenAddr.sin_port = htons(9000); listenAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //绑定端口 if (bind(sockfd, (sockaddr*)&listenAddr, sizeof(listenAddr)) < 0) { printf("bind error\n"); return; } //监听 if (listen(sockfd, 5) < 0) { printf("listen error\n"); return; } printf("start listen... port:%d\n", listenAddr.sin_port); //创建描述符集合,这里只有读集合和全部基本 //使用select时最常见的两个错误:1.忘了对最大描述符+1;2.忘了描述符集合是 //值-结果参数.每次调用selcet时,描述将指示哪些描述符以就绪。 fd_set rset, allset; int maxfd = sockfd + 1; //最大描述符+1 char recvline[256] = { 0 }; std::vector<int> clients;//用于保存链接客户端的描述符 FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(sockfd, &allset); for(;;) { rset = allset; int ret = select(maxfd, &rset, NULL, NULL, NULL); //客户端链接 if (FD_ISSET(sockfd, &rset)) { //当客户链接准备好时调用accpt函数接受客户端链接,返回链接描述符 int connfd = accept(sockfd, NULL, NULL); if (connfd < 0) { printf("accept error\n"); continue; } FD_SET(connfd, &allset); //将新建立链接的客户端加入到客户端数组 clients.push_back(connfd); if (connfd + 1 > maxfd) { maxfd = connfd + 1; } printf("client connected fd = %d\n", connfd); if (--ret <= 0) { continue; } } //有数据到来 for(auto it = clients.begin(); it != clients.end(); it++) //check all clients for data { int fd = *it; if (FD_ISSET(fd, &rset)) { int len = recv(fd, recvline, 256, 0); if (len <= 0) { close(fd); it = clients.erase(it); FD_CLR(fd, &allset); printf("client %d disconnect\n", fd); } else { printf("FD:%d %s", fd, recvline); send(fd, recvline, 256, 0); memset(recvline, 0, 256); } if (--ret <= 0) { break; } } } } }