linux网络编程之epoll reactor模式

I/O多路复用

在获取事件时,把关心的连接传给内核,再由内核检测

如果没有事件发生,线程只需要阻塞;如果有事件发生,内核返回了事件的连接,线程从阻塞状态返回。

Reactor模式采用面向对象的思想,对I/O多路复用接口进行封装。

Reactor模式

有事件到,reactor就有相应的反映。

Reactor模式由 Reactor处理资源池两部分组成。
Reactor:负责监听和分发事件(连接、读、写)
处理资源池:负责处理事件,如read->exec->write;

Reactor和处理资源池可以由一个或多个组成

单Reactor 单线程/进程

在这里插入图片描述
有三个对象
reactor:监听和分发事件(dispatch)。通过select监听事件,收到事件后dispatch进行分发给具体的acceptor还是handler对象
acceptor:获取链接,通过accept方法获取连接,并创建一个handler对象来处理后续响应事件。
handler:处理业务:read,业务处理,write

优点
单Reactor单进程在同一个进程内完成,事件比较简单,不需要考虑进程间通信,和资源竞争问题。

缺点
1、无法充分利用多核CPU资源
2、handler对象在业务处理时,整个进程是无法处理其他连接事件的。如果业务处理耗时比较长,会造成响应延迟。

单Reactor 多线程/进程

在这里插入图片描述
与上一个模式的区别:
子线程里的processor对象进行业务处理,处理完成后,将结果发送给主线程的handler对象,由handler通过send方法将响应结果发送给client

优点:能充分利用多核cpu资源
但共享资源需要加锁

多Reactor 多进程/线程

在这里插入图片描述
主线程中MainReactor对象通过select监控连接建立事件,收到事件后通过acceptor中的accept连接,将新连接分配给某个子线程

子线程中的SubReactor对象将MainReactor对称分配连接加入select继续监听,并创建handler用于处理连接的相应事件;
如果有新事件发生,SubReactor对象会调用当前连接对应的handler对象进行响应;

优点:
主线程只负责接收新连接,子线程负责完成后续的业务处理。
主线程只需要把新连接传给子线程,子线程无需返回数据。

netty 与 memcache都采用此方案

reactor是非阻塞同步网络模式,感知的是就绪可读可写事件,proactor是异步网络模式,感知的是已完成的读写事件。

epoll reactor模式实例

程序流程:
epoll_create创建监听-》epoll_ctl 添加fd-》while(1) epoll_wait 监听-》对应监听fd的事件-》返回监听满足的数据-》判断返回数组元素-》lfd满足–accept 设置cfd读事件-》cfd满足 -》recv 读 cfd从监听队列删除,epoll_ctrl设置回调EPOLLOUT写-》添加到队列EPOLL_CTL_ADD,反复读写

在这里插入图片描述

实例源码

#include<stdlib.h>
#include<stdio.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<time.h>

#define MAX_EVENTS 1024
#define BUFLEN 128
#define SERV_PORT 8080

//epoll ET mode + O_NONBLOCK
typedef struct myevent_s{
	int fd;
	int events;
	void *arg;
	void (*call_back)(int fd,int events,void *arg);
	int status;
	char buf[BUFLEN];
	int len;
	long last_active;
}myevent_s;

int g_efd;
struct myevent_s g_events[MAX_EVENTS + 1];

void recvdata(int fd, int events, void *arg); 
void senddata(int fd, int events, void *arg);

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;
	ev->last_active = time(NULL);
	return;
}

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;

	if(ev->status == 1)
	{
		op = EPOLL_CTL_MOD;
	}
	else
	{
		op = EPOLL_CTL_ADD;
		ev->status = 1;
	}
	if(epoll_ctl(efd,op,ev->fd,&epv) < 0)
	{
		printf("event add failed [%d],events[%d]\n",ev->fd,events);
	}
	else{
		printf("event add ok [fd=%d],op=[%d],events=;%x]\n",
				ev->fd,op,events);
	}
	return;
}

void eventdel(int efd,struct myevent_s *ev)
{
	struct epoll_event epv={0,{0}};

	if(ev->status != 1)
	{
		return;
	}
	epv.data.ptr = ev;
	ev->status = 0;
	epoll_ctl(efd,EPOLL_CTL_DEL,ev->fd,&epv);
	return;
}

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++)
		{
			if(g_events[i].status == 0)
				break;
		}
		if(i == MAX_EVENTS){
			printf("%s max connection limit[%d]\n",__func__,MAX_EVENTS);
			break;
		}
		int flag = 0;
		if((flag = fcntl(cfd,F_SETFL,O_NONBLOCK))<0)
		{
			printf("%s:fcntl noblocking failed ,%s\n",__func__,strerror(errno));
			break;
		}
		eventset(&g_events[i],cfd,recvdata,&g_events[i]);
		eventadd(g_efd,EPOLLIN,&g_events[i]);
	}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 ;
}

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);
	
	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);
		eventadd(g_efd,EPOLLOUT,ev);
	}
	else if(len == 0)
	{
		close(ev->fd);
		printf("fd=%d,pos=%d,closed\n",fd,(int)(ev - g_events));
	}
	else{
	
		close(ev->fd);
		printf("recv fd=%d,error %d:%s\n",fd,errno,strerror(errno));

	}
	return;

}

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);

	eventdel(g_efd,ev);
	if(len > 0)
	{
		printf("send fd=%d, %d %s\n",fd,len,ev->buf);
		eventset(ev,fd,recvdata,ev);
		eventadd(g_efd,EPOLLIN,ev);
	}
	else
	{
		close(ev->fd);
		printf("send [fd=%d] error %s\n",fd,strerror(errno));
	}
	return;
}


void initlistensocket(int efd,short port)
{
	int lfd = socket(AF_INET,SOCK_STREAM,0);
	fcntl(lfd,F_SETFL,O_NONBLOCK);
	
	eventset(&g_events[0],lfd,acceptconn,&g_events[0]);
	eventadd(efd,EPOLLIN,&g_events[0]);

	struct sockaddr_in sin;

	memset(&sin,0,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;
}


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 );
	if(g_efd <=0)
	{
		printf("create efd in %s err %s\n",__func__,strerror(errno));
	}
	initlistensocket(g_efd,port);

	struct epoll_event events[MAX_EVENTS +1];
	printf("server running:port[%d]\n",port);

	int checkpos = 0,i;

	while(1)
	{
		long now = time(NULL);
		for(i = 0;i<100;i++,checkpos++){
			if(checkpos == MAX_EVENTS)
				checkpos = 0;
			if(g_events[checkpos].status != 1)
				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]);
			}
		}

		int nfd = epoll_wait(g_efd,events,MAX_EVENTS+1,10000);
		if(nfd < 0)
		{
			printf("epoll_wait error exit\n");
			break;
		}

		for (i = 0; i < nfd; i++) 
		{ 
			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) ; 
			}
		}
	}
	return 0;
}

两个客户端,一个主动断掉连接,一个超时被服务端断掉

(base) wy@ubuntu:~/network$ ./epoll
event add ok [fd=4],op=[1],events=;1]
server running:port[8080]
event add ok [fd=5],op=[1],events=;1]
new connect [127.0.0.1:34766][time:1644381280], pos[1]
C [5]:hello

event add ok [fd=5],op=[1],events=;4]
send fd=5, 6 hello

event add ok [fd=5],op=[1],events=;1]
event add ok [fd=6],op=[1],events=;1]
new connect [127.0.0.1:34768][time:1644381290], pos[2]
C [6]:world

event add ok [fd=6],op=[1],events=;4]
send fd=6, 6 world

event add ok [fd=6],op=[1],events=;1]
fd=6,pos=2,closed
[fd=4] timeout
[fd=5] timeout

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了维护世界和平_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值