1. IO多路复用含义:
IO: 输入输出操作;
多路复用:若请求的IO操作阻塞,且它不是真正阻塞IO,而是让其中的一个函数等待,在这期间,IO 还能进行其他的操作。该函数有select() 和 poll()两个函数。
2. Selete()和poll()函数:
设置程序中每一个所关心的文件描述符的条件、希望等待的时间等,selete()和poll() 函数返回时内核会通知用户已经准备好的文件描述符的数量、已准备好的条件等。
通过使用selete()和poll() 函数的返回结果,就可调用相应的IO处理函数;
2.1 Select()函数介绍:
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exeptfds, struct timeval *timeout);
参数说明:
函数参数 | 参数说明 | 备注 |
numfds | 需要监视的文件描述符的最大值+1 |
|
readfds | select()监视的读文件描述符集合 |
|
writefds | 监视的写文件描述符集合 |
|
exeptfds | 监视的异常处理文件描述符集合 |
|
timeout | 1) NULL ——永远等待,直到扑捉到信号; 2) 具体值——在等待的时间范围内,没有等到,则立即返回; 3) 0——从不等待,所有文件描述符立即返回 |
|
|
|
|
返回值 | 1) >0:成功,返回准备好的文件描述符的数目; 2) 0:超时; 3) -1:出错; |
|
select() 文件描述符处理函数:
FD_ZERO(fd_set *set); 清除一个文件描述符集;
FD_SET(int fd, fd_set *set); 将文件描述符fd加入到文件描述符集合set中
FD_CLR(int fd, fd_set *set); 将一个文件描述符fd从文件描述符集合中删除;
FD_ISSET(int fd, fd_set *set);
//timeout数据类型介绍:
struct timeval
{
long tv_sec;
long tv_usec;
}
2.2 为什么linux select函数的第一个参数总应该是fdmax + 1 ?
这就涉及到linux select第一个参数的含义: 待测试的描述集的总个数。 但要注意, 待测试的描述集总是从0, 1, 2, ...开始的。 所以, 假如你要检测的描述符为8, 9, 10, 那么系统实际也要监测0, 1, 2, 3, 4, 5, 6, 7, 此时真正待测试的描述符的个数为11个, 也就是max(8, 9, 10) + 1
select 函数注意点:http://blog.csdn.net/jkazan/article/details/52529687
2.3 poll() 函数介绍:
#include <sys/types.h>
#include <poll.h>
int poll(struct pollfd *fds, int numfds, int timeout);
参数说明:
函数参数 | 参数说明 | 备注 |
fds | struct pollfd结构指针:描述需要对哪些文件的 哪种类型的操作进行监控; struct pollfd { int fd; short events; //要监听的事件 short revents; //已发生的事件 } events成员描述主要有以下几类: POLLIN; POLLPRI; POLLOUT; POLLERR; POLLHUP; POLLNVAL; |
|
numfds | 要监听的文件描述符个数; 即第一个参数所指向的数组张哦功能的元素数目; |
|
timeout | 非0值: 等待的ms数; <0: 无限等待; |
|
|
|
|
返回值 | 1) >0:成功,事件发生的pollfd结构的个数; 2) 0:超时; 3) -1:出错; |
|
3. 实例验证:
通过调用select() 函数来监听3个终端的输入,并分别进行相应的处理。3个文件描述符分别是:一个标准输入,两个管道文件描述符;通过监视主程序的输入终端来实现程序控制,比如结束程序;两个管道文件描述符作为输入,主程序将两个管道读取的输入字符串写入到标准输出文件,即屏幕上。
在标准终端输入‘q’ 或者 ’Q’时,监控终端退出,或者60S超时退出。
实验运行结果如下:
创建两个段管道,用命令:mknod in1 mknod in2
管道、网络编程等,都有阻塞作用。
管道中的数据,在下一次select()函数启动的时候,会调用读取管道里面的数据。
3.1 select() 程序代码:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#define MAX_BUFF_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];
char buff[MAX_BUFF_SIZE];
int i, res, real_read, maxfd;
struct timeval tv;
fd_set inset, tmp_inset;
fds[0] = 0;
if( (fds[1] = open("in1", O_RDONLY | O_NONBLOCK)) < 0 )
{ printf("Open in1 is error!\n");
return 1;
}
if( (fds[2] = open("in2", O_RDONLY | O_NONBLOCK)) < 0)
{ printf("Open in2 is error!\n");
return 1;
}
maxfd = MAX( MAX(fds[0], fds[1]), fds[2]);
FD_ZERO(&inset);
for(i = 0; i < IN_FILES; i++)
{
FD_SET(fds[i], &inset);
}
FD_SET(0, &inset);
tv.tv_sec = TIME_DELAY;
tv.tv_usec = 0;
while( FD_ISSET(fds[0], &inset) || FD_ISSET(fds[1], &inset) || FD_ISSET(fds[2], &inset) )
{
tmp_inset = inset;
//printf("tmp_inset = %d\n", tmp_inset);
res = select( maxfd+1, &tmp_inset, NULL, NULL, &tv);
printf("select return res = %d,准备好的fd数目\n", res);
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(buff, 0, MAX_BUFF_SIZE);
real_read = read(fds[i], buff, MAX_BUFF_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( (buff[0] = 'q') || (buff[0] == 'Q') )
return 1;
}
else
{
buff[real_read] = '\0';
printf("Read Data is %s\n", buff);
}
}
}
}
}
}
}
}
select() 函数缺点:
1)内核必须检查多余的文件描述符;
2)每次调用select()函数之后,必须重置被监听的文件描述符集;
3)可监听的文件描述符受限;
4. poll()函数实例:
代码实现:
#include <fcntl.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <time.h>
7 #include <errno.h>
8 #include <poll.h>
9
10 #define MAX_BUFFSIZE 1024
11 #define IN_FILES 3
12 #define TIME_DELAY 100
13 #define MAX(a, b) ((a > b) ? (a) : (b))
14
15 int main(void)
16 {
17
18 struct pollfd fds[IN_FILES];
19 char buff[MAX_BUFFSIZE];
20 int i, res, real_read, maxfd;
21
22 fds[0].fd = 0;
23
24 if( (fds[1].fd = open("in1", O_RDONLY | O_NONBLOCK)) < 0)
25 { printf("Open in1 Error\n");
26 return 1;
27 }
28
29 if( (fds[2].fd = open("in2", O_RDONLY | O_NONBLOCK)) < 0)
30 { printf("Open in2 Error\n");
31 return 1;
32 }
33
34 //1. 初始化所要监听的文件描述符的事件
35 for( i=0; i<IN_FILES; i++)
36 {
37 fds[i].events = POLLIN;
38 }
39
40 //2. 循环检测所监听的文件描述符的事件类型的事件是否发生:if( fds[i].revents )
41 while( fds[0].events || fds[1].events || fds[2].events )
42 {
43 //poll()函数,返回值为fds结构体的个数,即发生的文件描述>符的个数
44 if( (res = poll(fds, IN_FILES, 0)) < 0)
45 { printf("Poll error\n");
46 return 1;
47 }
48 printf("1) poll fun returned is %d\n", res);
49 for(i=0; i<IN_FILES; i++)
50 {
51 printf("2) %d fds.fd is happened!\n",fds[i].fd);
52
53 //特别注意,该判断切勿遗漏: revents表示已发生的事件; events表示需要监听的事件的事件类型初始化;
54 if(fds[i].revents)
55 {
56 printf("3) %d fds.fd is happened!\n",fds[i].fd);
57 memset(buff, 0, MAX_BUFFSIZE);
58 real_read = read(fds[i].fd, buff, MAX_BUFFSIZE);
59 if(real_read < 0)
60 { if(errno != EAGAIN)
61 return 1;
62 }
63 else if( !real_read )
64 {
65 close(fds[i].fd);
66 fds[i].events = 0;
67 }
68 else
69 {
70 //控制终端输入 q 或者 Q 时,退出程序运行;
71 if( 0 == i )
72 {
73 if( (buff[0] == 'q') || (buff[0] == 'Q') )
74 return 1;
75 }
76 else
77 {
78 buff[real_read] = '\0';
79 printf("read data are %s\n", buff);
80 }
81 }
82 }
83 }
84 }
85 exit(0);
86 }
87
88