Linux高并发学习---epoll的reactor实现

10 篇文章 0 订阅
本文详细解析了Reactor模式在I/O处理中的应用,通过饭店运营比喻阐述其工作原理,涉及非阻塞IO、IO复用和epoll实现。介绍了Reactor结构、初始化过程、事件循环和回调函数,展示了如何在实际代码中运用这一设计模式。
摘要由CSDN通过智能技术生成

Reactor模式

目前主流的网络通信库,如C/C++的libevent、Java的Netty等,使用的都是Reactor模式。Reactor模式是一种事件处理的设计模式,在I/O请求到达后,服务处理程序使用I/O复用技术同步地讲这些请求派发给相关的请求处理程序。
如何具体的说明Reactor模式呢?我们以饭店运营模式为例:
饭店作为服务器,顾客则是I/O请求,很显然,几乎绝大部分饭店都不会给每位顾客单独分配一个服务员,这样开销太大了。而采用了Reactor模式的饭店是这样做的:一般由某个服务员接待进来用餐的顾客,里面有服务员接待几桌的顾客,当顾客有需求时(如点菜、结账)告诉服务员,服务员会将这些需求转告给对应的工作人员进行处理(如点菜需要转告给厨房、结账转告给收银台),这样的话,即使顾客爆满(I/O事件频繁),饭店依然能够有条不紊的运作。
在这里插入图片描述

具体使用“非阻塞IO+IO复用”,基本结构是一个事件循环(event loop),以事件驱动(event-driven)和事件回调的方式实现业务逻辑,即Reactor模式。
伪代码如下:

while(!done)
{
     int nready = epoll_wait(epollfd, epollEvents, EPOLL_EVENT_SIZE, timeout);
     if(nready < 0){
          // 处理错误
     }
     else{
          // 处理到期的timer,回调用户的timer handler
          if(nready > 0){
               // 处理IO事件,回调用户的IO handler
          }
     }
}

Epoll的Reactor实现

在reactor模式的设计中,我们首先得设计一个功能较完善的结构,这个结构至少得包含基本的socketfd、读写事件的回调函数等等,这样在后续epoll_wait返回events事件时,能够根据事件类型直接调用所绑定的回调函数。本例程设计的结构体变量如下:

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

typedef struct _event_item 
{
     int fd;
     int events;
     void *args;
     callBack rhandle;     // 读事件回调
     callBack whandle;     // 写事件回调
     
     unsigned char sendBuf[BUFFER_SIZE];
     int sendLen;
     unsigned char recvBuf[BUFFER_SIZE];
     int recvLen;
} event_item;

typedef struct _reactor
{
     int epollfd;
     event_item *events;
}reactor;

其中event_item是一个基本结点,对应着一个socketfd,以及相关的读写回调和发送接收缓存;reactor结点则是用于管理epoll_create()创建的epollfd和所有的event_item结点,event_item *events在本例程中是一个大小为512的数组,也就是说最多能支持512个TCP连接,后续支持更多连接的话,读者可以使用链表来组织结构。

和原始服务器模型一样,首先需要创建socket到listen状态,我们使用一个init_socket()的函数,返回监听状态的socket给后续使用:

sockfd = init_socket(SERVER_PORT);

int init_socket(short port){
     int reuseAddr = 1;
     int fd = socket(AF_INET, SOCK_STREAM, 0);
     if(fd < 0)
     {
          printf("errno: %s, socket() failed!\n", strerror(errno));
          return -1;
     }
     if(fcntl(fd, F_SETFL, O_NONBLOCK) < 0){
          printf("errno: %s, fcntl() failed!\n", strerror(errno));
          return -1;
     }

     struct sockaddr_in serverAddr;
     memset(&serverAddr, 0, sizeof(serverAddr));
     serverAddr.sin_family = AF_INET;
     serverAddr.sin_port = htons(port);
     serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&reuseAddr, sizeof(reuseAddr));
     if(bind(fd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0){
          printf("errno: %s, bind() failed!\n", strerror(errno));
          return -1;
     }

     if(listen(fd, MAX_LISTEN_BAGS) < 0){
          printf("errno: %s, listen() failed!\n", strerror(errno));
          return -1;
     }

     return fd; 
}

在得到监听socket之后,我们接着就可以获取新的socket连接了,在此之前,我们先初始化上述设定的结构变量:

int init_reactor(reactor *r){
     if(r == NULL)  return -1;
     
     memset(r, 0, sizeof(reactor));
     r->epollfd = epoll_create(1);
     if(r->epollfd <= 0){
          printf("errno: %s, epoll_create() failed!\n", strerror(errno));
          return -1;
     }

     r->events = (event_item*)malloc((MAX_EPOLL_SIZE) * sizeof(event_item));
     if(r->events == NULL){
          printf("errno: %s, (event_item*)malloc() failed!\n", strerror(errno));
          return -1;
     }

     return 0;
}

reactor *instance = NULL;
reactor *getInstance(){
     if(instance == NULL){
          instance = (reactor*)malloc(sizeof(reactor));
          if(instance == NULL) 
               return NULL;
          memset(instance, 0, sizeof(reactor));
          if(init_reactor(instance) < 0){
               free(instance);
               return NULL;
          }
     }

     return instance;
}

上述代码中,我们对reactor结构进行初始化,包括创建epollfd,分配MAX_EPOLL_SIZE大小的数组用来存放tcp连接;为了方便后续代码使用reactor结构,我们定义了一个全局的reactor*变量instance,使用单例模式初始化该instance变量(不是很严谨的单例)。

由于后续accept获取的连接也是属于读事件的一种,我们设计了一个set_reactor_events()函数来统一设置accept、read和write三种事件:

int set_reactor_events(int fd, int event, void *args){
     struct epoll_event ev;
     reactor *r = getInstance();
     if(r == NULL){
          printf("set_reactor_events(): getInstance() failed!\n");
          return -1;
     }

     if(event == ACCEPT){
          r->events[fd].fd = fd;
          r->events[fd].args = args;
          r->events[fd].rhandle = acceptCB;
          ev.events = EPOLLIN;
     }else if(event == READ){
          r->events[fd].fd = fd;
          r->events[fd].args = args;
          r->events[fd].rhandle = readCB;
          ev.events = EPOLLIN;
          // ev.events |= EPOLLET;
     }else if(event == WRITE){
          r->events[fd].fd = fd;
          r->events[fd].args = args;
          r->events[fd].whandle = writeCB;
          ev.events = EPOLLOUT;
     }

     ev.data.ptr = &r->events[fd];
     if(r->events[fd].events == INIT){
          epoll_ctl(r->epollfd, EPOLL_CTL_ADD, fd, &ev);
          r->events[fd].events = event;
     }else if(r->events[fd].events != event){
          epoll_ctl(r->epollfd, EPOLL_CTL_MOD, fd, &ev);
          r->events[fd].events = event;
     }

     return 0;
}

在该函数中,只要传入fd和需要设定的event事件类型,再封装epoll_ctl()函数完成事件的注册。
那么如何使用呢?我们使用reactor_loop()函数进行事件循环,根据不同类型的事件,来分配给不同的回调函数处理,代码如下:

int reactor_loop(){
     struct epoll_event events[MAX_EPOLL_SIZE] = {0};
     reactor *r = getInstance();
     if(r == NULL){
          printf("reactor_loop(): getInstance() failed!\n");
          return -1;
     }

     while(1){
          int nready = epoll_wait(r->epollfd, events, MAX_EPOLL_SIZE, -1);
          if(nready == -1)    
               continue;

          for(int i = 0; i < nready; ++i){
               event_item *item = (event_item *)events[i].data.ptr;
               if(events[i].events & EPOLLIN)
                    (*item->rhandle)(item->fd, 0, NULL);
               if(events[i].events & EPOLLOUT)
                    (*item->whandle)(item->fd, 0, NULL);
          }         
     }

     return 0;
}

读到这里,相信大家已经明白了reactor是怎么一回事了,接下来再给出accept、read和write事件的回调函数:

int acceptCB(int fd, int events, void* args){
     struct sockaddr_in clientAdr;
     socklen_t len = sizeof(clientAdr);
     int connfd = accept(fd, (struct sockaddr*)&clientAdr, &len);
     if(fcntl(connfd, F_SETFL, O_NONBLOCK) < 0){
          printf("errno: %s, fcntl() failed!\n", strerror(errno));
          return -1;
     }
     printf("new connection: %d\n", connfd);
     set_reactor_events(connfd, READ, args);
     return 0;
}

int readCB(int fd, int events, void* args){
     reactor *r = getInstance();
     if(r == NULL){
          printf("reactor_loop(): getInstance() failed!\n");
          return -1;
     }

     unsigned char *rbuf = r->events[fd].recvBuf;

#if 0  // ET
     int cnt = 0, num = 0;
     while(1){
          num = recv(fd, rbuf+cnt, BUFFER_SIZE-cnt, 0);
          if(num == -1){
               if(errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
          }
          else if(num > 0)
               cnt += num;
          else
               break;
     }
     if (cnt == BUFFER_SIZE && num != -1) {
		set_reactor_events(fd, READ, NULL);
	} else if (num == 0) {
          printf("对端连接断开!clientfd = %d\n", fd);
		del_reactor_events(fd);
	} else {
          unsigned char *sbuf = r->events[fd].sendBuf;
          memcpy(sbuf, rbuf, cnt);
          r->events[fd].sendLen = cnt;
          printf("recv from fd = %d: %s\n", fd, sbuf);
		set_reactor_events(fd, WRITE, NULL);
	}

#else
     int n = recv(fd, rbuf, BUFFER_SIZE, 0);
     if(n == 0){
          printf("errno: %s, clientfd %d closed!\n", strerror(errno), fd);
          del_reactor_events(fd);
          return -1;
     }else if(n < 0){
          if(errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN){
               printf("errno: %s, error!\n", strerror(errno));
               return -1;
          }
     }
     else{
          unsigned char *sbuf = r->events[fd].sendBuf;
          memcpy(sbuf, rbuf, n);
          r->events[fd].sendLen = n;
          printf("recv from fd = %d: %s\n", fd, sbuf);
          set_reactor_events(fd, WRITE, args);
     }

#endif
     return 0;
}

int writeCB(int fd, int events, void* args){
     reactor *r = getInstance();
     if(r == NULL){
          printf("reactor_loop(): getInstance() failed!\n");
          return -1;
     }

     unsigned char *sbuf = r->events[fd].sendBuf;
     int len = r->events[fd].sendLen;

     int ret = send(fd, sbuf, len, 0);
     if(ret < len){
          set_reactor_events(fd, WRITE, args);
     }else{
          set_reactor_events(fd, READ, args);
     }
     return 0;
}

后续

文章到这里就告一段落啦,下一篇会基于reactor实现单机百万并发的代码例程,此外代码有需要的同学可以在评论区留言或者私信我哈,我也上传了对应的代码,链接在这:添加链接描述
有问题的地方请告知我哈~
互相学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿杰的小鱼塘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值