int poll(struct pollfd *arr_fds, nfds_t nfds,int timeout)
返回值:>1表示就绪的描述符事件个数;
返回值==0–等待超时;
<0–表示监控出错
poll监控采用事件结构体的形式:
struct poolfd{
int fd;//要监控的描述符
short events;//要监控的事件
short revents;//调用返回时填充的就绪事件
}
#include
#include
#include
int main(){
struct pollfd poll_fd;
poll_fd.fd = 0; poll_fd.events = POLLIN;
for (;;) {
int ret = poll(&poll_fd, 1, 1000);
if (ret < 0) {
perror("poll");
continue;
}
if (ret == 0) {
printf("poll timeout\n");
continue;
}
if (poll_fd.revents == POLLIN) {
char buf[1024] = {0};
read(0, buf, sizeof(buf) - 1);
printf("stdin:%s", buf);
}
}
}
1.创建epoll句柄
int epoll_create(int size//创建epoll句柄
//size:在linux2.6.2之后被忽略,只要大于0即可
//返回值:文件描述符---epoll的操作句柄
2.epoll事件注册
int epoll_ctl(int epfd, int cmd, int fd,
struct epoll_event* ev);
//epfd:epoll_create返回的操作句柄
//cmd:针对fd描述符的监控信息要进行的操作-添加/删除/修改 EPOLL_CTL_ADD/EPOLL_CTL_DEL/EPOLL_CTL_MOD
//fd:要监控操作的描述符
//ev:fd描述符对应的事件结构体信息
struct epoll_event{
uint32_t events;//对fd描述符要监控的事件--EPOLLIN/EPOLLOUT
union{int fd; void *ptr;}data;//要填充的描述符信息
}
events可以是以下几个宏的集合:
一旦epoll开始监控,描述符若就绪了进行关心的事件,则就会给用户返回我们所添加的对应时间结构体信息,通过时间结构体信息中所包含的描述符进行操作—因此第三个参数与结构体中的fd描述符通常是同一个描述符
3.开始监控
int epoll_wait(int epfd, struct epoll_event* evs,
int max_event, int timeout);
//epfd:epoll操作句柄
//evs:struct epoll_event结构体数组的首地址,用于接收就绪描述符对应的时间结构体信息
//max_event:本次监控想要获取的就绪事件的最大数量,不大于evs数组的最大节点个数,防止访问越界
//timeout:超市等待时间单位:毫秒
返回值:>0–就绪的事件个数
==0–等待超时
<0–监控出错
epoll的监控原理:异步阻塞操作
监控由系统完成,用户添加的描述符以及对应事件结构体会被添加到内核的eventpoll结构体中的红黑树中
一旦发起调用开始监控,则操作系统为每个操作符的事件做了一个回调函数,功能室当描述符就绪了关心的事件,则将描述符对应的事件结构体添加到双向链表中
进程自身,只是每隔一段时间,判断双向链表是否为NULL,决定是否有就绪
4.进程遍历获取evs中就绪的事件结构体信息,针对其中的events就绪时间对data.fd进行相应操作
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.
epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反.
对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll.
例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网APP的入口服务器, 这样的服务器就很适合epoll.
如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适. 具体要根据需求和场景特点来决定使用哪种IO模型.
#include
#include"tcpsock.hpp"
#include
#include
#define MAX_TIMEOUT 3000
class Epoll
{
public:
Epoll():_epfd(-1){
_epfd=epoll_create(1);
if(_epfd<0){
perror("create epoll error\n");
exit(-1);
}
}
bool Add(TcpSocket &sock){
//2.添加描述符监控事件信息
//获取描述符
int fd=sock.GetFd();
//定义描述符对应的事件结构体
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=fd;
//添加到内核中
int ret=epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);
if(ret<0){
perror("epoll ctl error\n");
return false;
}
return true;
}
bool Del(TcpSocket &sock){
int fd=sock.GetFd();
int ret=epoll_ctl(_epfd,EPOLL_CTL_DEL,fd,NULL);
if(ret<0){
perror("epoll ctl error\n");
return false;
}
return true;
}
bool Wait(std::vector<TcpSocket> *list,int timeout=MAX_TIMEOUT){
struct epoll_event evs[10];
int nfds=epoll_wait(_epfd,evs,10,timeout);
if(nfds<0){
perror("epoll wait error\n");
return false;
}else if(nfds==0){
printf("epoll wait timeout\n");
list->clear();
return true;
}
for(int i=0;i<nfds;i++){
if(evs[i].events&EPOLLIN){
TcpSocket sock;
sock.SetFd(evs[i].data.fd);
list->push_back(sock);
}
}
return true;
}
private:
int _epfd;
};