「这是我参与2022首次更文挑战的第 23 天,活动详情查看:2022首次更文挑战」。
reactor
先说说基本的 epoll 编程:
epoll 编程
- epoll_create ⇒ 创建一个fd的池子,负责监管fd
- epoll_ctl ⇒ 负责管理这个池子的fd的增删查改
- epoll_wait ⇒ 负责让出CPU调度,有事件发生才会从这里被唤醒
高效的原因:
- 内部管理fd使用了高效的红黑树,增删改的性能优秀
- epoll_ctl 添加fd时,会执行回调函数;会将对应的**fd结构体(epitem)**放入就绪队列(双向链表);epoll池只需要遍历这个队列,就可以返回给上层已经就绪的fd数组
注意几个要点:
int sock_fd,conn_fd; // 监听套接字和已连接套接字的变量
sock_fd = socket() // 创建套接字
bind(sock_fd) // 绑定套接字
listen(sock_fd) // 在套接字上进行监听,将套接字转为监听套接字
epfd = epoll_create(EPOLL_SIZE); //创建epoll实例,
// 创建epoll_event结构体数组,保存套接字对应文件描述符和监听事件类型
ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE);
// 创建epoll_event变量
struct epoll_event ee
// 监听读事件
ee.events = EPOLLIN;
// 监听的文件描述符是刚创建的监听套接字
ee.data.fd = sock_fd;
// 将监听套接字加入到监听列表中
epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ee);
while (1) {
// 等待返回已经就绪的描述符
n = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
// 遍历所有就绪的描述符
for (int i = 0; i < n; i++) {
// 如果是监听套接字描述符就绪,表明有一个新客户端连接到来
if (ep_events[i].data.fd == sock_fd) {
conn_fd = accept(sock_fd); //调用accept()建立连接
ee.events = EPOLLIN;
ee.data.fd = conn_fd;
// 添加对新创建的已连接套接字描述符的监听,监听后续在已连接套接字上的读事件
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ee);
} else { // 如果是已连接套接字描述符就绪,则可以读数据
...// 读取数据并处理
}
}
}
- 针对的socket:是服务端所在的socket,监听也是这个上的读写事件
- 可读事件:有客户端发送数据到服务端所在的socket → 客户端三次握手成功,开始
支撑整个框架运行的主要函数:
- 框架主循环的 aeMain()
- 实践捕获和分发的 aeProcessEvents()
- 负责事件和 handler注册的 aeCreateFileEvent()
主循环
就是不断判断事件循环的结束标志:如果被标记为 true,整个就结束了
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
**// 判断是否结束**
while (!eventLoop->stop) {
…
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}