0voice-2.1.2-事件驱动reactor的原理与实现
什么是 reactor
它只是一种设计模式,用来组织和管理事件驱动的网络程序。
底层还是用操作系统提供的 IO
多路复用技术(select
/ poll
/ epoll
)。
Reactor
做的事就是:在这些系统调用之上,提供一个统一的“事件分发机制”。
-
由
io
管理转变为了对io
事件管理 -
回调函数就是事先绑定好每个
fd
对应的处理函数 -
不同的
io
事件,对应不同回调register
io
-->event
-->callback
listenfd
-->EPOLLIN
-->accept_cb
clientfd
-->EPOLLIN
-->recv_cb
clientfd
-->EPOLLOUT
-->send_cb
conn 结构体的作用
struct conn conn_list[CONNECTION_SIZE] = {0};
struct conn {
int fd;
char rbuffer[BUFFER_LENTH];
int rlength;
char wbuffer[BUFFER_LENTH];
int wlength;
RCALLBACK send_callback;
union { //二选一,或者的关系
RCALLBACK recv_callback;
RCALLBACK accept_callback;
} r_action;
};
-
直接分析最核心的问题,
epoll
和reactor
实现差异在哪里? -
看主循环
//epllo if (connfd == sockfd) { int clientfd = accept(sockfd , (struct sockaddr*)&clientaddr , &len); printf("accept finished: %d\n",clientfd); ev.events = EPOLLIN; ev.data.fd = clientfd; epoll_ctl(epfd , EPOLL_CTL_ADD , clientfd , &ev); } else if (events[i].events & EPOLLIN) { char buffer[1024] = {0}; // 临时的读写缓冲区 int count = recv(connfd, buffer , 1024 , 0); printf("RECV %s\n", buffer); if (count == 0) { // 客户端断开连接 printf("client disconnect: %d\n",connfd); close(i); // 这里应该是 close(connfd); 你的代码这里有个小bug epoll_ctl(epfd , EPOLL_CTL_DEL , connfd , &ev); // 从epoll中删除 continue; } count = send(connfd , buffer , count , 0); // 直接发送数据 printf("SEND: %d\n", count); }
//reactor if (events[i].events & EPOLLIN) { conn_list[connfd].r_action.recv_callback(connfd); } if (events[i].events & EPOLLOUT) { conn_list[connfd].send_callback(connfd); }
-
直观来看,几点差异:
-
epllo
要先去判断是不是sockfd
, 再判断是否是可读事件。- 这是因为
sockfd
和clientfd
后面执行的操作不一样,而这个操作就是reactor
的回调函数,它本身就在事件创立的时候就已经确定了,所以就不用多判断了,直接看事件是可读还是可写就行了。
- 这是因为
-
epllo
则需要自己每次 临时 创建读写缓存区 ,而reactor
的每个事件都有独立的读写
缓冲区-
注意
reactor
的缓存区信息都是存下来记录的(数组的内容,长度),可以记录肯定比临时存储的强很多,不然存在以下问题。 -
无法处理半包/粘包: 如果一个客户端发送的数据量大于 1024 字节,或者分多次发送一个逻辑上的完整包,这个临时缓冲区无法完整保存数据,也无法累积接收。它只能处理一次 recv 接收到的数据。
-
无法处理发送缓冲区满: 如果
send(connfd , buffer , count , 0)
时,内核的发送缓冲区已满,send
函数可能会阻塞(如果套接字是阻塞模式)或者只发送一部分数据(如果是非阻塞模式),并且返回已发送的字节数。你的代码没有机制来记录剩余未发送的数据,导致数据丢失或逻辑错误。 -
状态信息缺失: 每个连接除了读写数据,还可能有其他状态,例如认证状态、协议解析进度、超时时间等。这份代码没有一个地方来集中存储这些状态,使得管理非常困难。
-
-
-