IO多路复用(多路转接)
在没有IO多路复用技术前,进程在同一时间只能监控一个IO状态(一个文件描述符的状态),例如A和B两个文件在操作,定的顺序是A前B后,那么在A阻塞过程中、B在后面也是不能被操作的,这样会造成资源浪费、响应缓慢。
IO多路复用:同时监控多个IO文件描述符状态,如果没有准备好的描述符、则进程始终处于睡眠状态(或超时返回);在监控期间内 如果有准备好的描述符,则立刻告诉进程,进程开始处理就绪的描述符。例如上面的A前B后的情形,使用IO复用技术则可同时监听A和B哪个就绪,接下来可优先处理已就绪的文件事件。
两种方法解决IO多路复用,epoll后续博客再说。
方法一 select :
int select (int n, // 所有集合中文件描述符的最大值+1
fd_set *readfds, // 需监控的读 描述符集合
fd_set *writefds, // 需监控的写 描述符集合
fd_set *exceptfds, // 需监控的异常/带外数据的 描述符集合
struct timeval *timeout); // 超时时间
// 其中timeout参数:
/* 时间结构体 */
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
select在等待时间范围内,没有准备好的描述符,则会阻塞等待;在此期间 如果有文件描述符就绪,立刻返回。
返回值:通常情况下返回所有就绪的文件描述符的数目,发生错误返回-1 。
且在返回时,各描述符集合中 只保留 就绪的文件描述符。
select调用超过一定时间(参数timeout),还没有准备好的描述符,则 select() 调用返回0(就绪的描述符数量为0)。
以下是管理文件描述符集合的宏:
/* 以下是一些宏,管理文件描述符集合 */
FD_CLR(int fd, fd_set *set); // 从set集合中移除一个描述符fd
FD_ISSET(int fd, fd_set *set); // 测试某描述符fd是否在set集合中。返回值:0-不在;非负值-在
FD_SET(int fd, fd_set *set); // 向set集合中添加一个描述符fd
FD_ZERO(fd_set *set); // 从set集合中移除所有描述符
程序示例:
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(){;
fd_set read_fds;
fd_set write_fds;
FD_ZERO(&read_fds);
FD_SET(STDIN_FILENO, &read_fds); // 将STDIN_FILENO添加到监控的描述符中
// 设定select等待时间
struct timeval wait_time;
wait_time.tv_sec = 5; // 秒
wait_time.tv_usec = 0; // 微秒
int select_rslt = select(STDOUT_FILENO + 1,
&read_fds,
NULL,
NULL,
&wait_time);
if (select_rslt == -1) {
perror("select");
return 1;
} else if (select_rslt == 0) {
fprintf(stdout, "No fd is ready, and 5 seconds elapsed.\n");
return 0;
} else {
// 有就绪的描述符
if (FD_ISSET(STDIN_FILENO, &read_fds)) { // 如果标准输入就绪
char buf[20] = {0};
int n_read = read(STDIN_FILENO, buf, 10);
if (n_read == -1) {
perror("read");
return 1;
}
fputs(buf, stdout);
}
}
return 0;
}
方法二 poll :
int poll (struct pollfd *fds, // struct pollfd的数组
unsigned int nfds, // 上面数组中的元素个数
int timeout); // 等待的时间长度(毫秒),负数表示永远等待,0表示立即返回
// 其中 struct pollfd如下:
struct pollfd {
int fd; // 监视的文件描述符
short events; // 要监视的事件,由用户设置
short revents; // 发生的事件,内核设置
};
参数 fds 说明: 一个struct pollfd代表一个文件描述符,结构内:fd是文件描述符值,events是用来被用户(即程序员)设置希望可监听的属性(例如可读、可写等等),revents是内核返回时设置的已发生的事件(例如这个描述符已经可写、可读等等)。
返回值:返回pollfd结构体中,revents非零的结构体的个数(其实就是有事件发生的描述符个数)。
并且在返回时,可查看相应文件描述符的pollfd结构中的 revents字段,根据此字段判断描述符是否可操作。
程序示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/poll.h>
int main() {
struct pollfd fds[2];
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN; // 监听标准输入是否可读
int time_wait = 3;
int poll_rslt = poll(fds, 1, time_wait * 1000);
if (poll_rslt == -1) { // poll() error
perror("poll");
return 1;
} else if (poll_rslt == 0) { // non fd is ready
fprintf(stdout, "No fd is available.\n");
return 0;
} else {
if (fds[0].revents & POLLIN) { // 判断 fds[0]是否可读
char buf[20] = {0};
ssize_t n_read = read(STDIN_FILENO, buf, 5);
if (n_read == -1) {
perror("read");
return 1;
}
puts(buf);
}
}
return 0;
}