我看好多类似的程序需要积分下载,本着开源精神,我不想把程序搞封闭,所以我在文章中直接附上可执行源码,只希望各位工程师多多关注在下。
今天学了下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
服务端