不涉及原理的Epoll使用笔记(二)

本文深入解析了Linux的Epoll事件机制,包括简单和复杂两种注册事件的方法。通过`epoll_ctl`注册socket关注的事件,利用`epoll_wait`等待事件发生。文章还介绍了如何在服务器端使用Epoll实现监听客户端连接,详细阐述了监听、接受连接、处理事件的步骤,并强调了回调函数在事件处理中的作用。通过对`epoll_event`结构体的扩展,实现了自定义事件处理的机制。
摘要由CSDN通过智能技术生成

下面链接里是我找到的测试可用的完整代码:

epoll反应堆 - 张飘扬 - 博客园 (cnblogs.com)https://www.cnblogs.com/hesper/p/10739073.html

一、注册和返回的事件

1. 简单方法

上一篇文章里说过,最简单的方法可以直接使用epoll_ctl,例如我让Epoll帮我管理一个socket,只需要把一个epoll_event的事件类型events(关心什么事)和data(关心哪个文件)注册到Epoll就行了。

【这段代码叫代码1,下面会提到这段代码】 

// 代码1 简单注册
struct epoll_event ev;
int listen_sock, 

ev.events = EPOLLIN;  // 1. epoll_data指明要关注的事件是EPOLLIN,即写入
ev.data.fd = listen_sock;// 2. epoll_data指明要关注的文件是socket句柄listen_sock

// 3. 注册到Epoll
epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev);

2. 复杂一点的方法

上面链接中代码写的稍复杂一点。这里展开分析一下,复杂在哪呢?

首先作者自己封装了一个myevent_s结构体。这个myevent_s结构体和Epoll的epoll_event的最核心的不同就在于,多了一个call_back函数。也就是说,myevents不但保存了要知道发生了什么事,那个文件出了事,还要知道事件触发后要调用这个call_back函数处理。

struct myevent_s
{
	int fd;             //要监听的文件描述符                     (eventset赋值)
	int events;         //对应的监听事件,EPOLLIN和EPLLOUT       (eventadd赋值)
	void *arg;          //指向自己结构体指针                     (eventset赋值)
	//当这个事件发生的时候,就调用这个回调函数                                        
    void(*call_back)(int fd, int events, void *arg);//          (eventset赋值) 
	int status;         //是否在epoll上,1在0不在                (eventadd赋值)
	char buf[BUFLEN];   //                                      (接收时赋值)
	int len;            //                                      (接收时赋值)
	long last_active;   //记录每次加入红黑树 g_efd 的时间值       (eventset赋值)
};

 在此基础上,我们看上面链接中注册事件的代码是:

eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
// 第一个参数,设置后的g_events
// 第二个参数,要监听的文件
// 第三个参数,事件触发后要调用的函数
// 第四个参数,g_events自己,感觉没啥大作用。

eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); 
//功能:将lfd添加到监听树上,监听读事件
//第一个参数, epoll句柄
//第二个参数,要监听的事件
//第三个参数,包含回调函数的事件本身,传给void* ptr

这个eventset和eventadd都是代码作者自己封装的函数。其实,这个和简单方法(上面用代码1标注的那段)的思路是一致的,eventset函数完成的功能是1和2,eventadd完成的功能是3。总的功能就是,发现lfd发生EPOLLIN事件后,调用acceptconn函数。

那么这个代码的不同之处是什么呢,又是怎么配合callback函数完成回调,处理发生的时间的呢?

我把源码中eventadd函数简化了。

void eventadd(int efd, int events, struct myevent_s *ev) {
	struct epoll_event epv = { 0, { 0 } };
	epv.data.ptr = ev;                 // 关键代码
	epv.events = ev->events = events;  //EPOLLIN 或 EPOLLOUT

	epoll_ctl(efd, EPOLL_CTL_ADD, ev->fd, &epv);
	return;
}

epoll_ctl最终传入的三个参数分别是:efd是epoll句柄,events是正常的宏定义如EPOLLIN 或 EPOLLOUT,第三个参数是文件句柄,前三个和简单方法都一样,不同的是第四个参数。第四个参数是epoll_data联合体的中的void*型的ptr,这个指针指向的是eventset设置好的myevents_s结构体。我们需要知道以下几点:1. epoll_data 会随着 epoll_wait 返回的 epoll_event 一并返回。2. epoll_data联合体中的ptr是一个void*型,简单来讲就是可以指向任何类型。也就是说,我们传进去的myevents_s结构体中设置了事件触发后回调函数,而这个结构体在事件发生后会被epoll_wait原样返回。于是,事件发生后我们可以直接调用这个函数。

所以,总的来说,这种复杂的方法也是用了epoll_ctl函数注册事件,但是这种方法利用了void* ptr传递包含了回调函数的自定义消息。

二、Epoll实现服务器的思路

其实,实现服务器基本都是一个思路,现在分析一下上面代码的逻辑。

第一步,创建一个Epoll句柄。关键代码为

int g_efd = epoll_create(MAX_EVENTS + 1); 

第二步,初始化一个socket。关键代码为:

struct sockaddr_in sin;
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK); //将socket设为非阻塞
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);

第三步,监控是否有客户端连接。具体的方法是通过将socket句柄lfd注册到Epoll上去,监控socket的EPOLLIN事件。这里简单说一下为什么监控EPOLLIN事件能发现是不是有客户端连接呢?我的理解是,TCPIP建立链接要三次握手,这个过程中会有一些报文发送到服务器上,所以客户端连接服务器,会触发EPOLLIN事件。

eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); 

第四步,开始while(1)循环,不断地使用epoll_wait等待事件发生。

struct epoll_event events[MAX_EVENTS + 1]; 
while (1) {
		int nfd = epoll_wait(g_efd, events, MAX_EVENTS + 1, 1000);
        /* 处理事件 */
        //······
        /* 处理完了 */
}

当有注册过的事件发生了,epoll_wait会把这些事件放在epoll_events数组(也就是变量events[MAX_EVENTS + 1])里。

第五步,遍历epoll_events数组(也就是变量events[MAX_EVENTS + 1])。

for (i = 0; i < nfd; i++) {
    /* 遍历全部事件 */
    struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
    /* 具体处理 */
    //·······
}

epoll把发生的事件的具体情况放到了events[i]中。那么来具体的说说socke在建立、收发过程中,都会发生哪些事件。

一开始,客户端要先和服务器建立连接。在第三步中,我们关注了socket句柄l的EPOLLIN事件,并且设置了回调函数callback是acceptconn。建立连接时有握手信息发到socket上,EPOLLIN事件被触发。

if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {
    //ev->call_back(ev->fd, events[i].events, ev->arg);
    ev->call_back(ev->fd, events[i].events, ev);
}

这里的call_back函数,指向的就是acceptconn。在accept建立和客户端的连接后,将返回的客户端句柄也加入到epoll之中,监听EPOLLIN事件,并设置回调函数为读。

(未完)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值