select是同步的,由进程自己操作
int select
(int nfds,fd_set * readfds,fd_set * writefds,fd_set* exceptfds,struct timeval * timeout );
返回值:
大于0:所有监听集合(3个)中,满足对应事件的总数
0:没有满足的
-1:error
参数:
nfds: 最大的描述符+1的值
readfds/writefds/exceptfds: 可读/可写/异常事件监控集合,关心则将描述符加入集合
timeout:timeval{tv_sec,tv_usec} select超时等待时间
select 默认timeout 为NULL,永久阻塞,直到,直到描述符就绪
1.用户自己定义相应事件(读,写,异常)描述符集合 fd_set;
用户关心 客户事件某一事件(如可读),则添加到集合(如 fd_set* readfds),关心所有的事件,则添加到所有集合
2.select将集合拷贝到内核
遍历轮询,判断有没描述符就绪对应事件
3.若集合中有描述符就绪,先遍历完毕集合,然后把没有就绪的文件描述符从集合移除,这个时候返回调用,通知进程有多少描述符就绪
4.进程处理就绪的描述符
fd_set是一个位图,用于存储监控的描述符,需要监控则置1,大小取决于__FD_SETSIZE=1024宏
void FD_CLR(int fd, fd_set * set); //将一个文件描述符从集合中移除
int FD_ISSET(int fd, fd_set * set); //判断一个文件描述符是否在一个集合中,返回值:在 1,不在 0
void FD_SET(int fd, fd_set * set); //将监听的文件描述符,添加到监听集合中
void FD_ZERO(fd_set * set); //清空一个文件描述符集合
优缺点:
select 能够监控的描述符有上限,1024 __FD_SETSIZE
缺点
linxu 下使用,
原理:
int poll( struct pollfd * fds,nfds_t nfds,int timeout)
struct pollfd{
int fd;
short events;//requested,描述符关心事件 POLLIN/POLLOUT
short revents;…returned 描述符实际就绪的事件
}
实现原理:
1 用户定义描述符事件数组,向数组中添加关心的描述符事件
2 将pollfd事件数组拷贝到内核轮询监控,判断是否就绪关心的事件events
3 把描述符实际就绪的事件信息,标记到revents
3 当poll 返回,用户遍历pollfd事件数组,判断每一个事件revents是否是关心的事件,
poll 对比select 没有监控的文件描述符上限
采用事件结构信息进行描述符监控,简化了三种描述符的操作流程
但是poll依然需要将事件结构信息拷贝到内核进行监控
在内核中需要轮询遍历的方式进行描述符事件监控(随着描述符增多而性能降低)
poll也不会告诉用户具体哪个描述符就绪,需要遍历判断revents来决定描述符应该进行何种操作
int epoll_create(int size) //在内核中创建eventpoll结构体
// size 决定epoll监控多少描述符
//返回值:返回描述符,决定epoll的操作句柄
//struct eventpoll { 红黑树,双向链表}
int epoll_ctl (int epfd,int op,int fd,struct epoll_event * event)
对内核eventpoll结构体操作,采用时间结构方式对描述符进行事件监控 用户定义struct
epoll_event描述符事件结构信息,将事件信息可以拷贝到北河,添加到eventpoll的红黑树结点
epfd
:epoll 操作句柄
op
:对内核eventpoll进行的操作
EPOLL_CTL_ADD 向红黑树中添加描述符fd的监控事件信息event
EPOLL_CTL_DEL 从红黑树中移除fd的监控事件信息event
EPOLL_CTL_MOD 修改描述符fd在红黑树的监控事件信息event
fd
: 用户需要监控的描述符
event
: 描述符对应的事件结构信息
struct epoll_event{
uint32_t events;//用户对描述符进行监控的事件(EPOLLIN/EPOLLOUT 读写)
union{
int fd;
void* ptr;
}data
}
int epoll_wait (int epfd,struct epoll_event* events,int maxevents, int timeout)
epfd
:epoll 操作句柄
events
:epoll _event事件结构信息数组
maxevents
:epoll_events
epoll监控流程:
epoll 对描述符的事件监控是一个异步操作;epoll_wait 发起调用,让操作系统对描述符进行相应事件监控,操作系统对每个要监控的描述符都定义了就绪事件回调函数,当描述符相应事件就绪的时候,触发事件,调用回调函数(将描述符事件结构信息添加到eventpoll的双向链表中)
epoll_wait没有返回(异步阻塞操作),每隔一会查看eventpoll中双向链表是否为空(为空:没有描述符就绪,则等待一会,链表不为空表示有描述符就绪;将这个描述符对应事件结构信息,拷贝到epoll_wait传入的事件结构数组中后调用返回,事件回调的方式)
epoll_wait 将就绪的描述符对应事件结构信息拷贝到events结构数组;相当于直接告诉用户哪个描述符就绪;
//epoll大致逻辑代码
bool Init()
bool add(sock)
bool del(sock)
bool wait(list ,int timeout)
class Epoll
{
private:
int _epfd;
public:
Init()
{
_epfd=epoll_create(1);
}
Add(sock)
//int epoll_ctl (int epfd,int op,int fd,struct epoll_event * event)
{
fd=sock.getfd();
struct epoll_event ev;
ev.events=EPOLLIN;//读
//=EPOLLIN|EPOLLET j就是边缘触发了
int ret=epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);//ret<0==>return false
}
Del(sock)
{
//同add
//epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,NULL)
}
Wait(vector<tcosocket> & list,int timeout_msec=3000)//3000毫秒,3秒
{
// int epoll_wait (int epfd,struct epoll_event* events,int maxevents, int timeout)
struct epoll_event evs[MAX_EVENTS];
int nfds=epoll_wait(_epfd,evs,MAX_EVENTS,timeout_msec);//nfds<0,=0 出错和超时
for(0-nfds)
{
tcpsocket sock;
sock(evs[i].data.fd)
list.push_back(sock);//list里面的都是就绪的
}
}
}
水平触发
默认
可读事件就绪:接受缓冲区中数据大小,大于低水位标记
可写事件就绪:发送缓冲区中空闲空间大小,大于低水位标记
只要接受/发送缓冲区数据大于低水位标记
就会一直触发事件
边缘触发
可读事件就绪:接受缓冲区,只要新数据到来才触发
可写事件就绪 :发送缓冲区中,只有剩余空间大小从0变成>0时才会触发一次
边缘触发中,只有新数据到来的时候,可读事件才会被触发一次
需要用户在这一次事件中把缓冲区中数据全部读取完毕为止(循环读到不能读为止)
但是recv中缓冲区没有数据时,recv会阻塞,为了避免循环读取数据导致进程阻塞,把描述符设置为非阻塞
//fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK)
事件结构方式对描述符进行监控,简化了select集合操作的流程
epoll描述符监控无上限
每个epoll 监控的描述符事件信息,只需要向内核拷贝一次
epoll_wait使用异步阻塞操作在内核完成事件监控
epoll 直接通过epoll_wait 传入的事件结构数组 向用户返回就绪的事件信息
直接告诉用户哪些描述符就绪,不需要用户进行空遍历
场景
大量描述符监控,同一时间部分描述符活跃的场景 事件监控