reactor模型解析与实现方法
高并发网络编程的网络模型
在高并发编程领域中,对于接入数据的处理,通常可以分为两个阶段:
等待消息准备好
消息处理
当使用默认的阻塞套接字时(例如前面提到的1个线程捆绑一个cpu然后处理1个连接),往往把这两个阶段合而为一,阻塞等待消息拷贝完成,然后进行处理。这样操作套接字的代码所在的线程就得睡眠来等待数据准备好,这就导致了高并发场景下线程会频繁的睡眠、唤醒,从而影响了CPU的使用效率。
高并发编程方法当然就是把两个阶段分开处理。即在实现上,等待消息准备好的代码段,与处理消息的代码段是分离的。当然,这也要求套接字必须是非阻塞的,否则,处理消息的代码段很容易出现条件不满足时,线程又进入了睡眠等待的情况。那么问题来了,等待消息准备好这部分代码怎么实现?它毕竟还是等待,这意味着线程还是要睡眠的!
解决办法就是,线程主动查询,或者让1个线程为所有连接而等待!这就是IO多路复用了。多路复用就是处理等待消息准备好这件事的,它可以同时处理多个连接!
当然,它也可能"等待”,所以它也会导致线程睡眠,然而这不要紧,因为它一对多、可以监控所有连接。这样,当找们的线程被哄醒时,就一定是有一些连接准备好,需要被我们的代码处理了。
同时,作为一个高性能服务器程序通常需要考虑处理三类事件:
I/O事件
定时事件
信号。
目前有两种高效的事件处理模型:
Linux平台下的同步IO模型reactor
Windows平台下的 proactor(IOCP+异步IO)
目前题主的主要平台还是Linux,故本文不展开讨论Proactor。
Reactor
reactor简介及构成
reactor,直译过来就是反应器的意思,实际上他更确切的翻译时反应堆,主要是他会被大量的响应事件对应的文件描述符进行反应,事件的响应会触发反应堆注册的相关操作。他与主动调用函数不同,比较正向的思维方式是我们检查到某些事件,针对事件的类型进行对应处理函数的调用。
reactor则逆转了思维方式,将处理函数与事件关联并注册到reactor上,当某个事件发生了,就会驱动reactor上相关的处理函数,这个函数也就是我们常说的回调函数。
简而言之,reactor由以下几个部分构成:
多路复用器:一般是select,poll,epoll等多路复用系统调用。由于epoll比poll+aio_read表现还好,所以一般是epoll。
事件分发器:将对应事件分发到对应的处理函数。
事件处理器:常用特定事件处理函数。
reactor的优点
reactor有以下几个优点:
响应快,不必为单个同步事件所阻塞,只是reactor 本身是同步的;
编程相对简单,单线程处理可以最大程度的避免复杂的多线程及同步问题及切换开销;
可扩展性,可以方便的通过增加reactor 实例个数来充分利用CPU 资源;
可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;
reactor的实现部分解析
reactor相关定义
typedef int misc_event_callback(int fd, int events, void *arg);
typedef struct {
int fd; ///< event fd
int events; ///< event status
void *arg; ///< extensible mem
/*事件对应触发的处理函数,不同事件执行后会修改对应的触发事件与函数,大致流程如下
1. accept事件触发后,将处理函数改为可读事件处理
2. 可读事件触发后,进行读到数据的处理,之后将处理函数改为可写事件处理
3. 可写事件触发结束后,将处理函数改为读事件处理,之后读写反复即可。
*/
misc_event_callback* call_back_func; ///< function ptr ->处理器
int status;
/*读写使用的内存实际上应该用内存池维护,并用指针指向独立内存,这里属于偷懒的声明方法*/
uint8_t rbuffer[ITEM_BUFFER_LENGTH]; ///< get value
uint32_t rlength;
uint8_t wbuffer[ITEM_BUFFER_LENGTH]; ///< post value
uint32_t wlength;
char sec_accept[ACCEPT_KEY_LENGTH];
int wsstatus;
}reactor_item_t;
///<event item bucket list
typedef struct eventblock {
struct eventblock *next; ///< next node
reactor_item_t *item_bucket; ///< item
}event_list_node_t;
///< reactor
typedef struct{
int epoll_fd; ///< ->复用器
int block_count;
event_list_node_t *list_header; ///< 指向事件管理块,一个块管理固定个数个事件,事件超过单个管理块管理的范围后,扩充管理块即可
}reactor_t;
reactor部分模块介绍
reactor初始化(多路复用器初始化)
static force_inline reactor_item_t* find_itme_in_reactor(reactor_t* reactor , int sockfd)
{
int buctet_index = sockfd / MAX_EPOLL_EVENTS; ///< 计算所在的fd块下标
while(unlikely(buctet_index >= reactor->block_count))
misc_reactor_alloc_block(reactor);
int i = 0 ;
event_list_node_t *tmp_node = reactor->list_header;
while(i++ != buctet_index && tmp_node != NULL)
tmp_node = tmp_node->next;
return &(tmp_node->item_bucket[sockfd % MAX_EPOLL_EVENTS]); ///< 从对应的块中找到目标item并返回
}
static int misc_reactor_add_listener(reactor_t* reactor , int sockfd , misc_event_callback* event_proc)
{
if(unlikely(reactor == NULL))
{
zlog_error(g_zlog_handle , "reactor is invalid");
return -1;
}
if(unlikely(reactor->list_header == NULL))
{
zlog_error(g_zlog_handle , "reactor's bucket node is invalid");
return -1;
}
reactor_item_t *item_worker = find_itme_in_reactor(reactor , sockfd);
if(unlikely(item_worker == NULL))
{
zlog_error(g_zlog_handle , "fisst itme node find failed");
return -1;
}
misc_item_init(item_worker , sockfd, event_proc , reactor); ///< item 初始化
misc_item_add(reactor->epoll_fd , EPOLLIN , item_worker); ///< item 添加到epoll
return 0;
}
static int misc_comm_init()
{
if(misc_zlog_init())
return -1;
g_reactor =(typeof(g_reactor))malloc(sizeof(reactor_t));
assert(g_reactor);
if(misc_reactor_init())
return -1;
const uint8_t add_listrner_num = g_muti_mode == 1 ? MISC_HIGH_CONN_NUM : 0; ///< 根据是否开启海量接入模式的判断要增加监听套接字的数量
int i = 0 ,tmp_sock_fd;
for( ; i <= add_listrner_num ; i++)
{
g_sockfds[i] = misc_init_sock(g_init_port + i); ///< 根据要服务的端口号初始化套接字
if(likely(g_sockfds[i] > 0))
misc_reactor_add_listener(g_reactor , g_sockfds[i] , accept_cb); ///< 初始化成功则加入响应池
}
return 0;
}
事件注册(处理器的注册)
static force_inline void misc_item_init(reactor_item_t *item , int sockfd , misc_event_callback* event_proc, reactor_t* reactor)
{
item->fd = sockfd;
item->call_back_func = event_proc;
item->events = 0;
item->arg = reactor;
return ;
}
处理的迭代,事件轮转调用(分发器&触发器)
/*send事件触发的回调函数原型*/
static int misc_item_send_cb(int fd, int events, void *arg)
{
reactor_t *reactor = (reactor_t*)arg;
reactor_item_t *ev = find_itme_in_reactor(reactor, fd);
if (ev == NULL) return -1;
ws_response(ev);
int len = send(fd, ev->wbuffer, ev->wlength, 0);
if (len > 0) {
zlog_info(g_zlog_handle ,"send[fd=%d], [%d]%s\n", fd, len, ev->wbuffer);
epoll_item_del(reactor->epoll_fd, ev);
misc_item_init(ev, fd, misc_item_recv_cb, reactor);
misc_item_add(reactor->epoll_fd, EPOLLOUT, ev);
} else {
epoll_item_del(reactor->epoll_fd, ev);
close(ev->fd);
zlog_info(g_zlog_handle ,"send[fd=%d] error %s\n", fd, strerror(errno));
}
return len;
}
/*recv事件触发的回调函数原型*/
static int misc_item_recv_cb(int fd, int events, void *arg)
{
reactor_t *reactor = (reactor_t*)arg;
reactor_item_t *ev = find_itme_in_reactor(reactor, fd);
if (unlikely(ev == NULL)) return -1;
int len = recv(fd, ev->rbuffer, ITEM_BUFFER_LENGTH, 0);
zlog_info(g_zlog_handle ,"recv info :%s\n",ev->rbuffer);
epoll_item_del(reactor->epoll_fd, ev); ///< 删除该事件
if (len > 0)
{
ev->rlength = len;
ev->rbuffer[len] = '\0';
ws_request(ev); ///< 处理web_socket请求内容
misc_item_init(ev, fd, misc_item_send_cb, reactor);
misc_item_add(reactor->epoll_fd, EPOLLOUT, ev);
}
else if (len == 0)
{
epoll_item_del(reactor->epoll_fd, ev);
zlog_info(g_zlog_handle, "recv_cb --> disconnect\n");
close(ev->fd);
}
else
{
if (errno == EAGAIN && errno == EWOULDBLOCK)
{ //
}
else if (errno == ECONNRESET)
{
epoll_item_del(reactor->epoll_fd, ev);
close(ev->fd);
}
zlog_info(g_zlog_handle ,"recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return len;
}
/*accept事件触发的回调函数原型*/
static int accept_cb(int fd, int events, void *arg)
{
reactor_t *reactor = (reactor_t*)arg;
if (unlikely(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)
{
}
zlog_error(g_zlog_handle ,"accept: %s", strerror(errno));
return -1;
}
int flag = 0;
if ((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0)
{
zlog_error(g_zlog_handle ,"%s: fcntl nonblocking failed, %d", __func__, MAX_EPOLL_EVENTS);
return -1;
}
reactor_item_t *work_item = find_itme_in_reactor(reactor, clientfd);
if (work_item == NULL) return -1;
misc_item_init(work_item, clientfd, misc_item_recv_cb, reactor);
misc_item_add(reactor->epoll_fd, EPOLLIN, work_item);
zlog_info(g_zlog_handle ,"new connect [%s:%d], pos[%d]",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd);
return 0;
}
测试结果
经过特制的海量客户端工具测试,reactor具有服务百万级(c1000K)并发的能力,实际上该能力得益于两点:
- epoll的多路复用支持
- 处理函数足够的健壮与高效
最后:
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下的文章,对c/c++linux课程感兴趣的读者,可以去零声官网查看详细的服务,也欢迎一起蹭免费公开课,共同进步鸭~