epoll反应堆

一、epoll反应堆思想

epoll还有一种更高级的使用方法,那就是借鉴封装的思想,简单的说就是当某个事情发生了,自动的去处理这个事情。这样的思想对我们的编码来说就是设置回调,将文件描述符,对应的事件,和事件产生时的处理函数封装到一起,这样当某个文件描述符的事件发生了,回调函数会自动被触发,这就是所谓的反应堆思想。
从我们之前对epoll的使用上如何去支持反应堆呢?需要重新再认识一下struct epoll_event中的epoll_data_t结构体:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
我们之前使用的是共用体上的fd域,如果是要实现封装思想,光有fd是不够的,所以转换思路,看第一个域ptr,是一个泛型指针,指针可以指向一块内存区域,这块区域可以代表一个结构体,既然是结构体,那么我们就可以自定义了,将我们非常需要的文件描述符,事件类型,回调函数都封装在结构体上,这样当我们要监控的文件描述符对应的事件发生之后,我们去调用回调函数就可以了,这样就可以将文件描述符对应事件的处理代码梳理的非常清晰。

二、epoll 反应堆的感性认识

(1)普通 epoll 模型:
epoll_create 创建树根 ->epoll_ctl 设置 fd 可读、上树 -> 服务器调用 epoll_wait -> 客户端发送数据 -> epoll_wait 返回 -> fd 可读 -> read -> write -> 服务器再次调用 epoll_wait
(2)epoll 反应堆 模型:
epoll_create 创建树根 -> epoll_ctl 设置 fd 可读、上树 -> 服务器调用 epoll_wait -> 客户端发送数据 -> epoll_wait 返回 -> fd 可读 -> read -> epoll_ctl 设置 fd 下树 -> epoll_ctl 设置 fd 可写、上树 -> 服务器调用 epoll_wait -> epoll_wait 返回 -> fd 可写 -> write -> epoll_ctl 设置 fd 下树 -> epoll_ctl 设置 fd 可读、上树 -> 服务器再次调用 epoll_wait。

三、各种函数

1、主函数

#define MAX_EVENTS 1024 // 监听上限数
#define BUFLEN 4096 // 读写缓冲区大小
#define SERV_PORT 8080 // 默认端口
int g_efd; // 全局变量,保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1]; // 自定义结构体类型数组. +1 : listen fd
struct myevent_s
{
    int fd; // 要监听的文件描述符
    int events; // 对应的监听事件
    void *arg; // 泛型参数
    void (*call_back)(int fd, int events, void *arg); // 回调函数 (前3个元素,其实是回调函数的参数)
    int status; // 是否在监听:1  在红黑树上(监听) 0  不在(不监听)
    char buf[BUFLEN];
    int len;
    long last_active; // 记录每次加入红黑树 g_efd 的时间值
};
int main(int argc, char *argv[])
{
    unsigned short port = SERV_PORT; // 设置默认端口
    if (argc == 2)
        port = atoi(argv[1]); // 如果用户传参,使用用户指定端口,如未指定,用默认端口
    g_efd = epoll_create(MAX_EVENTS+1); // 创建红黑树(树根),返回给全局 g_efd
    if (g_efd <= 0)
        printf("create efd in %s err %s\n", __func__, strerror(errno));
    initlistensocket(g_efd, port); // 初始化监听socket 跳转到109.3
    // 通过 initlistensocket ,完成 socket 创建lfd ,lfd 设置非阻塞,上树监听读事件,bind , listen
    struct epoll_event events[MAX_EVENTS+1]; //保存满足事件的文件描述符数组,记录epoll_wait传出
    printf("server running: port[%d]\n", port);
    int checkpos = 0, i;
    while (1) 
    {
    //超时验证,每次测试100个链接,不测试listenfd,当客户端60秒内没有和服务器通信,则关闭此客户端链接
	    long now = time(NULL); // 当前时间
	    for (i = 0; i < 100; i++, checkpos++) //一次循环检测100个。 使用checkpos控制检测对象
	    {
		    if (checkpos == MAX_EVENTS)
		        checkpos = 0;
		    if (g_events[checkpos].status != 1) //不在红黑树 g_efd 上
		        continue;
		    long duration = now - g_events[checkpos].last_active; //客户端不活跃的世间
		    if (duration >= 60)
		    {
		    close(g_events[checkpos].fd); //关闭与该客户端链接
		    printf("[fd=%d] timeout\n", g_events[checkpos].fd);
		    eventdel(g_efd, &g_events[checkpos]); //将该客户端 从红黑树 g_efd移除
		    }
		}
		/*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/
		int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
		if (nfd < 0)
		{
			printf("epoll_wait error, exit\n");
			break; // 退出while循环,直接return
		}
		// 至此, if (nfd > 0) 进入回调函数,accpet 获得 cfd ,cfd 设置非阻塞,cfd 上树,监听cfd 读事件
		for (i = 0; i < nfd; i++)
		{
		/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/
			struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
			if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))
			{ //读就绪事件
				ev->call_back(ev->fd, events[i].events, ev->arg);
			}
			if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
			{ //写就绪事件
				ev->call_back(ev->fd, events[i].events, ev->arg);
			}
		}
	}
	/* 退出while 释放所有资源 */
	return 0;
}

2、初始化监听描述符

void initlistensocket(int efd, short port)
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	fcntl(lfd, F_SETFL, O_NONBLOCK); // 将socket设为非阻塞
	/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg); */
	eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
	// 初始化结构体 
	/* void eventadd(int efd, int events, struct myevent_s *ev) */
	eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); // 跳转
	// 至此,lfd 已经 添加到红黑树上,并监听其读事件
	struct sockaddr_in sin;
	memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin))
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(port);
	bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
	listen(lfd, 20);
	return ;
}

3、初始化结构体

void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
	ev->fd = fd;
	ev->call_back = call_back;
	ev->events = 0;
	ev->arg = arg;
	ev->status = 0;
	memset(ev->buf, 0, sizeof(ev->buf));
	ev->len = 0;
	ev->last_active = time(NULL); // 调用eventset函数的时间
	return;
} 

4、在红黑树上监听文件描述符

void eventadd(int efd, int events, struct myevent_s *ev)
{
	struct epoll_event epv = {0, {0}};
	int op;
	epv.data.ptr = ev;
	epv.events = ev->events = events; // EPOLLIN 或 EPOLLOUT
	if (ev->status == 1) 
	{
		// 已经在红黑树 g_efd 里
		op = EPOLL_CTL_MOD; // 修改其属性
	}
	else 
	{ // 不在红黑树里
		op = EPOLL_CTL_ADD; // 将其加入红黑树 g_efd, 并将status置1
		ev->status = 1;
	}
	if (epoll_ctl(efd, op, ev->fd, &epv) < 0) // 实际 添加 / 修改
		printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
	else
		printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);
	return ;
}

5、回调函数

/* 当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */
void acceptconn(int lfd, int events, void *arg)
{
	struct sockaddr_in cin;
	socklen_t len = sizeof(cin);
	int cfd, i;
	if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) 
	{
		if (errno != EAGAIN && errno != EINTR) 
		{
		/* 暂时不做出错处理 */
		}
		printf("%s: accept, %s\n", __func__, strerror(errno));
		return ;
	}
	do 
	{
		for (i = 0; i < MAX_EVENTS; i++) // 从全局数组 g_events 中找一个空闲元素
			if (g_events[i].status == 0) // 类似于 select 中找值为-1的元素
				break; // 跳出 for
		if (i == MAX_EVENTS) 
		{
			printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
			break; // 跳出do while(0) 不执行后续代码
		}
		int flag = 0;
		if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) //将cfd也设置为非阻塞
		{
			printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
			break;
		}
		/* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */
		eventset(&g_events[i], cfd, recvdata, &g_events[i]);
		eventadd(g_efd, EPOLLIN, &g_events[i]); // 将cfd添加到红黑树g_efd中,监听读事件
	} while(0);
	printf("new connect [%s:%d][time:%ld], pos[%d]\n",
	inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
	return ;
}

6、读事件回调函数

void recvdata(int fd, int events, void *arg)
{
	struct myevent_s *ev = (struct myevent_s *)arg;
	int len;
	len = recv(fd, ev->buf, sizeof(ev->buf), 0); //读文件描述符, 数据存入myevent_s成员buf中
	eventdel(g_efd, ev); //将该节点从红黑树上摘除
	if (len > 0) 
	{
		ev->len = len;
		ev->buf[len] = '\0'; //手动添加字符串结束标记
		printf("C[%d]:%s\n", fd, ev->buf);
		eventset(ev, fd, senddata, ev); //设置该 fd 对应的回调函数为 senddata
		eventadd(g_efd, EPOLLOUT, ev); //将fd加入红黑树g_efd中,监听其写事件
	} 
	else if (len == 0) 
	{
		close(ev->fd);
		/* ev-g_events 地址相减得到偏移元素位置 */
		printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
	} 
	else 
	{
		close(ev->fd);
		printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
	}
	return;
}

7、写事件的回调函数

void senddata(int fd, int events, void *arg)
{
	struct myevent_s *ev = (struct myevent_s *)arg;
	int len;
	len = send(fd, ev->buf, ev->len, 0); //直接将数据 回写给客户端。未作处理
	/*
	printf("fd=%d\tev->buf=%s\ttev->len=%d\n", fd, ev->buf, ev->len);
	printf("send len = %d\n", len);
	*/
	if (len > 0) 
	{
		printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
		eventdel(g_efd, ev); // 从红黑树g_efd中移除
		eventset(ev, fd, recvdata, ev); // 将该fd的 回调函数改为 recvdata
		eventadd(g_efd, EPOLLIN, ev); // 从新添加到红黑树上, 设为监听读事件
	}
	else 
	{
		close(ev->fd); // 关闭链接
		eventdel(g_efd, ev); // 从红黑树g_efd中移除
		printf("send[fd=%d] error %s\n", fd, strerror(errno));
	}
	return ;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值