一、四种I/O模型
1、阻塞I/O
最常用,最简单,效率最低
2、非阻塞I/O
可防止进程阻塞在I/O操作上,需要轮询
3、I/O多路复用
允许同时对多个I/O进行控制
4、信号驱动I/O
一种异步通信模型
二、非阻塞IO
fcntl函数
功能:对打开的文件描述符fd执行操作,获取一个已打开的文件描述符fd的属性, 由cmd参数来决定获取还是设置
参数
fd:已打开的文件描述符
cmd:
1、F_GETFL (void)
Return (as the function result) the fille descriptor flags; arg is ignored.
获取文件描述符fd的属性
int flag = fcntl(0,F_GETFL);
2、F_SETFL(int)
Set the file descriptor flags to the value specified by arg.
将arg参数里面的属性设置到fd所表示的文件描述符中
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
//1.打开鼠标设备
int fd = open("/dev/input/mice",O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
//2.设置非阻塞 标准输入
//2.1 获取文件描述符的标志
int flag = fcntl(0,F_GETFL);
flag |= O_NONBLOCK;
//2.2 设置新的标志到指定的文件描述符
fcntl(0,F_SETFL,flag);
//2.设置非阻塞 mice
flag = fcntl(fd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd,F_SETFL,flag);
char inputBuf[32] = {0};
char miceBuf[32] = {0};
while(1)
{
read(fd,miceBuf,sizeof(miceBuf));
printf("%d %d\n",miceBuf[0],miceBuf[1]);
read(0,inputBuf,sizeof(inputBuf));
printf("%s\n",inputBuf);
}
return 0;
}
三、I/O多路复用
应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
比较好的方法是使用I/O多路复用。其基本思想是︰
先构造一张有关描述符的表,然后调用一个函数。当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
服务器模型
在网络程序里面,通常都是一个服务器处理多个客户机。为了处理多个客户机的请求,服务器端的程序有不同的处理方式。
目前最常用的服务器模型。
1、循环服务器
循环服务器在同一个时刻只能响应一个客户端的请求
循环服务器模型:
1、TCP服务器
TCP服务器端运行后等待客户端的连接请求。
TCP服务器接受一个客户端的连接后开始处理,完成了客户的所有请求后断开连接。TCP循环服务器一次只能处理一个客户端的请求。
只有在当前客户的所有请求都完成后,服务器才能处理下一个客户的连接/服务请求。
如果某个客户端一直占用服务器资源,那么其它的客户端都不能被处理。TCP服务器一般很少采用循环服务器模型。
2、UDP服务器
2、并发服务器:
并发服务器在同一个时刻可以响应多个客户端的请求
1、TCP服务器
为了弥补TCP循环服务器的缺陷,人们又设计了并发服务器的模型。
并发服务器的设计思想是服务器接受客户端的连接请求后创建子进程来为客户端服务TCP并发服务器可以避免TCP循环服务器中客户端独占服务器的情况。
为了响应客户机的请求,服务器要创建子进程来处理。如果有多个客户端的话,服务器端需要创建多个子进程,过多的子进程会影响服务器端的效率。
2、UDP服务器
人们把并发的概念用于UDP就得到了UDP并发服务器模型。
UDP并发服务器模型TCP服务器模型一样,创建一个子进程来处理客户端的请求
除非UDP服务器在处理某个客户端的请求时所用的时间比较长,人们实际上较少用这种模型。
3、I/O多路复用并发服务器
1、select函数
功能
同时监测多个文件描述符(0-1023),当所监测的一个或多个文件描述符有对于的IO事件产生时select返回
参数
nfds:所监测的最大文件描述符加一
readfds:读表
writefds:写表
exceptfds:异常表
timeout:超时
struct timeval timeout = {0};---非阻塞
struct timeval timeout = {10, 0}; ---等待十秒
NULL:---阻塞
返回值:
>0:三张表中所监测的文件描述符总个数---有事件产生
= 0:超时
-1:错误
示例:
int maxfd; //保存表中最大文件描述符
fd_set rfds,tmp; //定义读表 rfds -- 初始化表 tmp--返回表
FD_ZERO(&rfds); //清空读表
FD_SET(0,&rfds); //将文件描述符0添加到读表中(rfds)
maxfd = 0; //保存当前表中最大的文件描述符
FD_SET(fd,&rfds); //将文件描述符fd添加到读表中(rfds)
maxfd = maxfd<fd?fd:maxfd; //保存当前表中最大的文件描述符
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//1.打开鼠标设备
int fd = open("/dev/input/mice",O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
int maxfd;//保存表中最大文件描述符
fd_set rfds,tmp;//定义读表 rfds -- 初始化表 tmp--返回表
FD_ZERO(&rfds);//清空读表
FD_SET(0,&rfds);//将文件描述符0添加到读表中(rfds)
maxfd = 0;//保存当前表中最大的文件描述符
FD_SET(fd,&rfds);//将文件描述符fd添加到读表中(rfds)
maxfd = maxfd<fd?fd:maxfd;//保存当前表中最大的文件描述符
while(1)
{
tmp = rfds;//拷贝初始化表
select(maxfd+1,&tmp,NULL,NULL,NULL);//让内核阻塞检测表中文件描述符的读事件
char buf1[32] = {0};
char buf2[32] = {0};
for(int i = 0;i < maxfd+1;i++)//遍历返回表 判断那一个文件描述符产生了读事件
{
if(FD_ISSET(i,&tmp))//判断文件描述符i是否在返回表中
{
if(0 == i)
{
read(0,buf1,sizeof(buf1));
printf("buf1:%s\n",buf1);
}
else if(fd == i)
{
read(fd,buf2,sizeof(buf2));
printf("%d %d %d\n",buf2[0],buf2[1],buf2[3]);
}
}
}
}
return 0;
}
- void FD_CLR(int fd, fd_set *set); //将指定的fd从表中删除
- int FD_ISSET(int fd, fd_set *set); //判断fd对应的文件描述符是否还在表中
- void FD_SET(int fd, fd_set *set); //将指定的文件描述符fd添加的指定set中
- void FD_ZERO(fd_set *set); //清空指定的set
-
poll函数(效率较高)
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能
同时监测多个文件描述符,当所监测的一个或多个文件描述符有对于的IO事件产生时poll返回
参数
fds:监测表----struct pollfd 类型的数组
- struct pollfd {
- int fd; /* file descriptor */
- short events; /* requested events */
- short revents; /* returned events */
- };
- fd:文件描述符
events:感兴趣的事件
POLLIN :读事件
POLLOUT:写事件
POLLERR:错误事件
revents:系统返回的事件---由系统填充
nfds: 监测表中最后一个元素的下标加一
timeout : 超时,指定poll()在等待一个文件描述符准备就绪时应该阻塞的毫秒数
timeout > 0 :超时时间为timeout ----在timeout之内如果有时间产生poll返回
timeout = 0:poll非阻塞
timeout = -1: 阻塞等待事件 ----有事件产生poll返回
返回值
> 0 :成功返回一个正数,表中产生事件的文件描述符的总个数
= 0: 超时
- 1 :错误
示例
int fd = open("/dev/input/mice",O_RDONLY);
struct pollfd fds[1024];//定义监测表
for(int i = 0;i < 1024;i++)//初始化监测表
fds[i].fd = -1;
int nfds = 0;//保存监测表中最后一个有效元素的下标
fds[nfds].fd = 0;//将标准输入添加到检测表中
fds[nfds].events = POLLIN;
nfds++;
fds[nfds].fd = fd;//将鼠标文件描述符添加到监测表中
fds[nfds].events = POLLIN;
nfds++;
poll(fds,nfds,-1);
for(int i = 0;i < nfds;i++)
{
if(fds[i].events == fds[i].revents)
{
if(fds[i].fd == 0)
{
char buf[32] = {0};
read(fds[i].fd,buf,sizeof(buf));
printf("buf:%s\n",buf);
}
else if(fds[i].fd == fd)
{
char buf[32] = {0};
read(fds[i].fd,buf,sizeof(buf));
printf("%d %d %d\n",buf[0],buf[1],buf[2]);
}
}
}