基本思想
1、先构造一张有关文件描述符的表,然后使用我们的select、poll、epoll函数
2、我们的应用程序会将这张表复制给内核。
3、内核层初始化表中的需要检测的描述符。
4、当检测到有文件操作时,则立即将文件描述符作为标志并返回给应用程序。
5、应用程序根据内核返回的表来进行相应的I/O操作。
函数原型
select
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
参数说明:
nfds: 程序中最大的文件描述符值 + 1(注意:一般我们自定义的文件描述符是从3开始,0,1,2被系统占用为stderr,stdout,stdin)
readfds: 读操作文件描述符的集合.
writefds: 写操作文件描述符的集合.(在线程池当中可能用到)
exceptfds: 异常文件描述符集合.(基本不会使用)
timeout: 函数超时检测.
如果为 NULL 则一直等待,直到有可操作的文件描述符
比如:2秒的话,表示2秒后如果没有文件描述符可操作则立即返回
返回值:错误 -1 .
正确返回select函数检测到的能够执行操作的文件描述符个数.
void FD_CLR(int fd, fd_set *set);
清除集合表当中的指定文件描述符fd.
int FD_ISSET(int fd, fd_set *set);
判断文件描述符是否在表内为1.
void FD_SET(int fd, fd_set *set);
把文件描述符加入集合
void FD_ZERO(fd_set *set);
把集合表中所有文件描符清空
示例:
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd = open("/dev/input/mice", O_RDONLY);//打开我们Linux下的鼠标文件
if(fd == -1)
{
perror("open");
return -1;
}
char buf[32] = {0};
fd_set rfds, t_fds;//创建两张表,一张用于操作,一张用于维持原来的状态
FD_ZERO(&rfds); //把集合中的所有文件描述清空
FD_SET(0, &rfds);//最开始我们将所有的状态都置为0
FD_SET(fd, &rfds);//将我们的鼠标文件描述加入到文件描述表中
int max_fd = fd + 1;
int i = 0;
int ret = 0;
struct timeval t_val = {3, 0};//初始化时间
while(1)
{
t_fds = rfds;
t_val.tv_sec = 3;
t_val.tv_usec = 0;
if((ret = select(max_fd, &t_fds, NULL, NULL, &t_val)) <= 0)//调用select函数
{
if(ret == 0)
{
printf("timeout-----------\n");
//return -1;
continue;
}
else
{
perror("select");
return -1;
}
}
for(i = 0; i < max_fd; i++)//逐一检测从0到最大的fd中的可操作文件描述符,或者进行我们想要的操作
{
if(1 == FD_ISSET(i, &t_fds))
{
if(i == 0)//i=0时从键盘输入数据
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
printf("%s\n", buf);
}
else if(i == fd)//等于我们的fd时,获取鼠标的坐标
{
memset(buf, 0, sizeof(buf));
read(fd, buf, sizeof(buf));
printf("%d--%d--%d\n", buf[0], buf[1], buf[2]);
}
}
}
}
return 0;
select函数的缺点:
1、文件描述符有上限,Linux系统一般默认1024个.
2、监听表与返回表是同一张表,没有实现分离.
3、文件描述符表需要被复制到内核,又需要从内核复制到用户区.
4、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.
poll
函数原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数说明:
fds为系统指定的结构体数组(传首地址),nfds最大的文件描述符+1,timeout设置的超时,其值为负整数或正整数
示例:
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#define FDSIZE 3002//最大的文件描述符个数
int main(int argc, char *argv[])
{
struct pollfd fd_arr[FDSIZE];//定义结构体类型
int m_fd = open("/dev/input/mice", O_RDONLY);
if(m_fd == -1)
{
perror("open");
return -1;
}
/*init*/
bzero(fd_arr, FDSIZE);//清空结构体
int i = 0;
for(i = 0; i < FDSIZE; i++)//将结构体里面的fd全部置为-1
{
fd_arr[i].fd = -1;
}
int maxpos = 0;
fd_arr[maxpos].fd = 0;
fd_arr[maxpos].events = POLLIN;//pollon为系统定义的宏,意思是读操作
maxpos++;
fd_arr[maxpos].fd = m_fd;
fd_arr[maxpos].events = POLLIN;
maxpos++;
char buf[32] = {0};
while(1)
{
if(-1 == poll(fd_arr, FDSIZE, 0))//调用poll函数
{
perror("poll");
return -1;
}
for(i = 0; i < maxpos; i++)//逐一检测fd的状态,实现我们想要进行的操作
{
if(fd_arr[i].fd == -1)
continue;
if(fd_arr[i].revents == POLLIN)
{
if(fd_arr[i].fd == 0)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
printf("buf=%s\n", buf);
}
else if(fd_arr[i].fd == m_fd)
{
memset(buf, 0, sizeof(buf));
read(m_fd, buf, sizeof(buf));
printf("%d\n", buf[0]);
}
}
}
}
return 0;
}
poll的优缺点:
优点:
1、文件描述符无上限(使用链表进行管理),但是受硬件限制. 2、监听表与返回表实现分离.
缺点:
1、文件描述符表需要被复制到内核,又需要从内核复制到用户区.
2、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.
epoll
epoll跟上面两个函数,稍微有点区别,它的函数包括了create,ctl,wait等,可以说是一个函数族
函数原型:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
函数和参数说明
epoll_create:返回的是一个引用epoll后的文件描述符,该文件描述符为后面所有文件描述符的接口,这跟epoll的实现有关系,相当于创建一个棵树,size为树的节点个数,
epoll_ctl:文件描述符控制函数(把文件描述符添加到树上、或者从数上删除以及修改)
epfd: epoll_create 函数返回的树操作句柄
op: 要对文件描述符执行的操作.
EPOLL_CTL_ADD: 在树中添加文件描述符
EPOLL_CTL_MOD: 修改
EPOLL_CTL_DEL: 删除.
fd: 要操作的文件描述符.
event: 一个结构体的地址.
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events: 文件描述符需要的操作
例如:读:EPOLLIN
写:EPOLLOUT
异常:EPOLLERR
data是一个联合体:
通常使用第二个成员fd.代表需要检测的文件描述符
返回值: 正确返回 0 错误返回 -1
epoll_wait:系统调用等待文件描述epfd引用的实例
epfd: epoll_create 函数返回的树操作句柄
events: 结构体数组的首地址.
maxevents: 数组中要操作的文件描述符最大个数.(一般和create函数传入的参数一致)
timeout:
-1: 代表一直等待直到有io操作才返回
0 :不等待立即返回
>0: 定时的时间 单位毫秒
返回值:
正确返回 可操作的文件描述符个数
错误返回 -1
示例:
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#define MAXSIZE 2000
int main(int argc, char *argv[])
{
int i = 0;
int m_fd = open("/dev/input/mice", O_RDONLY);
if(m_fd == -1)
{
perror("open");
return -1;
}
struct epoll_event epfd, lfds[MAXSIZE];//定义操作句柄和结构体数组
int e_fd = epoll_create(1);
if(e_fd == -1)
{
perror("epoll_create");
return -1;
}
bzero(lfds, sizeof(lfds));
bzero(&epfd, sizeof(epfd));
epfd.events = EPOLLIN;//EPOLL系统定义的宏,读操作
epfd.data.fd = 0;
if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, 0, &epfd))//将e_fd加入到我们的操作树句柄中
{
perror("ctl");
return -1;
}
epfd.events = EPOLLIN;//设置读操作
epfd.data.fd = m_fd;//将fd设置为我们的鼠标fd
if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, m_fd, &epfd))
{
perror("ctl");
return -1;
}
int nread = 0;
char buf[32] = {0};
while(1)
{
if((nread = epoll_wait(e_fd, lfds, MAXSIZE, -1)) == -1)//调用epoll_wait函数,并保留我们可操作的文件描述符个数
{
perror("epoll_wait");
return -1;
}
printf("fd=%d\n", lfds[0].data.fd);
for(i = 0; i < nread; i++)//根据自己的需求进行对相应的文件描述符进行操作
{
if(lfds[i].events == EPOLLIN)
{
if(lfds[i].data.fd == 0)
{
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
printf("buf=%s\n", buf);
}
else if(lfds[i].data.fd == m_fd)
{
memset(buf, 0, sizeof(buf));
read(m_fd, buf, sizeof(buf));
printf("%d\n", buf[0]);
}
}
}
}
return 0;
}
epoll函数的优缺点:
优点:
1、文件描述符无上限,但是受硬件限制.
2、监听表与返回表实现分离.
3、文件描述符表采用映射(mmap)机制.
4、利用二叉树(红黑树) 以及一个就绪链表来管理文件描述符,就绪链表只会有就绪的IO 操作(不会有无意义的循环).
缺点:
1、内部数据结构的管理比较麻烦,如果文件描述符少的情况下建议使用select或poll