Linux学习之 多路复用
一.多路复用基本概念
IO多路复用是指内核一旦发现进程的一个或者多个IO条件准备读取,它就通知该进程。
在I/O编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。
二.多路复用的优点:
与传统的多线程/多进程模型比,I/O多路复用的优势有:
系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降底了系统的维护工作量,节省了系统资源
三.多路复用的应用场景:
1. 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字。
2. 服务器需要同时处理多种网络协议的套接字。
四.多路复用的函数
1.intselect(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
函数参数:
(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2...maxfdp1-1均将被测试,因为文件描述符是从0开始的。
(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
voidFD_ZERO(fd_set*fdset); //清空集合
voidFD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中
voidFD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除
intFD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
structtimeval{
longtv_sec; //seconds
long tv_usec; //microseconds
};
这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
返回值:返回总的位数这些位对应已准备好的描述符,否则返回-1
select()函数实现IO多路复用的步骤
(1)清空描述符集合
(2)建立需要监视的描述符与描述符集合的关系
(3)调用select函数
(4)检查监视的描述符判断是否已经准备好
(5)对已经准备好的描述符进程IO操作
五.多路复用的应用:
问题:采用管道函数创建有名管道,使用select函数替代使用poll函数实现多路复用: 创建两个有名管道,获取3个文件描述符(2个管道1个标准输入),然后初始化读文件描述符,select监视文件描述符的文件读写,管道1输出到屏幕上,管道2输出到屏幕上,标准输入‘Q’来进行判读是否退出。
设计流程:
编程代码:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#define FIFO1 "in1"
#define FIFO2 "in2"
#define MAX_BUFFER_SIZE 1024 //缓冲区大小
#define IN_FILES 3 //多路复用输入文件数目
#define TIME_DELAY 60 //超时秒数
#define MAX(a,b) ((a > b) ? (a) : (b))
int main(void)
{
int fds[IN_FILES]; //管道描述符
int i;
int res;
int real_read;
int maxfd;
char buf[MAX_BUFFER_SIZE];
struct timeval tv;
fd_set inset;
fd_set tmp_inset; //文件描述符集
fds[0] = 0; //终端的文件描述符
if(access(FIFO1,F_OK) == -1) //创建两个有名管道
{
if((mkfifo(FIFO1,0666) < 0) && (errno != EEXIST))
{
printf("Cannot creat fifo1 file!\n");
exit(1);
}
}
if(access(FIFO2,F_OK) == -1)
{
if((mkfifo(FIFO2,0666) < 0) && (errno != EEXIST))
{
printf("Cannot creat fifo2 file\n");
exit(1);
}
}
if((fds[1] = open(FIFO1,O_RDONLY | O_NONBLOCK)) < 0) //以只读非阻塞的方式打开两个管道文件
{
printf("open in1 error!\n");
return 1;
}
if((fds[2] = open(FIFO2,O_RDONLY | O_NONBLOCK)) < 0)
{
printf("open in2 error!\n");
return 1;
}
maxfd = MAX(MAX(fds[0],fds[1]),fds[2]); //取出两个文件描述符中的较大者
//初始化读集inset,并在读文件描述符集中加入相应的描述集
FD_ZERO(&inset); //将insert清零,使集合中不含任何fd
for(i = 0; i < IN_FILES; i++)
{ //将fds[i]加入inset集合
FD_SET(fds[i],&inset);
}
FD_SET(0,&inset);
tv.tv_sec = TIME_DELAY; //设置超时60s
tv.tv_usec = 0;
//循环测试该文件描述符是否准备就绪,并调用selelct()函数对相关文件描述符做相应的操作
while(FD_ISSET(fds[0],&inset) || FD_ISSET(fds[1],&inset) || FD_ISSET(fds[2],&inset))
{ //文件描述符集的备份,以免每次都进行初始化
tmp_inset = inset;
res = select(maxfd+1,&tmp_inset,NULL,NULL,&tv);
switch(res)
{
case -1:
{
printf("Select error!\n");
return 1;
}
break;
case 0:
{
printf("Time out!\n");
return 1;
}
break;
default:
{
for(i = 0; i < IN_FILES; i++)
{
if(FD_ISSET(fds[i],&tmp_inset))
{
memset(buf,0,MAX_BUFFER_SIZE);
real_read = read(fds[i],buf,MAX_BUFFER_SIZE);
if(real_read < 0)
{
if(errno != EAGAIN)
{
return 1;
}
}
else if(!real_read) //已到达文件尾
{
close(fds[i]);
FD_CLR(fds[i],&inset);
}
else
{
if(i == 0)
{ //主程序终端控制
if((buf[0] == 'q') || (buf[0] == 'Q'))
{
return 1;
}
}
else
{ //显示管道输入字符串
buf[real_read] = '\0';
printf("%s",buf);
}
}
}
}
}
break;
}
}
return 0;