reactor的应用
网络编程关注的问题:
网络IO职责
Struct eventpoll {
Struct rb_root rbr; //管理epoll监听的事件
Struct list_head rdllist; //保存着epoll_wait返回满足条件的事件
};
Struct epitem {
Struct rb_node rbn; //红黑树节点
Struct list_head rdllist;//双向链表节点
struct epoll_filefd ffd;//事件句柄信息
Struct eventpoll *ep;//指向所属的eventpoll对象
Struct epoll_event event;//注册的事件类型
};
struct epoll_event {
__uint32_t events;//epollin epollout epollel
Epoll_data_t data;//保存关联数据
};
typedef union epoll_data {
Void *ptr;
Int fd;
Uint32_t u32;
Unit64_t u64;
} epoll_data_t;
Int epoll_create(int size);
/**
Op:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
Event.events:
EPOLLIN//读事件
EPOLLOUT//写事件
EPOLLET//边缘触发模式,默认时水平触发
**/
Int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/**
Event[i].events:
EPOOLLIN
EPOLLOUT
EPOLLERR
EPOLLRDHUP
EPOLLHUP
*/
Int epoll_wait(int epfd, stuct epoll_event* events, int maxevents, int timeout);
Epoll_create会创建红黑树和就绪队列(创建一个epoll对象)。调用epoll_ctl添加epoll中的事件会与网卡驱动程序建立回调关系,通过epoll_ctl调用回调关系,把红黑树节点拷贝到就绪队列中(用户态events),maxevents是预期要读的个数通常设为512。backlog的值一般不会很大(一个事件循环)超出会refused,epoll不一定只检测listenfd。连接fd也可以监测
Struct epoll_evenet ev;
Ev.events |= EPOLLIN;
Epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &ev);
Int clientfd = accept(listenfd, addr, sz);
Struct epoll_event ev;
Ev.evnets |= EPOLLIN;//不能监听写事件,最开始写缓冲区是空的,写事件会一直被触发。
Epoll_ctl(efd, EPOLL_CTL_ADD, clientfd, &ev);
Int connectfd = socket(AF_INET, SOCK_STREAM, 0);
Connect(connectfd, (struct sockaddr*)&addr, sizeof(addr));
struct epoll_event ev;
Ev.events |= EPOLLOUT;
Epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev);
If(status == e_connecting && e->events & EPOLLOUT) {
Status = e_connected;
}
If(e->events & EPOLLRDHUP) {
//读端关闭, 客户端写端关闭,做读端关闭的对应逻辑,通常就是close(fd)
Close_read(fd);
}
If(e->events & EPOLLHUP) {
//读写端都关闭
close(fd);
}
//reactor要用非阻塞io
If(e->events & EPOLLIN) {
Int n = read(fd, buf, sz);
If(n < 0) {
If(errno == EINTR)//被信号打断,如果是边缘触发所以要continue
Continue;
If(errno == EWOULDBLOCK)//读缓冲区米有数据
Break;
Close(fd);
} else if (n==0) {//读端关闭
Close_read(fd);//close(fd)
}
//业务逻辑
}
Int n = write(fd, buf, dz);
If(n == -1) {
If (errno == EINTR)
Continue;
If(errno == EWOULDBLOCK) {
Struct epoll_event ev;
Ev.events = EPOLLOUT;
Epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}
Close(fd);
}
If(e->events & EPOLLOUT) {
Int n = write(fd, buf, sz);
If(n > 0) {
Epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
}
//当写缓冲区不够的时候,会写失败,进行写注册再次尝试写
reactor的应用(组成:io多路复用+非阻塞io,事件方式通知 事件循环)
将对io的处理转化为对事件的处理
While(1) {
Int n = epoll_wait();
For(int I = 0; I < n; I++) {
If(e->events & )
}
}
只有一个io多路复用,一个epoll对象管理,一个reactor有一个acceptor,read,write。读事件要先进行decode解密之后进行对应的业务逻辑之后将结果encode压缩成字节流发送到对端去。
Ae_epoll.c
Ae_evport.c ae.c anet.c connection.h
Ae_kqueue.c ➡ ae.h anet.h connection.c networking.c
Ae_select.c
io多路复用 io检测 io操作 协议处理(redis协议)
Redis6.0支持了io多线程就是在networking中封装的
首先在ae.c中会根据HAVE_EVPORT判断到底使用哪一个io复用(linux用aeepoll,mac用aekqueue都不是用select)
redis事件封装:ae.h中aeFileEvent(reactor中先注册事件,事件触发回调函数)mask用来对应红黑树节点状态,rfileProc,wfileProc读写事件的回调
aeFireEvent事件循环中取出来的事件
aeEventLoop代表reactor的封装。maxfd是因为select的原因。setsize为maxevent大小
在ae_epoll.c中使用aeApiCreate创建epoll对象,aeDeleteEvenetLoop删除事件,arStop停止事件。aeProcessEvents事件循环