poll


参数:
1.fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合.
2.nfds表示fds数组的长度.
3.timeout表示poll函数的超时时间, 单位是毫秒(ms).
events和revents的取值:

poll的返回结果:
返回值小于0, 表示出错;
返回值等于0, 表示poll函数等待超时;
返回值大于0, 表示poll由于监听的文件描述符就绪而返回.
poll的优点 :
1.pollfd结构包含了要监视的event和发生的event,不再使用select"参数-值"传递的方式. 接口使用比select更方便.
2.poll并没有最大数量限制 (但数量过大后性能也会下降).
poll的缺点:
1.和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符.
2.每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中.
3.同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效
率也会线性下降.
例:使用poll监控标准输入
cpp
#include <unistd.h>
#include <stdio.h>
#include <poll.h>
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);
}
}
}
epoll
epoll_create:
int epoll_create(int size);
创建一个epoll的句柄.自从linux2.6.8之后,size参数是被忽略的.
用完之后, 必须调用close()关闭.
epoll_ctl:epoll的事件注册函数.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示.
第三个参数是需要监听的fd.
第四个参数是告诉内核需要监听什么事.
第二个参数的取值:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd;

events可以是以下几个宏的集合:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
EPOLLERR : 表示对应的文件描述符发生错误;
EPOLLHUP : 表示对应的文件描述符被挂断;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要
再次把这个socket加入到EPOLL队列里.
epoll_wait:收集在epoll监控的事件中已经发生的事件.
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数events是分配好的epoll_event结构体数组.
epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).
maxevents告知内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.
参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.
epoll的优点
1.接口使用方便: 虽然拆分成了三个函数, 但使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开.
2.数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝).
3.事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
4.没有数量限制: 文件描述符数目无上限.
示例:
Epoller.hpp
cpp
#pragma once
#include "nocopy.hpp"
#include "log.hpp"
#include <cerrno>
#include <cstring>
#include <sys/epoll.h>
class Epoller : public nocopy
{
static const int size=128;
public:
Epoller()
{
_epfd=epoll_create(size);//
if(_epfd==-1)
{
lg(Error,"epoll create error:%s",strerror(errno));
}
else
{
lg(Info,"epoll create success:%d",_epfd);
}
}
int EpollerWait(struct epoll_event revents[],int num)
{
int n=epoll_wait(_epfd,revents,num,-1);
return n;
}
int EpollerUpdate(int oper,int sock,uint32_t event)
{
int n=0;
if(oper==EPOLL_CTL_DEL)
{
n=epoll_ctl(_epfd,oper,sock,nullptr);
if(n!=0)
{
lg(Error,"epoll_ctl delete error!");
}
}
else
{
//修改或添加
struct epoll_event ev;
ev.events=event;
ev.data.fd=sock;//方便我们后期得知是哪一个fd就绪了。
n=epoll_ctl(_epfd,oper,sock,&ev);
if(n!=0)
{
lg(Error,"epoll_ctl error!");
}
}
return n;
}
~Epoller()
{
if(_epfd>=0)
close(_epfd);
}
private:
int _epfd;
int _timeout{3000};
};
EpollServer.hpp
cpp
#pragma once
#include <iostream>
#include <memory>
#include "Socket.hpp"
#include "log.hpp"
#include "nocopy.hpp"
#include "Epoller.hpp"
using namespace std;
uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);
class EpollServer : public nocopy
{
static const int num = 64;
public:
EpollServer(uint16_t port)
:_port(port),_listsocket_ptr(new Sock()),_epoller_ptr(new Epoller())
{}
void Init()
{
_listsocket_ptr->Socket();
_listsocket_ptr->Bind(_port);
_listsocket_ptr->Listen();
lg(Info,"create listensocket success:%d\n",_listsocket_ptr->Fd());
}
void Accepter()
{
std::string clientip;
uint16_t clientport;
int sock=_listsocket_ptr->Accept(clientip,clientport);
if(sock>0)
{
_epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,EVENT_IN);
lg(Info,"get a new link,client info@ %s:%d",clientip.c_str(),clientport);
}
}
void Recver(int fd)
{
char buffer[4096];
ssize_t n=read(fd,buffer,sizeof(buffer)-1);
if(n>0)
{
buffer[n]=0;
cout<<"client say@ "<<buffer<<endl;
string echo_string="server echo: ";
echo_string+=buffer;
write(fd,echo_string.c_str(),echo_string.size());
}
else if(n==0)
{
cout<<"client quit,me too!"<<endl;
_epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
close(fd);
}
else
{
cout<<"read error,fd: "<<fd<<endl;
_epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL,fd,0);
close(fd);
}
}
void Dispatcher(struct epoll_event revs[],int num)
{
for(int i=0;i<num;i++)
{
uint32_t events=revs[i].events;
int fd=revs[i].data.fd;
if(events&EVENT_IN)
{
if(fd==_listsocket_ptr->Fd())
{
Accepter();
}
else
{
Recver(fd);
}
}
else if(events&EVENT_OUT)
{
}
else
{}
}
}
void Start()
{
_epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD,_listsocket_ptr->Fd(),EVENT_IN);
struct epoll_event revs[num];//接收就绪事件结果的数组。
while(true)
{
int n=_epoller_ptr->EpollerWait(revs,num);
if(n>0)
{
lg(Debug,"event happened,fd is:%d",revs[0].data.fd);
Dispatcher(revs,n);
}
else if(n==0)
{
lg(Info,"time out...");
}
else
{
lg(Error,"epoll wait error!");
}
}
}
~EpollServer()
{
_listsocket_ptr->Close();
}
private:
std::shared_ptr<Sock> _listsocket_ptr;
std::shared_ptr<Epoller> _epoller_ptr;
uint16_t _port;
};