您的位置:首页 > 其它

多路复用I/O模型之select

2017-06-16 17:08 267 查看
1 .所谓I/O多路复用是指内核一旦发现进程指定的一个或者多个I/O条件准备读取,它就通知该进程。I/O多路复用适用如下场合:

  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

  (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

2 .在多路I/O复用的场合中,应用程序需要同时处理多路输入流和输出流,若我们采用阻塞的模式,则达不到预期的目的;若采用非阻塞的模式,对多个输入进行轮询,会很浪费cpu 的时间;若采用多进程/多线程来处理这样的问题,会使程序变得更加复杂,并且不易维护;因此最好的方法就是采用“多路I/O复用”。

3 .select函数

该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个时间发生或经历一段指定的时间后才唤醒;

该函数原型为:

int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);

所需要头文件:
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>


函数说明:

select()函数用来等待文件描述符状态的改变。参数nfds代表最大的文件描述符+1,参数readfds、writefds、execptfds称为“描述符组”,用来传回描述符的读、写或或异常的状况;

select中各参数含义:

nfds:表示集合中需要检查的号码最高的文件描述符+1

readfds:由select()函数监听的读文件描述符集合

writefds:由select()函数监听的写文件描述符集合

execptfds:由select()函数监视的异常处理文件描述符集合

timeout:

对于timeout;它是一个结构体指针,原型为:struct timeval *timeout;这个时间结构体的精确度可以设置到微妙级。该结构体如下:

struct timeval *timeout{
long tv_sec;    /* second 秒 */
long tv_unsec;  /* and microseconds 微秒 */
}


该结构体的变量设置有3种情况:

1.
NULL:表示“永远等待”。直到捕捉到信号或文件描述符已经准
备好为止。如果捕捉到一个信号,则select返回-1,errno
设置为EINTR;
2.
timeout->tv_sec == 0 && timeout->tv_usec == 0;
表示“完全不等待”;测试所有已指定的描述符并立即返回。这是
得到多个描述符的状态而不阻塞select函数的轮询方法;
3.
timeout->tv_sec != 0 && timeout->tv_usec != 0;
表示“等待指定的秒数和微秒数”。当指定的描述符之一(readfds
、writefds或execptfds)已经准备好,或当指定的时间值已经
超过了的时候则立即返回。如果在超时时候还没有一个文件描述符
已经准备好,则返回值为0;


select函数是一个阻塞的函数,当select函数返回时,也就是有文件描述符可以读写数据的时候,此时可以用读写函数完成数据的读写;其返回值表示有多少个文件有消息;和阻塞模式相比,多路复用技术的高级之处就在于能够同时去等待多个文件描述符,当这些文件描述符中的任意一个进入就绪状态时,select函数就可以返回。

结论:select函数是一个“阻塞”函数,当该函数执行时,必定会有下面2个事件中的一个;

1.select监测的文件I/O集合中,有文件有消息

2. 时间超时,通过select的第5个参数指定超时的时间

4 .在使用select函数的时候需要对文件描述符进行分类别处理,当文件描述符的处理主要涉及到4个宏函数;分别是:

select文件描述符集处理宏

/* 清除一个文件描述符集合 */
FD_ZERO(fd_set *set)

/* 将一个文件描述符加入文件描述符集合中 */
FD_SET(int fd, fd_set *set)

/* 将一个文件描述符从文件描述符集合中清除*/
FD_CLR(int fd, fd_set *set)

/* 测试该集合中的一个文件描述符有无发送变化 */
FD_ISSET(int fd, fd_set *set)

通常,在使用select函数之前,首先应该调用FD_ZERO 和FD_SET
来初始化文件描述符集,在使用了select函数时,可以循环使用F_ISSET来测试哪一个文件描述符就绪,在执行完相关的文件描述符
之后,可使用FD_CLR来清除该文件描述符。


5 .select函数的原理为:



从set集合中挑选出有数据的fd,一旦set集合中有文件描述符发送状态改变,则select立刻退出阻塞状态,然后继续向下执行相应的操作。

6 .示例 1

linux的/dev/input/下分别有一个mice鼠标读写事件文件和event1键盘读写事件文件;当用open打开这两个文件,并且对其进行读操作时,都是一个阻塞的操作;即当键盘的按键被按下,或者鼠标被点击的时候,才会解除阻塞,开始读写;现在用select的i/o复用模型知识来解决这个问题;用一个进程(主程序)来处理,当有键盘读写时候,立刻去调用个键盘打印函数;若有鼠标读写时,立刻执行鼠标的打印函数;

有两个注意的地方:
(1)  需要修改/dev/input/mice 和 /dev/input/event1这
两个文件的权限
(2)  在打印鼠标响应事件和键盘响应事件的地方,每次把set里面
的值取出来,不然每次来判断里面都有数据,会陷入一个死循环中;

结果:当有鼠标被按下的时候,打印“鼠标被响应....”;当有键盘被
按下的时候,打印“键盘被响应....”;


/*************************************************************************
* File Name: select.c
* Author:    The answer
* Function:  Other
* Mail:      2412799512@qq.com
* Created Time: 2017年06月16日 星期五 16时14分19秒
************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<errno.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<signal.h>
#include<time.h>
#include<unistd.h>

void sys_err(const char *ptr,int num)
{
perror(ptr);
exit(num);
}

int main(int argc,char **argv)
{

int fd_mice = open("/dev/input/mice",O_RDONLY);
if(fd_mice < 0)
sys_err("open mice",-1);

int fd_key = open("/dev/input/event1",O_RDONLY);
if(fd_key < 0)
sys_err("open keyboard",-2);

while(1)
{

struct timeval timeout;
fd_set set;
int maxfd;
/* 取出两个文件描述符中的最大者 */
maxfd = fd_mice > fd_key ? fd_mice : fd_key;
/* 初始化集合set  */
FD_ZERO(&set);
/* 在set集合中加入相应的描述集 */
FD_SET(fd_mice,&set);
FD_SET(fd_key, &set);

/* 设置select阻塞等待最长的时间(秒数) */
timeout.tv_sec = 5;
timeout.tv_usec = 0;
/* select有准备就绪的文件描述符就返回,否则等待
* 5秒后报错
* */
int ret = select(maxfd + 1,&set,NULL,NULL,&timeout);
if(ret == 0 )
{
printf("time out\n");
continue;
//表示被信号中端给打断
}else if(ret == -1 && errno == EINTR){
printf("signal inturrupt\n");
continue;
}else if(ret == -1)
{
printf("bad error\n");
break;
}else if(ret > 0)
{
char buf[1024];
/* bzero将buf中的sizeof(buf)字节空间全部清零 */
bzero(buf,sizeof(buf));

if(FD_ISSET(fd_key,&set))
{
printf("键盘有消息....\n");

/* 记得将数据读出,不然会死循环 */
read(fd_key,buf,sizeof(buf));

}
if(FD_ISSET(fd_mice,&set))
{
puts("鼠标有消息....\n");

/* 记得将数据读出,不然会死循环 */
read(fd_mice,buf,sizeof(buf));
}
}
}
}


示例2:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: