reactor模式

系列文章目录

推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
第三章 :reactor事件处理模式


文章目录

  • 系列文章目录
  • 前言
  • 一、reactor模式简介
  • 二、reactor工作流程
  • 三、reactor模式的代码实列
  • 四、采用reactor的并发模式
    • 简单的半同步/半反应堆模式
    • 高效的半同步/半反应堆模式
  • 总结


前言

reactor的英文翻译叫反应堆,这个反应堆是指对事件的反应,反应堆里面包含了一堆的监听事件集合,当一个事件就绪后,reactor马上对它做出反应和处理。这个反应和处理可以交给特定的函数或线程进程处理。

一、reactor模式简介

reactor模式要求主线程(IO处理单元),只负责监听文件描述符是否有时间发生,有的话就立马将该事件通知给工作线程(逻辑单元)处理。除此之外主线程不做其它任何实质性的工作。读写数据,接收新的连接,处理客户请求都在工作线程中处理

二、reactor工作流程

以epoll为例,实现reactor的流程是:

  1. 主线程网epoll内核事件表中注册socket上的就绪事件
  2. 主线程调用epoll_wait等待socket上有数据可读
  3. 当socket上有数据可读时,epoll_wait通知主线程,主线程将可读事件放入请求队列
  4. 睡眠在请求队列上的某个工作线程被唤醒,它从socket上读取数据,并处理客户请求,然后往epoll内核事件表中注册该socket上的写就绪事件
  5. 主线程调用epoll_wait等待socket可写
  6. 当socket可写时,epoll_wait通知主线程,主线程把可写事件放入请求队列
  7. 睡眠在请求队列上的某个工作线程被唤醒,它往socket上写入服务器处理客户请求的结果。

工作线程从请求队列中取出事件后,将更具事件的类型来处理它,对于可读事件执行,执行读数据和处理请求的操作;对于可写事件执行写数据操作;对于连接事件处理连接操作。所以没必要区分读工作线程和写工作线程。

三、reactor模式的代码实列

下面的这个代码实列,为了简便易读,没有创建工作线程,而是用对应的回调函数取而代之。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_LENGTH		4096
#define MAX_EPOLL_EVENTS	1024

int ReadCallback(int fd, int event, void *arg);
int WriteCallback(int fd, int event, void *arg);
int AcceptCallback(int fd, int event, void *arg);

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

enum TagFdType {
    LISTENFD = 0,
    CLIENTFD
};

struct TagEvent {
	int fd;
	int events;
	void *arg;

	NCALLBACK *ReadCallback;
	NCALLBACK *WriteCallback;
	NCALLBACK *AcceptCallback;

	int status;
	char buffer[BUFFER_LENGTH];
    enum TagFdType type;
	int length;
	long lastActive;
};

struct TagReactor {
	int epollFd;
	struct TagEvent *events;
};

int SetFdNonblock(int fd) 
{
    int flag = fcntl(fd, F_GETFD, 0);
    flag = fcntl(fd, F_SETFL, flag| O_NONBLOCK);

    return flag;
}

int ListenFdInit(unsigned int port)
{
    int ret = 0;
    int listenFd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenFd < 0) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return -1;
    }

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

    ret = bind(listenFd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 
    if ( ret == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return -2;
    }

    ret = listen(listenFd, 10);
    if (ret == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return -3;
    }

    return listenFd;
}

int ReactorInit(struct TagReactor *reactor) {

	if (reactor == NULL) {
        return -1;
    }

	memset(reactor, 0, sizeof(struct TagReactor));

	reactor->epollFd = epoll_create(1);
	if (reactor->epollFd <= 0) {
		printf("create epollFd in %s err %s\n", __func__, strerror(errno));
		return -2;
	}

    // 使用线性表分配了MAX_EPOLL_EVENTS个事件的存储空间,
	reactor->events = (struct TagEvent*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct TagEvent));
	if (reactor->events == NULL) {
		printf("create epfd in %s err %s\n", __func__, strerror(errno));
		close(reactor->epollFd);
		return -3;
	}

	return 0;
}

void EventSet(struct TagEvent *ev, int fd, NCALLBACK AcceptCb,NCALLBACK ReadCb,NCALLBACK WriteCb, enum TagFdType type ,void *arg) 
{
    ev->type = type;
	ev->fd = fd;
	ev->events = 0;
	ev->arg = arg;
	ev->lastActive = time(NULL);

	ev->AcceptCallback = AcceptCb;
    ev->ReadCallback = ReadCb;
    ev->WriteCallback = WriteCb;

	return ;
}

int EventDel(int epfd, struct TagEvent *ev) {

	struct epoll_event event = {0, {0}};

	if (ev->status != 1) {
		return -1;
	}

	event.data.ptr = ev;
	ev->status = 0;
	epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &event);

	return 0;
}

int EventAdd(int epfd, int events, struct TagEvent *ev) {

	struct epoll_event event = {0, {0}};
	event.data.ptr = ev;
	event.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, &event) < 0) {
		printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
		return -1;
	}
	return 0;
}

int ReactorAddListener(struct TagReactor *reactor, int sockfd) {

	if (reactor == NULL) {
        return -1;
    }

	if (reactor->events == NULL) {
        return -1;
    }
    enum TagFdType type = LISTENFD;
	EventSet(&reactor->events[sockfd], sockfd, AcceptCallback, ReadCallback, WriteCallback, type, reactor);
	EventAdd(reactor->epollFd, EPOLLIN, &reactor->events[sockfd]);

	return 0;
}

int WriteCallback(int fd, int event, void *arg) {

	struct TagReactor *reactor = (struct TagReactor*)arg;
	struct TagEvent *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);
		EventDel(reactor->epollFd, ev);
        enum TagFdType type = CLIENTFD;
		EventSet(ev, fd, AcceptCallback, ReadCallback, WriteCallback, type, reactor);
		EventAdd(reactor->epollFd, EPOLLIN, ev);
	} else {
		close(ev->fd);
		EventDel(reactor->epollFd, ev);
		printf("send[fd=%d] error %s\n", fd, strerror(errno));
	}

	return len;
}

int ReadCallback(int fd, int event, void *arg) {
	struct TagReactor *reactor = (struct TagReactor*)arg;
	struct TagEvent *ev = reactor->events + fd;

	int len = recv(fd, ev->buffer, BUFFER_LENGTH , 0);
	EventDel(reactor->epollFd, ev);

	if (len > 0) {
		ev->length = len;
		ev->buffer[len] = '\0';
		printf("C[%d]:%s\n", fd, ev->buffer);
        enum TagFdType type = CLIENTFD;
		EventSet(ev, fd, AcceptCallback, ReadCallback, WriteCallback, type, reactor);
		EventAdd(reactor->epollFd, 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 AcceptCallback(int fd, int event, void *arg) 
{
    int ret = 0;
	struct TagReactor *reactor = (struct TagReactor*)arg;
	if (reactor == NULL) return -1;

	struct sockaddr_in clientAddr;
	socklen_t len = sizeof(clientAddr);

	int clientFd = accept(fd, (struct sockaddr*)&clientAddr, &len);
	if (clientFd == -1) {
		printf("accept: %s\n", strerror(errno));
		return -1;
	}

    if (clientFd > MAX_EPOLL_EVENTS) {
        printf("%s: max connect limit[%d]\n", __func__, MAX_EPOLL_EVENTS);
        return -1;
    }

    int flag = SetFdNonblock(clientFd);
    if (flag  < 0) {
        printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
        return -2;
    }

    enum TagFdType type = CLIENTFD;
    EventSet(&reactor->events[clientFd], clientFd, AcceptCallback, ReadCallback, WriteCallback, type, reactor);
    EventAdd(reactor->epollFd, EPOLLIN, &reactor->events[clientFd]);

	printf("new connect [%s:%d][time:%ld], pos[%d]\n", 
		inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), reactor->events[clientFd].lastActive, clientFd);

	return 0;
}

int ReactorRun(struct TagReactor *reactor) {
	if (reactor == NULL) {
        return -1;
    }
	if (reactor->epollFd < 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].lastActive;
			if (duration >= 60) {
				close(reactor->events[checkpos].fd);
				printf("[fd=%d] timeout\n", reactor->events[checkpos].fd);
				EventDel(reactor->epollFd, &reactor->events[checkpos]);
			}
		}

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

		for (i = 0;i < nready;i ++) {
			struct TagEvent *ev = (struct TagEvent*)events[i].data.ptr;

			if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
                if (ev->type == LISTENFD) { // 监听fd
                    ev->AcceptCallback(ev->fd, events[i].events, ev->arg);   
                } else { // 客户端连接fd
                    ev->ReadCallback(ev->fd, events[i].events, ev->arg);
                }
			}

			if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {
                ev->WriteCallback(ev->fd, events[i].events, ev->arg); // 写事件
			}
		}
	}
}

int ReactorDestory(struct TagReactor *reactor)
{
	close(reactor->epollFd);
	free(reactor->events);

	return 0;
}


int main(int argc, char const *argv[])
{
    int listenFd = ListenFdInit(9999);

	struct TagReactor *reactor = (struct TagReactor*)malloc(sizeof(struct TagReactor));
	ReactorInit(reactor);

	ReactorAddListener(reactor, listenFd);
    ReactorRun(reactor);
    ReactorDestory(reactor);
    close(listenFd);

    return 0;
}

四、采用reactor的并发模式

这里主要介绍两种并发模式:

简单的半同步/半反应堆模式

reactor模式_第1张图片异步线程就是主线程,它监听所有socket上的事件。如果有新的连接请求过来,主线程就接受并往epoll上注册读写事件。如果监听的socket上有毒血事件过来,主线程则把该socket插入请求队列(一般会把和该socket相关的应用程序数据、任务类型等信息封装成一个对象),交由工作线程处理。工作线程通过竞争机制获取任务的接管权。

高效的半同步/半反应堆模式

前面的简单模式有如下缺点:

  1. 主线程和工作线程共享请求队列。需要对工作线程进行加锁,白白耗费cpu事件
  2. 每个工作线程同一时间只能处理一个任务请求。如果客户任务请求数量比较多,二工作线程少,则请求队列堆积了很多请求对象,工作线程处理不过来,客户端的响应速度将越来越慢。
    高效模式则是为了解决上面的两个问题。
    reactor模式_第2张图片主线程只管监听listenFd并连接socket,返回的socket连接派发给工作线程,由工作线程的epoll_wait监听并处理该socket的可读可写等请求。主线程向工作线程派发socket最简单的方式就是往它与工作线程的管道之间传送数据,工作线程检测到管道上有数据可读时,就分析是否有一个新的客户端连接过来,于是该新的socket的读写事件注册到自己的epoll内核事件表中。

总结

本文章主要对基于reactor的高并发模式进行了一个介绍,并给出了一个reactor的简单实现。

你可能感兴趣的:(网络,服务器,后端)