文章目录
一、reactor模型
(1) 基本原理
Reactor 释义“反应堆”,是一种事件驱动机制,和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor 逆置了事件处理流程,应用程序需要提供相应的接口并注册到 Reactor 上,如果相应的时间发生,Reactor 将主动调用应用程注册的接口,这些接口又称为“回调函数”。
Reactor 模式是处理并发 I/O 比较常见的一种模式,用于同步 I/O,中心思想是将所有要处理的 I/O 事件注册到一个中心 I/O 多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有 I/O 事件到来或是准备就绪(文件描述符或socket 可读、写),多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中
(2) 三个重要组件
Reactor 模型有三个重要的组件:
- 多路复用器:由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。
- 事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中。
- 事件处理器:负责处理特定事件的处理函数。
(3) 具体流程
- 注册读就绪事件和相应的事件处理器;
- 事件分离器等待事件;
- 事件到来,激活分离器,分离器调用事件对应的处理器;
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制
权。
(4) 优点
- 响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;
- 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
- 可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
- 可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
二、具体实现
(1) 结构定义
分别定义了两个结构体,事件结构体 ntyevent , reactor 结构体 ntyreactor
#define BUFFER_LENGTH 4096 //缓冲区长度
#define MAX_EPOLL_EVENT 1024 // epoll中的事物数量
#define SERVER_PORT 8888
/*
ntyevent
* fd: 该事件的fd
* events : 对应的事件类型,比如输入输出
* arg : 需要传给回调函数的参数,一般传的是该事件所属reactor的指针void* arg
* int (*callback) : 对应的回调函数指针
* status : 事件是否创建的状态
* buffer : 事件消息
* length : 消息长度
* last_active : 最后的运行时间
* */
struct ntyevent
{
int fd; //事务的fd
int events; // epoll events类型
void *arg; //需要传给回调函数的参数,一般传的是reactor的指针
int (*callback)(int fd, int events, void *arg); //对应的回调函数
int status; // 0:新建 1:已存在
char buffer[BUFFER_LENGTH];
int length;
long last_active;
};
/*
ntyreactor(通过reactor来管理所有的事件)
* 该reactor的fd
* 该reactor内部的ntyevent数组==*events==(运行时通过epoll_wait取出所有存在内核的事件)
*/
struct ntyreactor
{
int epfd; // reatctor的fd
struct ntyevent *events; // reactor管理的基础单元
};
(2) 事件的设置、添加、删除
int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
int accpet_cb(int fd, int events, void *arg);
void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg);
int nty_event_add(int epfd, int events, struct ntyevent *ev);
int nty_event_del(int epfd, struct ntyevent *ev);
//设置ntyevent的参数
void nty_event_set(struct ntyevent *ev, int fd, NCALLBACK callback, void *arg)
{
ev->fd = fd;
ev->callback = callback;
ev->events = 0;
ev->arg = arg;
ev->last_active = time(NULL);
return;
}
//增加或修改
int nty_event_add(int epfd, int events, struct ntyevent *ev)
{
//使用的是linux内核里的epoll
struct epoll_event ep_ev = {0, {0}};
//这一步非常关键传到联合体data的ptr里的是ntyevent指针
ep_ev.data.ptr = ev;
ep_ev.events = ev->events = events;
//判断该事件是否已经添加过
int op;
if (ev->status == 1)
{
op = EPOLL_CTL_MOD;
}
else
{
op = EPOLL_CTL_ADD;
ev->status = 1;
}
// epoll_ctl进行对应操作
if (epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0)
{
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
return -1;
}
return 0;
}
int nty_event_del(int epfd, struct ntyevent *ev)
{
struct epoll_event ep_dv = {0, {0}};
if (ev->status != 1)
{
return -1;
}
ep_dv.data.ptr = ev;
ev->status = 0;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_dv);
return 0;
}
(3) 事件建立连接、收消息、发消息的回调函数
int recv_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = reactor->events+fd;
int len = recv(fd, ev->buffer, BUFFER_LENGTH, 0);
nty_event_del(reactor->epfd, ev);
if (len > 0) {
ev->length = len;
ev->buffer[len] = '\0';
printf("C[%d]:%s\n", fd, ev->buffer);
nty_event_set(ev, fd, send_cb, reactor);
nty_event_add(reactor->epfd, EPOLLOUT, ev);
} else if (len == 0) {
close(ev->fd);
printf("[fd=%d] pos[%ld], closed\n", fd, ev-reactor->events);
} else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return len;
}
int send_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
struct ntyevent *ev = reactor->events+fd;
int len = send(fd, ev->buffer, ev->length, 0);
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buffer);
nty_event_del(reactor->epfd, ev);
nty_event_set(ev, fd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, ev);
} else {
close(ev->fd);
nty_event_del(reactor->epfd, ev);
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return len;
}
int accept_cb(int fd, int events, void *arg) {
struct ntyreactor *reactor = (struct ntyreactor*)arg;
if (reactor == NULL) return -1;
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int clientfd;
if ((clientfd = accept(fd, (struct sockaddr*)&client_addr, &len)) == -1) {
if (errno != EAGAIN && errno != EINTR) {
}
printf("accept: %s\n", strerror(errno));
return -1;
}
int i = 0;
do {
for (i = 3;i < MAX_EPOLL_EVENTS;i ++) {
if (reactor->events[i].status == 0) {
break;
}
}
if (i == MAX_EPOLL_EVENTS) {
printf("%s: max connect limit[%d]\n", __func__, MAX_EPOLL_EVENTS);
break;
}
int flag = 0;
if ((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0) {
printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
break;
}
nty_event_set(&reactor->events[clientfd], clientfd, recv_cb, reactor);
nty_event_add(reactor->epfd, EPOLLIN, &reactor->events[clientfd]);
} while (0);
printf("new connect [%s:%d][time:%ld], pos[%d]\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), reactor->events[i].last_active, i);
return 0;
}
(4) socket初始化
int init_sock(short port)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(fd, F_SETFL, O_NONBLOCK);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port);
bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (listen(fd, 20) < 0)
{
printf("listen failed : %s\n", strerror(errno));
}
return fd;
}
(5) reactor 初始化
int ntyreactor_init(struct ntyreactor *reactor)
{
if (reactor == NULL)
{
return -1;
}
memset(reactor, 0, sizeof(struct ntyreactor));
//创建大房子
reactor->epfd = epoll_create(1);
if (reactor->epfd <= 0)
{
printf("create epfd in %s err %s\n", __func__, strerror(errno));
return -2;
}
//申请events的空间
reactor->events = (struct ntyevent *)malloc((MAX_EPOLL_EVENT) * sizeof(struct ntyevent));
if (reactor->events == NULL)
{
printf("create epfd in %s err %s\n", __func__, strerror(errno));
close(reactor->epfd);
return -3;
}
}
int ntyreactor_destroy(struct ntyreactor *reactor)
{
close(reactor->epfd);
//释放调之前malloc的所有资源
free(reactor->events);
}
(6) 添加listen
//添加最早的回调函数
int ntyreactor_addlistener(struct ntyreactor *reactor, int sockfd, NCALLBACK *acceptor)
{
if (reactor == NULL)
{
return -1;
}
if (reactor->events == NULL)
{
return -1;
}
nty_event_set(&reactor->events[sockfd], sockfd, acceptor, reactor);
nty_event_add(reactor->epfd, EPOLLIN, &reactor->events[sockfd]);
}
(7) reactor循环
int ntyreactor_run(struct ntyreactor *reactor)
{
if (reactor == NULL)
return -1;
if (reactor->epfd < 0)
return -1;
if (reactor->events == NULL)
return -1;
struct epoll_event events[MAX_EPOLL_EVENT + 1];
int checkpos = 0, i;
while (1)
{
// 100个为单位的进行检查是否超时
long now = time(NULL);
for (int i = 0; i < 100; i++, checkpos++)
{
//满了就清空
if (checkpos == MAX_EPOLL_EVENT)
{
checkpos = 0;
}
//未创建跳过
if (reactor->events[checkpos].status != 1)
{
continue;
}
//检测连接时间太长了就断掉
long duration = now - reactor->events[checkpos].last_active;
if (duration >= 60)
{
close(reactor->events[checkpos].fd);
printf("[fd=%d] timeout\n", reactor->events[checkpos].fd);
nty_event_del(reactor->epfd, &reactor->events[checkpos]);
}
}
//从内核中把事务提取出来放到events里面
int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENT, 1000);
if (nready < 0)
{
printf("epoll_wait error, exit\n");
continue;
}
//最最最关键的地方实现回调函数
for (i = 0; i < nready; i++)
{
//这里data是一个union传过来的就只有之前放进去的指向ntyevent的指针
struct ntyevent *ev = (struct ntyevent *)events[i].data.ptr;
if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))
{
ev->callback(ev->fd, events[i].events, ev->arg);
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
{
ev->callback(ev->fd, events[i].events, ev->arg);
}
}
}
}
(8) main函数
int main(int argc, char *argv[]) {
unsigned short port = SERVER_PORT;
if (argc == 2) {
port = atoi(argv[1]);
}
int sockfd = init_sock(port);
struct ntyreactor *reactor = (struct ntyreactor*)malloc(sizeof(struct ntyreactor));
ntyreactor_init(reactor);
ntyreactor_addlistener(reactor, sockfd, accept_cb);
ntyreactor_run(reactor);
ntyreactor_destory(reactor);
close(sockfd);
return 0;
}
三、编译与调试
- Ubuntu 14.04—server
gcc -o reactor reactor.c
- 运行服务器并使用网络调试助手调试