epoll主要是用来管理网络io,而对于reactor主要是用来管理网络事件的
1 epoll编程流程
1.1 上篇博客实现的epoll简单的流程。
//创建sockfd = socket()
//构建地址
//绑定bind(sockfd,..)
//监听listen(sockfd,..)
//创建epfd = epoll_create(1)
//将epfd可读事件按照LT模式添加到epoll管理
while(1){
int nready = epoll_wait(epfd,events,....);
for( int i = 0; i < nready; i++){
if (events[i].data.fd == sockfd){
//处理.....
int clientfd = accept(epfd,.....); //新生成一个clientfd
epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,EPOLLIN);//添加到epoll中管理
//....
}else{
//....
int clientfd = events[i].data.fd;
int ret = recv(clientfd, buffer,1024,0);
if(ret <0){
// recvbuffer没有数据,多线程中会出现
}else if (ret== 0){
//客户端断开连接
}else{
//正常接收到数据
}
}
}
}
上面的代码流程没有解决两个问题:
1 同一个fd同时读写处理,如何实现?
2 对事件的判断:比如在写前判断EPOLLOUT,发送缓冲区是否满
因为server发送一个大文件到客户端需要采用循环发送。
while(1){
ret = send(…);
if (ret == -1) 发送缓冲区未满需要等待,而事件将对应的ip改成可写加入到epoll处理。
}
1.2 加入同一个fd同时读写处理和事件判断
简单的具体流程
// 通用流程简约不写。。。
while(1){
nready = epoll_wait(....);
for(int i = 0; i < nready; i++){
if (listenfd) //判断{
//accept处理....
}else{
if(EPOLLIN){}; //可写事件处理
if (EPOLLOUT){}; //可读事件处理
}
}
}
对于listenfd判断,其实就是第一个socket,在上面的循环内每次都需要判断可以将它做成一个可读事件添加到epoll管理,性能得到提升,流程改成
while(1){
nready = epoll_wait(....);
for(int i = 0; i < nready; i++){
if(EPOLLIN){ //可写事件处理
if(listenfd){{
//accept处理
}else{
// send处理
}
};
if (EPOLLOUT){}; //可读事件处理
}
}
}
对于前面的epoll编写的代码,都只能用在自己的编写的demo中,而无法应用到工业生产中。
2 reactor模型
工业级做法: 每个fd都对应一个回调函数,定义一个结构体,将sockfd和callback一一对应起来,而对于回调函数可以定义为:int callback(int fd,int events, void* arg);
结构体定义:
struct sockitem{
int sockfd;
int (*callback)(int fd, int events, void* arg);
};
## 2.1 无send_cb回调版本 对于这种对应关系,fd只要有事件(不管可读可写等)就会调用与之对应的callback,成千上万的fd都一一对应callback模式,这种模式就叫反应堆模式。 编程就简化成:
// 通用流程。。。
// 注册accept_cb
while(1){
nready = epoll_wait(epfd, ...);
for(i = 0; i < nready; i++){
if(EPOLLIN) accept_cb()调用;
if(EPOLLOUT) recv_cb()调用;
}
}
2.2 简单版本的echo模型实现
注意接收数据还需要发送数据给客户端 添加send_cb。标准的reactor模型如下
在accet_cb函数调用中
上面实现了一个简单的echo模型,接收数据发送数据。
2.3 最终版本的reactor版本
上面的版本会有两个问题:
1 半包问题(当应用程序数据没有接收完,数据该如何存储) 解决方案:添加buffer缓冲区
2 全局的epfd 一些全局的变量 解决方案:加入Mainloop
结构体定义:
//单个io状态
struct sockitem {
int sockfd;
int (*callback)(int fd, int events, void *arg);
char recvbuffer[1024];
char sendbuffer[1024];
};
// 全局变量 对应开源框架中的Mainloop/eventloop
struct reactor {
int epfd;
struct epoll_event events[512]; // 目前采用数组存储,可以采用其它的结构存储如红黑树
};