一、reactor 模型
什么是reactor?
reactor 是指的多路 I/O 复用中select、poll 、epoll 网络模型下的一种高性能的处理机制。
reactor 释义“反应堆”,是一种事件驱动机制。
普通函数的调用机制:
程序调用某函数,函数执行,程序等待,函数将结果和控制权返回给程序,程序继续处理。
reactor的调用机制:
应用程序不是主动的调用某个 API 完成处理,相反,reactor 逆置了事件处理流程,应用程序需要提供相应的接口并注册到 reactor 上,
如果相应的时间发生,reactor 将主动调用应用程序注册的接口,这些接口又称为“回调函数”。
二、reactor 应用场景
reactor 模式是处理并发 I/O 比较常见的一种模式,用于同步 I/O,中心思想是将所有要处理的 I/O 事件注册到一个中心 I/O 多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有 I/O 事件到来或是准备就绪(文件描述符或 socket 可读、写),多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中。
reactor 模型有三个重要的组件:
多路复用器:由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。
事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中。
事件处理器:负责处理特定事件的处理函数。
具体流程如下:
- 注册读就绪事件和相应的事件处理器;
- 事件分离器等待事件;
- 事件到来,激活分离器,分离器调用事件对应的处理器;
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
三、具体代码实现
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>
#define SERVER_PORT 6668
#define MAX_EPOLL_EVENTS 1024
#define BUFFER_LENGTH 4096
typedef int (*NCALLBACK)(int, int , void *);
typedef struct ntyEvent{
int fd;
int events;
void *arg;
int (*callback)(int fd, int events, void *arg);
int status; //status为0,说明该节点不在红黑树上
int length;
char buffer[BUFFER_LENGTH];
}ntyEvent_t;
typedef struct ntyReactor{
int epfd;
struct ntyEvent *events;
}ntyReactor_t;
int recv_cb_hdl(int fd, int events, void *arg);
int send_cb_hdl(int fd, int events, void *arg);
int accept_cb_hdl(int listen_fd, int events, void *arg);
int socket_init(int port){
int ret = 0;
int reuse;
struct sockaddr_in serv;
socklen_t address_len = sizeof(struct sockaddr_in);
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd < 0){
printf("socket():%s\n", strerror(errno));
return -1;
}
ret = fcntl(listen_fd, F_SETFL, O_NONBLOCK);
if(ret < 0){
printf("fcntl() error: %s\n", strerror(errno));
return -1;
}
reuse = 1;
ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
if(ret < 0){
printf("setsockopt() error: %s\n", strerror(errno));
return -1;
}
memset(&serv, 0, sizeof(struct sockaddr_in));
serv.sin_family = AF_INET;
serv.sin_port = htons(port);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listen_fd, (struct sockaddr*)&serv, address_len);
if(ret < 0){
printf("bind() error: %s\n", strerror(errno));
return -1;
}
ret = listen(listen_fd, 128);
if(ret < 0){
printf("listen() error: %s\n", strerror(errno));
return -1;
}
return listen_fd;
}
int ntyReactor_init(ntyReactor_t *reactor){
if(reactor == NULL ) return -1;
reactor->epfd = epoll_create(1);
if(reactor->epfd < 0){
printf("ntyReactor_init() error:%s\n", strerror(errno));
return -1;
}
reactor->events = (ntyEvent_t *)malloc(sizeof(ntyEvent_t) * MAX_EPOLL_EVENTS);
if(NULL == reactor->events){
printf("malloc() error!\n");
close(reactor->epfd);
return -1;
}
memset(reactor->events, 0, sizeof(ntyEvent_t)*MAX_EPOLL_EVENTS);
return 0;
}
int ntyEventSet(ntyEvent_t *ev, int fd, NCALLBACK callback, void * arg){
if(ev == NULL) return -1;
ev->fd = fd;
ev->events = 0; //默认先设置成0
ev->arg = arg;
ev->callback = callback;
return 0;
}
int ntyEventAdd(int epfd, int events, ntyEvent_t *ev){
struct epoll_event ep_ev = {0, {0}};
ep_ev.data.ptr = ev;
ep_ev.events = ev->events = events;
int op;
int ret = 0;
if(ev->status == 1){
op = EPOLL_CTL_MOD;
}else{
op = EPOLL_CTL_ADD;
ev->status = 1;
}
ret = epoll_ctl(epfd, op, ev->fd, &ep_ev);
if(ret < 0){
printf("epoll_ctl():%s\n", strerror(errno));
return -1;
}
return 0;
}
int ntyReactorAddListenFd(ntyReactor_t *reactor, int listen_fd, NCALLBACK acceptor){
if(reactor == NULL) return -1;
if(reactor->events == NULL) return -1;
int ret = 0;
ret = ntyEventSet(&reactor->events[listen_fd], listen_fd, acceptor, (void *)reactor);
if(ret < 0){
printf("ntyEventSet() error!\n");
return -1;
}
ret = ntyEventAdd(reactor->epfd, EPOLLIN, &reactor->events[listen_fd]);
if(ret < 0){
printf("nty_event_add() error!\n");
return -1;
}
return 0;
}
int ntyEventDel(int epfd, ntyEvent_t *ev){
struct epoll_event ep_ev = {0, {0}};
//一个被添加到红黑树上的节点,status的值一定是1,如果status != 1,说明不在树上不用清除掉
if (ev->status != 1) {
return -1;
}
ep_ev.data.ptr = ev;
ev->status = 0; //重置状态
epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev);
return 0;
}
//小写转大写功能
void buffer_hdl(char *buffer, int length){
int i = 0;
for(i = 0; i < length; i++){
buffer[i] = toupper(buffer[i]);
}
}
int send_cb_hdl(int fd, int events, void *arg){
ntyReactor_t *reactor = (ntyReactor_t *)arg;
ntyEvent_t *ev = &reactor->events[fd];
//模拟业务处理
buffer_hdl(ev->buffer, ev->length);
int len = send(fd, ev->buffer, ev->length, 0);
if (len > 0) {
printf("sendbuff = [fd = %d]:%s\n", fd, ev->buffer);
ntyEventSet(ev, fd, recv_cb_hdl, reactor);
ntyEventAdd(reactor->epfd, EPOLLIN, ev);
}else if(len == 0){
ntyEventDel(reactor->epfd, ev);
close(ev->fd);
printf("---send_cb_hdl--client[%d] disconnected!\n", fd);
} else {
ntyEventDel(reactor->epfd, ev);
close(ev->fd);
printf("--send_cb_hdl--send[fd=%d] error %s\n", fd, strerror(errno));
}
return 0;
}
int recv_cb_hdl(int fd, int events, void *arg){
ntyReactor_t *reactor = (ntyReactor_t *)arg;
ntyEvent_t *ev = &reactor->events[fd];
int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
if(len > 0){
ev->length = len;
ev->buffer[len] = '\0';
printf("recvbuff = [fd = %d]:%s\n", fd, ev->buffer);
ntyEventSet(ev, fd, send_cb_hdl, reactor);
ntyEventAdd(reactor->epfd, EPOLLOUT, ev);
}else if(len < 0){
ntyEventDel(reactor->epfd, ev);
close(ev->fd);
printf("--recv_cb_hdl--recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}else{
ntyEventDel(reactor->epfd, ev);
close(ev->fd);
printf("--recv_cb_hdl--client[%d] disconnected!\n", fd);
}
return 0;
}
int accept_cb_hdl(int listen_fd, int events, void *arg){
ntyReactor_t *reactor = (ntyReactor_t *)arg;
if (reactor == NULL) return -1;
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int client_fd;
int i = 0;
client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
if(client_fd < 0)
{
if (errno != EAGAIN && errno != EINTR) {
printf("accept: %s\n", strerror(errno));
return -1;
}
}
do{
//标准输入(0)、 标准输出(1)、 标准错误(2)、 listen_fd(3), reactor->epfd(4)占用了前几个文件描述符
for (i = reactor->epfd + 1; i < MAX_EPOLL_EVENTS; i++) {
if (reactor->events[i].status == 0) {
break;
}
}
//用于判定监听的连接是否超过上限值,如果从上面break出来的i等于了最大的监听数,则不能再添加新的连接事件处理,后续优化可做动态扩容
if (i == MAX_EPOLL_EVENTS) {
printf("%s: max connect limit[%d]\n", __func__, MAX_EPOLL_EVENTS);
break;
}
int flag = 0;
if ((flag = fcntl(client_fd, F_SETFL, O_NONBLOCK)) < 0) {
printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
break;
}
ntyEventSet(&reactor->events[client_fd], client_fd, recv_cb_hdl, reactor);
ntyEventAdd(reactor->epfd, EPOLLIN, &reactor->events[client_fd]);
}while(0);
printf("new connect [%s:%d], clientfd[%d], cur_unused_index = [%d]\n",\
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd, i);
return 0;
}
int ntyReactorLoopRun(ntyReactor_t *reactor){
if(reactor == NULL) return -1;
if(reactor->epfd < 0) return -1;
if(reactor->events == NULL) return -1;
struct epoll_event events[MAX_EPOLL_EVENTS];
int nready = 0;
int i = 0;
while(1){
nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, -1);
if(nready < 0){
if(errno == EINTR){
//被信号打断,重新loop
continue;
}
printf("epoll_wait error(%s), exit---\n",strerror(errno));
return -1;
}
for(i = 0; i < nready; i++){
ntyEvent_t *ev = (ntyEvent_t*)events[i].data.ptr;
if((ev->events & EPOLLIN) && events[i].events & EPOLLIN){
ev->callback(ev->fd, events[i].events, ev->arg);
}
if((ev->events & EPOLLOUT) && events[i].events & EPOLLOUT){
ev->callback(ev->fd, events[i].events, ev->arg);
}
}
}
return 0;
}
int ntyReactorDestory(ntyReactor_t *reactor){
if(NULL == reactor) return -1;
close(reactor->epfd);
if(reactor->events != NULL)
free(reactor->events);
free(reactor);
return 0;
}
int main(int argc, char *argv[]){
int ret = 0;
int serv_port = SERVER_PORT;
if(argc == 2){
serv_port = atoi(argv[1]);
}
//1、初始化socket()
int listen_fd = socket_init(serv_port);
if(listen_fd < 0){
printf("init_socket() error!\n");
return -1;
}
printf("socket_init:listen_fd = [%d]\n", listen_fd);
//2、初始 reactor
ntyReactor_t *reactor = (ntyReactor_t *)malloc(sizeof(ntyReactor_t));
if(NULL == reactor){
printf("malloc() error!\n");
}
ret = ntyReactor_init(reactor);
if(ret < 0){
printf("ntyReactor_init() error!\n");
}
printf("ntyReactor_init:reactor->epfd = [%d]\n",reactor->epfd);
//3、listen_fd 事件处理回调函数注册到reactor监听
ret = ntyReactorAddListenFd(reactor, listen_fd, accept_cb_hdl);
if(ret < 0){
printf("ntyreactor_addlistener() error\n");
return -1;
}
//4、recator模型下的epoll_wait()函数等待事件发生
ret = ntyReactorLoopRun(reactor);
if(ret < 0){
printf("ntyreactor_run() error!\n");
}
//5、reactor 销毁
ret = ntyReactorDestory(reactor);
if(ret < 0){
printf("ntyReactorDestory() error!\n");
}
close(listen_fd);
return 0;
}
编译执行及其调试:
[root@localhost socket-reactor-mode]# ./test 8888
socket_init:listen_fd = [3]
ntyReactor_init:reactor->epfd = [4]
new connect [127.0.0.1:36798], clientfd[5], cur_unused_index = [5]
recvbuff = [fd = 5]:hello linux!
sendbuff = [fd = 5]:HELLO LINUX!
new connect [127.0.0.1:36800], clientfd[6], cur_unused_index = [6]
recvbuff = [fd = 6]:hello China!
sendbuff = [fd = 6]:HELLO CHINA!
new connect [127.0.0.1:36802], clientfd[7], cur_unused_index = [7]
recvbuff = [fd = 7]:abcdefghigkmon
sendbuff = [fd = 7]:ABCDEFGHIGKMON
new connect [172.16.10.223:58039], clientfd[8], cur_unused_index = [8]
recvbuff = [fd = 8]:http://www.cmsoft.cn QQ:10865600
sendbuff = [fd = 8]:HTTP://WWW.CMSOFT.CN QQ:10865600
--recv_cb_hdl--client[6] disconnected!
new connect [127.0.0.1:36804], clientfd[6], cur_unused_index = [6]
recvbuff = [fd = 6]:hello china!
sendbuff = [fd = 6]:HELLO CHINA!
四、reactor模式的优点
reactor 模式是编写高性能网络服务器的必备技术之一, 有如下优点:
响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;
编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进
程的切换开销;
可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
五、总结
reactor 模型开发效率上比起直接使用 IO 复用要高,它通常是单线程的,设计目标是希望单线程使用一颗 CPU 的全部资源,但也有附带优点,即每个事件处理中很多时候可以不考虑共享资源的互斥访问。可是缺点也是明显的,现在的硬件发展,已经不再遵循摩尔定律,CPU 的频率受制于材料的限制不再有大的提升,而改为是从核数的增加上提升能力,当程序需要使用多核资源时,reactor 模型就会悲剧, 为什么呢?如果程序业务很简单,例如只是简单的访问一些提供了并发访问的服务,就可以直接开启多个反应堆,每个反应堆对应一颗 CPU 核心,这些反应堆上跑的请求互不相关,这是完全可以利用多核的。