Reactor的原理与实现

        Reactor释义“反应堆”,是一种事件驱动机制。和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

         Reactor模式是处理并发IO比较常见的一种模式,用于同步IO,中心思想是将所有要处理的IO事件注册到一个中心IO多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有IO事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应IO事件分发到对应的处理器中。

Reactor模型三个重要的组件

  • 多路复用器:由操作系统提供,在linux上一般是select,poll,epoll等系统调用;
  • 事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中;
  • 事件处理器:负责处理特定事件的处理函数。

具体操作流程:

  1.  注册读就绪事件和相应的事件处理器;
  2.  事件分离器等待事件;
  3.  事件到来,激活分离器,分离器调用事件对应的处理器;
  4.  事件处理器完成实际的操作,处理读到的数据,注册新的事件,然后返还控制权;

Reactor的优点

  • 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
  • 编程相对简单,可以最大程度避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
  • 可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
  • 可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

        Reactor模型开发效率上比起直接使用IO复用要高,它通常是单线程的,设计目标是希望单线程使用一颗CPU的全部资源,但也有附带优点,即每个事件处理中很多时候可以不考虑共享资源的互斥访问。

代码实现

#define BUFFER_LENGTH		4096
#define MAX_EPOLL_EVENTS	1024
#define SERVER_PORT			8888

typedef int NCALLBACK(int ,int, void*);

struct ntyevent {
	int fd;
	int events;
	void *arg;
	int (*callback)(int fd, int events, void *arg);
	
	int status;
	char buffer[BUFFER_LENGTH];
	int length;
	long last_active;
};

struct ntyreactor {
	int epfd;
	struct ntyevent *events;
};

int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
//设置事件
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) {
  struct epoll_event ep_ev = {0, {0}};
  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;
  }

  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_ev = {0, {0}};

  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;
}
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:   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;
}
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;
}

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;
  }

  reactor->events =
      (struct ntyevent *)malloc((MAX_EPOLL_EVENTS) * 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_destory(struct ntyreactor *reactor) {
  close(reactor->epfd);
  free(reactor->events);
}

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]);

  return 0;
}

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_EVENTS + 1];

  int checkpos = 0, i;

  while (1) {
    long now = time(NULL);
    for (i = 0; i < 100; i++, checkpos++) {
      if (checkpos == MAX_EPOLL_EVENTS) {
        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]);
      }
    }

    int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, 1000);
    if (nready < 0) {
      printf("epoll_wait error, exit\n");
      continue;
    }

    for (i = 0; i < nready; i++) {
      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);
      }
    }
  }
}
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;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值