深入浅出谈谈网络reactor模式与百万并发

这篇博文围绕着服务器网络连接的百万并发问题来谈谈reactor网络处理模型,将回答以下这几个问题:1.什么是reactor模式,2. 为什么会有reactor模式,3. 网络并发的瓶颈在哪里,4.以http连接为例,讲解如何实现epoll reactor的封装。

几乎所有的服务器中间件的网络模型都是epoll 封装的reactor模式,比如redis,nginx,memcached的网路层。那什么是reactor模式呢?

1.什么是reactor模式:

首先,我们来回想一下普通函数调用的机制:程序调用某函数,函数执行,程序等待,函数将 结果和控制权返回给程序,程序继续处理。Reactor 释义“反应堆”,是一种事件驱动机制。 reactor模式和普通函数调用的不同之处在于:应用程序不是主动的调用某个 API 完成处理,而是恰恰相反,Reactor 逆置了事件处理流程,应用程序需要提供相应的接口并注册到 Reactor上, 如果相应的时间发生,Reactor 将主动调用应用程序注册的接口,这些接口又称为“回调函数”。这样说比较抽象,简单而言,reactor模式就是当有网络连接过来的时候,自动调用对应的函数,即网络事件将对应一个的业务层接口,做到一事件,一处理。具体理解,还得看下面的代码。我将接着讲reactor模式的好处。

2.reactor模型的好处

首先,需要记住的是,reactor模式是 非阻塞io+epoll,原因是一个epoll要处理多个io连接,如果使用epoll的水平触发模式,当调用read()函数的时候,如果数据为空,则会被阻塞。另外,一个reactor对应一个epoll事件,而一个线程是一个epoll,因此,一个reactor对应一个线程,所以不会有多线程竞争的情况。

下面,我讲讲解我写的一个基于epoll的reactor网络处理模型:

1.)头文件和预处理定义:

#include <unistd.h>

#include <sys/types.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#include <fcntl.h>
#include <string.h>
#include <errno.h>     //头文件不懂的,linux下直接man命令 或者 info命令

#define BUFFER_LENGTH       1024
#define MAX_EPOLL_EVENTS    1024
#define SERVER_PORT			9999   //顾名思义
#define PORT_COUNT			20

typedef int CALLBACK(int, int, void*); //回调函数指针

2.)  结构体定义:

//反应堆对应的事件,其实就是连接的事件
typedef struct reactor_events_{
    int fd; 
    int event_type;//epoll的事件类型

    void* arg;
    (void*)callback(int, int, void*);

    char rbuffer[BUFFER_LENGTH];
    char wbuffer[BUFFER_LENGTH];
    int rlength;
    int wlength;
    
    int status; //判断事件类型
    int methods;
//这里根据业务逻辑,可以再加其他的方法

}reactor_events;


//连接事件的储存,采用事件块储存,动态分配
typedef struct event_block_{
    struct reactor_events* event;  //指向event结构体的指针
    struct event_block* next;      //指向下一块block的指针
}event_block;

//反应堆,一个反应堆,一个epoll,一个线程
typedef struct C_Reactor_{
    int epfd;  //一个反应堆对应一个epoll
    struct event_block* evblock; //指向event_block的指针
    int evblockidx;   //当前reactor所有的event_block的数量,其实也是最后一块的索引
}C_Reactor;

3.)重要的函数:

(1)epoll事件块的设置,增加,删除(网络连接的设置,增加,删除)


int C_event_set(struct reactor_events* ev,int fd, CALLBACK callback,void* arg){
    ev->fd=fd;
    ev->events=0;   //没有任何epoll事件发生

    ev->callback=callback;
    ev->arg=arg;

    return 0;
}

int C_event_add(struct reactor_events* ev, int epfd,int events){
    struct epoll_event ep_ev={0,{0}};  //表示一个epoll中的就绪事件 ,事件类型和epoll_data
    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) return -1;
    
    return 0;
}

int C_event_delete(struct reactor_events* ev,int epfd){
    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;
}

读者可能会疑惑一下的代码:

 struct epoll_event ep_ev={0,{0}};  
    ep_ev.data.ptr=ev,     
    ep_ev.events=ev->events=events

那么,可以去看看我的其他博文《深入剖析linux 网络io多路复用》中对epoll原理的讲解。其实,epoll事件块是由红黑树来储存的,里面有一个联合体和一个结构体:具体代码如下

typedef union epoll_data {
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event {
  uint32_t events;  // 表示事件类型的位标志(bit flag)
  epoll_data_t data;  // 与事件相关的数据
};

当我们用把结构体指针reactor_events* ev赋值给ep_ev.data.ptr时,ev和epoll_data这个联合体就已经建立对应的关系,也即建立了一个epoll事件块。(联合体在初始化时只能对其中一个成员赋值,对其它成员的值会被覆盖)。

那么,到这里,我们先看一下,这个reactor模式的运行方式,然后我再讲解里面的函数实现

int main(int argc,char**agv){
      
    struct C_reactor *reactor = (struct C_reactor*)malloc(sizeof(struct C_reactor)); //创建一个reactor结构体
	C_reactor_init(reactor); //初始化

	unsigned short port = SERVER_PORT;
	if (argc == 2) {
		port = atoi(argv[1]);
	}

	int i = 0;
	int sockfds[PORT_COUNT] = {0};
	
	for (i = 0;i < PORT_COUNT;i ++) {
		sockfds[i] = init_sock(port+i);  //增加端口号
		C_reactor_addlistener(reactor, sockfds[i], accept_cb);
	}


	C_reactor_run(reactor);

	C_reactor_destory(reactor);
	
	for (i = 0;i < PORT_COUNT;i ++) {
		close(sockfds[i]);
	}
	free(reactor);
	
	return 0;
}

我们先来看C_reactor_init(reactor)

int C_reactor_init(struct C_Reactor*reactor){
    reactor=(struct C_Reactor*)malloc(sizeof(struct CReactor));
    memset(reactor,0,sizeof(struct CReactor));
    
    reactor->epfd=epoll_create(1);  //创建epoll
    if(reactor->epfd<=0){
        return -1;
    }

    struct reactor_events* ev=(struct reactor_events*)malloc((MAX_EPOLL_EVENTS)*sizeof(struct reactor_events));
    memset(ev,0,((MAX_EPOLL_EVENTS)*sizeof(struct reactor_events)));
    if(ev==nullptr){
        free(reactor);
        close(reactor->epfd);
        return -2;
    }

    struct event_block* block=(struct event_block*)malloc(sizeof(struct event_block));
    memset(block,0,sizeof(struct event_block));
    if(block==nullptr){
        free(reactor);
        free(ev);
        close(reactor->epfd);
        return -3;
    }

    reactor->evblock=block;
    reactor->evblockidx=1;

    reactor->evblock->events=ev;
    reactor->evblock->next=nullptr;

    return 0;
}

event_block是动态分配的,分配一个event_block块,这样一个块中有MAX_EPOLL_EVENTS个epoll事件块,同时events_block的个数加1:


int C_reactor_alloc(struct C_reactor *reactor) {

	if (reactor == NULL) return -1;
	if (reactor->evblock == NULL) return -1;
	
	struct event_block* blk = reactor->evblock;

	while (blk->next != NULL) {
		blk = blk->next;
	}

	struct reactor_event* evs = (struct reactor_event*)malloc((MAX_EPOLL_EVENTS) * sizeof(struct reator_event));
	if (evs == NULL)  return -2;

	memset(evs, 0, (MAX_EPOLL_EVENTS) * sizeof(struct reactor_event));

	struct event_block *block = malloc(sizeof(struct event_block));
	if (block == NULL) return -3;
	
	block->events = evs;
	block->next = NULL;

	blk->next = block;
	reactor->evblockid++;

	return 0;
}

在网络编程中,五元组(Five-Tuple)是指用于唯一标识网络通信中的一个数据流的五个要素。这五个要素包括:

  1. 源IP地址(Source IP Address):发送数据包的源设备的IP地址。

  2. 目标IP地址(Destination IP Address):接收数据包的目标设备的IP地址。

  3. 源端口号(Source Port Number):发送数据包的源设备使用的端口号。

  4. 目标端口号(Destination Port Number):接收数据包的目标设备使用的端口号。

  5. 传输层协议(Transport Layer Protocol):TCP、UDP等用于传输数据的协议.

int init_sock(short port) {

	int listenfd = 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 -1;
	}

	printf("listen server port : %d\n", port);

	return listenfd;
}
 
//sockfd所在的event_block,创建event事件块
struct reactor_event *C_reactor_idx(struct C_reactor *reactor, int sockfd) {

	if (reactor == NULL) return NULL;
	if (reactor->evblock== NULL) return NULL;

	int blkidx = sockfd / MAX_EPOLL_EVENTS;
	while (blkidx >= reactor->evblockidx) {
		C_reactor_alloc(reactor);
	}

	int i = 0;
	struct event_block* blk = reactor->evblock;
	while (i++ != blkidx && blk != NULL) {
		blk = blk->next;
	}

	return &blk->events[sockfd % MAX_EPOLL_EVENTS];
}

然后,将我们设置的listenfd加入epoll事件块里面:

int C_reactor_addlistener(struct C_reactor *reactor, int sockfd, NCALLBACK *acceptor) {

	if (reactor == NULL) return -1;
	if (reactor->evblock== NULL) return -1;

	struct reactor_events *event = C_reactor_idx(reactor, sockfd);
	if (event == NULL) return -1;
    
	C_event_set(event, sockfd, acceptor, reactor);
	C_event_add(reactor->epfd, EPOLLIN, event);

	return 0;
}

启动反应堆监听io的连接,实际上,就是不断地让epoll_wait()去轮询,当有epoll事件块状态发生改变的时候,就返回相应的fd

int C_reactor_run(struct C_reactor *reactor) {
	if (reactor == NULL) return -1;
	if (reactor->epfd < 0) return -1;
	if (reactor->evblock == NULL) return -1;
	
	struct epoll_event events[MAX_EPOLL_EVENTS+1];
	
	int checkpos = 0;

	while (1) {

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

		for (int i = 0;i < nready;i++) {

			struct reator_event *ev = (struct reator_event*)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 C_reactor_destory(struct C_reactor *reactor) {

	close(reactor->epfd);

	struct eventblock *blk = reactor->ev_block;
	struct eventblock *blk_next;
	while (blk != NULL) {
		blk_next = blk->next;

		free(blk->events);
		free(blk);
		
		blk = blk_next;
	}

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值