Linux高性能服务器编程 epoll反应堆模型
epoll反应堆模型
基于该视频所做笔记,视频里面讲的也挺难的,最好先让chat给你梳理一遍整体的代码再去看视频吧
15-epoll反应堆模型总述_bilibili_哔哩哔哩_bilibili
1.epoll反应堆模型概述
核心:epoll ET模式 + 非阻塞IO + 轮询 + void *ptr(回调函数)
一般而言socket也是非阻塞的
就是原来咱们监听到事件比如说读事件的话,就得自己去写read或者recv去读数据, 现在有了ptr,就不用管了,有了读事件程序自己就帮我调了回调函数,我不用自己写了
原来步骤:
1.socket、bind、listen
2.epoll_create 创建监听 红黑树
3.返回 epfd – epo11_ct1()向树上添加一个监听fd
4.while(1) 循环着做下面的事情 5-10
5.epoll_wait 监听
6.对应监听fd有事件产生
7.返回 监听满足数组元素个数
8.判断返回数组元素
9.lfd满足 – Accept 建立连接
10.cfd 满足– read() — 小->大 – write回去
反应堆:
不但要监听cfd的读事件、还要监听cfd的写事件。
1.socket、bind、listen
2.epol1_create 创建监听 红黑树
3.返回 epfd
4.epo11_ct1()向树上添加一个监听fd
5.while(1) 着做下面的事情 6-19
6.epoll_wait 监听
7.对应监听fd有事件产生 – 返回 监听满足数组
8.判断返回数组元素数量
9.lfd满足 Accept
10.cfd 满足 read() 小->大 (此时没有调用write歇会去)
11.cfd从监听红黑树上摘下
12.epoll_ctl() 要监听 cfd的写事件 将EPOLLIN 改为EPOLLOUT 将回调函数也在这里赋值给ptr
13.EPOLL_CTL_ADD() 重新放到红黑上监听写事件
15.等待 epoll_wait 返回 – 说明 cfd 可写 – write回去
17.cfd从监听红黑树上摘下 – 将EPOLLOUT改为EPOLLIN
18.epoll_ctl() – EPOLL_CTL_ADD 重新放到红黑上监听读事件
19.epoll_wait 监听
简单来说就是write的时候你不能想写就写,而是要先判断cfd能不能写,我们测试环境没错是因为环境比较简单。
2.具体讲解
1.myevent_s结构体
1 | /* 描述就绪文件描述符相关信息 */ |
2.超时检测
1 | /* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */ |
3.initlistensocket函数–创建socket 初始化lfd
1 | /*创建 socket, 初始化lfd */ |
4.eventset–初始化myevent_s结构体
设置回调函数
1 | /*将结构体 myevent_s 成员变量 初始化*/ |
5.acceptconn–建立连接
1 | /* 当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */ |
在listen监听到有连接建立的时候 会回调accepton函数 而这里面又会调用eventset函数,在init的时候是初始化监听listen套接字有没有读事件发生,而在这里是初始化一个事件把刚刚创建的连接connfd进行监听,监听它的读或写事件
6.eventadd–向 epoll监听的红黑树 添加一个 文件描述符
讲一个fd添加到监听红黑树,同时还可以设置监听读还是写事件
1 | /* 向 epoll监听的红黑树 添加一个 文件描述符 */ |
7.recvdata – 读事件发生的时候读取数据
1 | void recvdata(int fd, int events, void *arg) |
8.senddata – 写事件发生的时候发送数据
1 | void senddata(int fd, int events, void *arg) |
9.完整代码:
1 | /* |
10.完整的流程
1.运行程序,程序建立监听套接字lfd,调用initlistensocket函数建立监听 监听套接字lfd 上的读事件的结构体
2.eventset,在这个函数中就设置了监听套接字对应的结构体的回调函数是acceptconn函数,在eventadd把lfd挂到红黑树上监听读事件,一旦有连接,就被监听到就会回调acceptconn函数来建立连接
3.有连接过来了,被监听到了,调用acceptconn,创建cfd用于连接,调用eventset初始化他的结构体,绑定读事件的回调函数是recvdata,监听它的读事件,eventadd把它挂到红黑树上
4.有读事件来了,就调用recvdata,在读完数据的之后,用event_del把这个事件从红黑树摘下来,再用eventset改为监听读事件,然后用eventadd再挂回去
5.有写事件来了,就调用senddata,在写完数据的之后,用event_del把这个事件从红黑树摘下来,再用eventset改为监听写事件,然后用eventadd再挂回去
6.循环往复
7.如果一个连接60秒内又不读也不写,那就从红黑树上摘下并断开连接
11.难点解读
1.两个if
1 | struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; |
首先要确定的是ptr的含义以及ptr、events之间的区别
笔者认为ptr和C++中的this指针作用相同
用来标识初始化结构体的时候,对这个文件描述符监听的是什么事件
而events是传出参数,是满足条件的事件的结构体
我觉得是为了避免这种情况的发生:
监听到了写事件,那么返回的events里面自然是写事件了,如果此时我一开始给结构体绑定的时候是读事件,对应的回调函数是recvdata,那不就完蛋了,本来写事件我应该调用senddata的。
但是仔细一想,这种情况似乎不会发生,因为eventadd再往上挂的时候,给结构体里面赋值的时候就和红黑树监听的事件是一样的
所以笔者也不太懂这里的意思了
2.黑马视频中说
1 | ev->call_back(ev->fd, events[i].events, ev->arg); |
这个是设置回调函数其实不太对,这是就是对应的函数的调用,直接在main里面调用了这个函数
其他没有什么比较难的了,都挺好理解的
12.我认为存在的不足
连接第一次初始化一定监听的是读事件,问题是客户端可能就是不给你发数据,反而一直请求数据,那红黑树一直都不会监听写事件的,因为没有调用recvdata,换不到监听写事件上面去的。这样还会导致超时重连,因为在服务器看来你60秒内啥也没干,其实你一直在请求数据
可能libevent库做了更加完善的代码吧,毕竟老师讲的也是阉割版
大致意思是懂了