reactor编程模型+EPOLL服务器程序

我看好多类似的程序需要积分下载,本着开源精神,我不想把程序搞封闭,所以我在文章中直接附上可执行源码,只希望各位工程师多多关注在下。

今天学了下reactor编程模型,并用C语言在Linux上写了一个基于reactor事件驱动的服务器。在文末测试了两台客户机连接的情景,和预期结果一样。

reactor

   背景

        网络连接上的消息处理,可以分为两个阶段:1.等待消息准备完成,2.进行消息处理。

        使用默认的阻塞套接字时(1 个线程捆绑处理 1 个连接,如多线程http服务器),常常把这两个阶段合并,如此一来,处理连接的线程就得阻塞,当接收到消息时被唤醒,这导致高并发下线程会频繁的睡眠、唤醒,从而影响 CPU 的使用效率。

       高并发编程方法是把两个阶段分开处理。等待消息准备完成与处理消息的代码是分离的。等待消息准备完成这个阶段可以让线程主动查询,或者让 1 个线程为所有连接而等待。在处理消息这一阶段,要求套接字必须是非阻塞的,否则,处理消息的线程又可能被阻塞。

让 1 个线程为所有连接而等待,这就是 IO 多路复用。多路复用就是处理“等待消息准备完成”这件事的,它可以同时处理多个连接。它也可能“等待”,也就是让所在线程阻塞,然而这不要紧,因为它一对多、它可以监控它感兴趣的所有连接。这样,当查询线程被唤醒执行时,就一定是有一些连接要完成一些任务了。

   登台

         作为一个高性能服务器程序通常需要考虑处理三类事件: I/O 事件,定时事件及信号。Reactor就是一种高效的事件处理模型。

        下图是reactor的模式图:

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

       Reactor 模型有三个重要的组件:

       多路复用器:由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。

       事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中。

       事件处理器:负责处理特定事件的处理函数。

Reactor 模式是编写高性能网络服务器的必备技术之一,它具有如下的优点:

1.响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;

2.编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;

3.可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;

4.可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;

       reactor的缺点正好来自于它的优点 。reactor通常是单线程的,reactor希望用单线程使用一颗 CPU 的全部资源,这样每个事件处理中往往可以不考虑共享资源的互斥访问。但是现在处理机不只有一个CPU,这就导致了这样的问题:如果每个CPU上处理的信息需要跨CPU交换,reactor如何在多个CPU之间传递消息。

基于reactor模式的服务器(C语言实现)

客户机连接服务器,并向服务器发送消息;

服务器打印客户机的消息,并回传客户机的IP和端口号。

使用单例模式编写了一个reactor,主要函数如下:

int init_reactor(struct reactor *r);//初始化reactor

int reactor_set_event(int fd, call_back RD, call_back WT, call_back AC, int event ,void *arg) ;//设立事件

int reactor_del_event(int fd , int event, void *arg) ;//删除事件

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/poll.h>
#include <sys/epoll.h>

#include <pthread.h>
  
#define MAX_CON  8192              //最大连接数  通过使用链表可以扩充最大连接数,这里简化到数组

#define BUFFER_LENGTH		1024   //发送和接受缓冲区的长度
#define MAX_EPOLL_EVENT		1024   //最大的事件数目

#define CIENT_INFO_LEN      256    //客户端信息长度 包含IP和端口号

//事件状态
#define NOSET	   0               //没注册
#define RD_EV      1               //读
#define WT_EV      2               //写
#define RW_EV      3               //读写
#define AC_EV      4
typedef int (*call_back)(int fd, int event, void *arg); //回调函数类型定义

struct fd_events { 

	int fd;    //对应的连接

	int status; //状态
	int events; //事件  
	void *arg;

	char client_info[CIENT_INFO_LEN];

	call_back readcb;   // epollin 
	call_back writecb;  // epollout
	call_back acceptcb; // epollin

	unsigned char sd_buff[BUFFER_LENGTH]; 
	int slen;

	unsigned char rv_buff[BUFFER_LENGTH];
	int rlen;
	
};

struct reactor {
	int epfd;
	struct fd_events* ptr_fd_events;   
};

struct reactor *instance = NULL;

int init_reactor(struct reactor *r);                    //初始化reactor
int rd_callback(int fd, int event, void* arg);          //读事件
int wt_callback(int fd, int event, void* arg);          //写事件
int ac_callback(int fd, int event, void* arg);          //建立连接事件

struct reactor *getInstance(void) { //单例模式
	if (instance == NULL) {
		instance = malloc(sizeof(struct reactor));
		if (instance == NULL) return NULL;
		memset(instance, 0, sizeof(struct reactor));
		if (0 > init_reactor(instance)) {
			free(instance);
			return NULL;
		}
	}
	return instance;
}

int reactor_set_event(int fd, call_back RD, call_back WT, call_back AC, int event ,void *arg) {

	struct reactor *r = getInstance();
	
	struct epoll_event ev = {0};
	//连接事件
	if (event == AC_EV) { 
		r->ptr_fd_events[fd].fd = fd;
		r->ptr_fd_events[fd].acceptcb = AC;
		ev.events = EPOLLIN;
	} else {
		//读事件
		if (RD == &rd_callback) {
			r->ptr_fd_events[fd].fd = fd;
			r->ptr_fd_events[fd].readcb = RD;
			ev.events = EPOLLIN;
		}
		//写事件
		else if (WT == &wt_callback) {
			r->ptr_fd_events[fd].fd = fd;
			r->ptr_fd_events[fd].writecb = WT;
			ev.events = EPOLLOUT;
		} else if (RD == &rd_callback && WT == &wt_callback){

			r->ptr_fd_events[fd].fd = fd;
			r->ptr_fd_events[fd].writecb = WT;
			r->ptr_fd_events[fd].readcb = RD;
			ev.events = EPOLLIN|EPOLLOUT;
		}else{
			return -1;
		}
	}

	if (arg != NULL){
		char* buffer = (char*)arg;
		//printf("here %s\n",buffer);
		//添加客户端信息
		strcpy(r->ptr_fd_events[fd].client_info, buffer);
		//printf("here %s\n",buffer);
		printf("client %s added to listenning set\n", buffer);

	}

	ev.data.ptr = &r->ptr_fd_events[fd];

	//新加入的文件描述符需要设置状态
	if (r->ptr_fd_events[fd].events == NOSET) {
		
		if (epoll_ctl(r->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
			perror("epoll_ctl:");
			return -1;
		}
		r->ptr_fd_events[fd].events = event;
	} else if (r->ptr_fd_events[fd].events != event) {  //同一个文件描述符修改监视的状态

		if (epoll_ctl(r->epfd, EPOLL_CTL_MOD, fd, &ev) < 0) {
			perror("epoll_ctl:");
			return -1;
		}
		r->ptr_fd_events[fd].events = event;
	}
	else {
		printf("undefined in reactor_set_event\n");
	}

	return 0;
}


//删除一个事件
int reactor_del_event(int fd , int event, void *arg) {

	struct reactor *r = getInstance();
	
	struct epoll_event ev = {0};
	ev.data.ptr = arg;

	//从epoll中删除
	epoll_ctl(r->epfd, EPOLL_CTL_DEL, fd, &ev);

	//标志为0 代表删除
	r->ptr_fd_events[fd].events = 0;

	return 0;
}


//写事件
int wt_callback(int fd, int event, void *arg) {

	struct reactor *r = getInstance();
	
	unsigned char *sbuff = r->ptr_fd_events[fd].client_info;
	int len = strlen(sbuff); 

	int ret = send(fd, sbuff, len, 0);

	if (ret < len) {
		printf("failed to send to %s :", r->ptr_fd_events[fd].client_info);
	} else {
		printf("success to send to %s :", r->ptr_fd_events[fd].client_info);
		reactor_set_event(fd, rd_callback, NULL, NULL, RD_EV, NULL);
	}
	return 0;
}


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

	struct reactor *r = getInstance();

	unsigned char* rbuff = r->ptr_fd_events[fd].rv_buff;;

	int ret = recv(fd, rbuff, BUFFER_LENGTH, 0);

	if (ret == 0) { //关闭连接
		reactor_del_event(fd, 0, NULL);
		close(fd);
		
	} else if (ret > 0) {

		printf("socket %d read:%s\n", fd , rbuff);
		memset(r->ptr_fd_events[fd].rv_buff, 0, BUFFER_LENGTH);
		
		
		reactor_set_event(fd, NULL, wt_callback, NULL, WT_EV, NULL);

	}
}

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

	int connfd;
	struct sockaddr_in client;
    socklen_t len = sizeof(client);
    if ((connfd = accept(fd, (struct sockaddr *)&client, &len)) == -1) {
		perror("accept");
        return -1;
    }
	if (connfd >= MAX_CON) {
		printf("out range \n");
		close(connfd);
	}

	char buffer[CIENT_INFO_LEN] = { 0 };
	
	sprintf (buffer, "Hello !!! %s : %d", inet_ntoa(client.sin_addr) ,ntohs(client.sin_port));

	//检测读事件
	reactor_set_event(connfd, rd_callback, NULL, NULL, RD_EV, buffer);
	return 0;
}


//初始化服务器
int init_server(int port) { 

	int listenfd;
    struct sockaddr_in servaddr;

    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("failed to create socket:");
        return 0;
    }
	
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
 
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
		perror("failed to bind socket:");
        return 0;
    }
 
    if (listen(listenfd, 10) == -1) {
		perror("failed to listen:");
        return 0;
    }
	return listenfd;
}

//初始化reactor
int init_reactor(struct reactor *r) {

	if (r == NULL) return -1;
	int epfd = epoll_create(1); //初始化epoll对象
	r->epfd = epfd;
	//初始化映射表
	r->ptr_fd_events = (struct fd_events*)malloc(MAX_CON*sizeof(struct fd_events));
	if (r->ptr_fd_events == NULL) {
		close(epfd);
		return -2;
	} 
	memset(r->ptr_fd_events, 0, MAX_CON*sizeof(struct fd_events));

	return 0;
}

//查询线程
int reactor_loop(int listenfd) {

	struct reactor *r = getInstance();




	struct epoll_event events[MAX_EPOLL_EVENT] = {0};
	int n = 0;  //显示循环次数 调试用
	while (1) {
		
		int nready = epoll_wait(r->epfd, events, MAX_EPOLL_EVENT, 5);

		if (nready == -1) {
			continue;
		}

		for (int i = 0;i < nready;i++) {
			++n;
			struct fd_events * evnt = (struct fd_events*)events[i].data.ptr;
			int connfd = evnt->fd;

			if (connfd == listenfd) { //
				evnt->acceptcb(listenfd, 0, NULL);
			} else {
			
				if (events[i].events & EPOLLIN) { 
					evnt->readcb(connfd, 0, NULL);
					printf("read event #%d\n" ,n );
					
				
				} 
				if (events[i].events & EPOLLOUT) {
					evnt->writecb(connfd, 0, NULL);
					printf("write event #%d\n" ,n );
		
				}
			}
		}

	}

	return 0;
}


int main(int argc, char **argv) 
{
	int listenfd = init_server(9999);
	reactor_set_event(listenfd, NULL, NULL, ac_callback, AC_EV, NULL);
	reactor_loop(listenfd);
    return 0;
}

运行

在ubuntu 上编译运行代码,

并在win下使用TCP客户端连接测试;

客户机1

客户机2

 

服务端

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值