例子:我们在编写套接字编程的代码时,要把监听套接字放在epoll中来,让epoll帮我们来进行等待连接到来,连接到来事件叫做读事件。
这时候我们通过调用函数把监听套接字放入到红黑树中,Linux内核会对每一个事件对象创建一个epintm结构体。
判断也没有事件只需要通过rdllist是否为空。
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
LT:水平触发,不断的提醒你有事件到来,直到你把事件全部执行完
ET:边缘触发:只提醒你一次有事件到来,如果不执行,就要等下一次事件到来。
ET模式下:要采用非阻塞的方式进行读操作。且要不断的读,直到读完。
如果不采用非阻塞方式读取,则可能会阻塞住。
创建句柄
int epoll_create(int size);
返回一个整数,是一个文件描述符。
使用:int epfd=epoll_create(256);
添加事件到红黑树中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op:是一个宏
EPOLL_CTL_ADD:添加
EPOLL_CTL_MOD:修改
EPOLL_CTL_DEL:删除
struct epoll_event{
uint32_t events; //事件
epoll_data_t data; /* User data variable */
}
typedef union epoll_data {
void *ptr;
int fd; //文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
一般events填EPOLLIN(读事件)、EPOLLOUT(写事件)。
返回值:成功返回0,失败返回-1
//拿出事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
timeout:轮询检查的事件
返回值:成功返回事件到达的数量。
使用:
int epfd=epoll_create(256);
struct epoll_event item;
item.data.fd=sock; //添加套接字
item.events=EPOLLIN //只关心读时间
epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&item);//添加事件到红黑树
struct events eve[64];
int num=epoll_wait(epfd,eve,64,1000);
//有事件到来,会把事件的节点拷贝到eve数组中,
//我们只需要遍历数组就可以进行读或者写。
for(int i=0;i<num;i++){
if(eve[i].events & EPOLLIN){
int sock=eve[i].data.fd;
开始进行读操作(进行读操作时要区分是连接到来,还只是进行读取)
}
else if(eve[i].event& EPOLLOUT){
int sock=eve[i].data.fd;
开始进行写操作
}
}
sock.hpp//创建套接字
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#define LONG 5
using namespace std;
class Socket{
public:
static void Sock(int& listen_sock)
{
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0){
LOG(ERROR,"socket error...");
exit(1);
}
LOG(INFO,"socket success...");
}
static void Bind(int listen_sock,int port)
{
struct sockaddr_in own;
memset(&own,0,sizeof(own));
own.sin_family=AF_INET;
own.sin_port=htons(port);
own.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&own,sizeof(own))<0){
LOG(error,"bind error...");
exit(2);
}
LOG(INFO,"bind sucess...");
}
static void Listen(int listen_sock)
{
if(listen(listen_sock,LONG)<0){
LOG(error,"listen error...");
exit(3);
}
LOG(INFO,"listen succsee...");
}
};
reactor.hpp
#pragma once
#include
#include
#include
#include
#include
#include"log.hpp"
using namespace std;
#define MAX_NUM 64
class Reactor;
class EventItem;
typedef int(*callback_t)(EventItem *);
每一个事件都要对应一个这个结构体
class EventItem
{
public:
//定义回调函数
callback_t recv_handler;
callback_t send_handler;
//sock
int sock;
//缓存
string inbuffer;
string outbuffer;
//回指向Reactor
Reactor* R;
public:
EventItem():sock(0),R(nullptr),recv_handler(nullptr),send_handler(nullptr)
{}
//注册回调函数
void MakeCallBack(callback_t _recv,callback_t _send)
{
recv_handler=_recv;
send_handler=_send;
}
~EventItem(){}
};
class Reactor
{
private:
//epoll句柄
int epfd;
//使用哈希容器来一一对应
unordered_map<int,EventItem> mp;
public:
Reactor(){}
void InitReactor()
{
epfd=epoll_create(256);
if(epfd<0){
LOG(ERROR,"epoll_create error...");
exit(5);
}
LOG(INFO,"epoll_create success..."+to_string(epfd));
}
添加事件到红黑树中
void AddToEpoll(int sock,uint32_t ev,const EventItem& item)
{
struct epoll_event event;
event.events=0;
event.events|=ev;
event.data.fd=sock;
string s=to_string(sock);
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&event)<0){
LOG(ERROR,"epoll_ctl add error...sock:"+s);
}
else{
mp.insert({sock,item});
LOG(INFO,"epoll_ctl add success...sock:"+s);
}
}
删除红黑树中的事件
void RevokeEpoll(int sock)
{
if(epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr)<0){
string s=to_string(sock);
LOG(ERROR,"epoll_ctl del error...sock:"+s);
}
删除哈希中的映射
mp.erase(sock);
}
void Assignment(int timeout)
{
struct epoll_event revs[MAX_NUM];
int num=epoll_wait(epfd,revs,MAX_NUM,timeout);
事件到来轮询recv中的节点
for(int i=0;i<num;i++){
int sock=revs[i].data.fd;
uint32_t mask=revs[i].events;
if(mask & EPOLLIN){
if(mp[sock].recv_handler)
mp[sock].recv_handler(&mp[sock]);调用回调函数进行读
}
else if(mask & EPOLLOUT){
if(mp[sock].send_handler)
mp[sock].send_handler(&mp[sock]);调用回调函数进行写
}
}
}
~Reactor()
{
if(epfd>=0)
close(epfd);
}
};
insertface.hpp
#pragma once
#include
#include
#include
#include
#include"reactor.hpp"
#include"util.hpp"
int recver(EventItem* eve);
连接到来
int accepter(EventItem* eve)
{
struct sockaddr_in oth;
socklen_t len=sizeof(oth);
int sock=accept(eve->sock,(struct sockaddr*)&oth,&len);
if(sock<0){
}
else{
SelNonBlock(sock);
EventItem item;
item.sock=sock;
item.R=eve->R;
item.MakeCallBack(recver,nullptr);
Reactor* ptr=eve->R;
ptr->AddToEpoll(sock,EPOLLIN|EPOLLET,item);
}
}
只写了连接到来的接口,读没有写
int recver(EventItem* eve)
{
}
log.hpp//日志,方便知道走到哪里了
#pragma once
#include
#include
#include
using namespace std;
#define INFO 1
#define ERROR 2
采用宏来调用,具有可读性
#define LOG(str1,str2) log(#str1,str2,__FILE__,__LINE__)
void log(string str1,string str2,string str3,int line)
{
cout<<"["<<str1<<"]"<<"["<<time(nullptr)<<"]"<<"["<<str2<<"]"<<"["<<str3<<"]"<<"["<<line<<"]"<<endl;
}
util.hpp
#pragma once
#include
#include
#include
设置非阻塞
void SelNonBlock(int sock)
{
int fl = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, fl | O_NONBLOCK);
}
server.cc
#include
#include
#include"sock.hpp"
#include"reactor.hpp"
#include"insertface.hpp"
#include"util.hpp"
int main(int argc,char* argv[])
{
if(argc!=2){
exit(4);
}
int port=atoi(argv[1]);
int listen_sock=-1;
创建套接字
Socket::Sock(listen_sock);
Socket::Bind(listen_sock,port);
Socket::Listen(listen_sock);
创建Reactor,并初始化
Reactor Re;
Re.InitReactor();
创建监听套接字对应的结构
EventItem item;
item.sock=listen_sock;
item.R=ℜ
把监听套接字设置成非阻塞
SelNonBlock(listen_sock);
注册回调函数,我这里把读注册了accept,没有注册写
item.MakeCallBack(accepter,nullptr);
添加到红黑树中,采用ET模式
Re.AddToEpoll(listen_sock,EPOLLIN|EPOLLET,item);
int timeout=1000;
一直调用检查事件是否到来
while(true){
Re.Assignment(timeout);
}
}
整个的精髓是,把 epoll 和 读写分开了,采用了回调函数的方法,只不要管reactor.hpp,只需要在insertface.hpp中添加函数就行。