新页面
- epoll
- 通俗的理解:
有个小区,一开始小区里的人寄快递都是叫快递员上门取件,或者是去快递店里寄,前者有点费快递员的人力,后者可以有浪费顾客的时间,后来快递公司想到了一个方法, 在小区里建立了一个无人快递收件柜子(蜂巢),顾客把想寄的东西都放在那个柜子了,快递人员到一定的时间带个大袋子,把要寄送的东西带走,这样就省去快递员挨家挨户的收快递,提高了效率
小区里的人就是 那些文件io,柜子里的装的东西就是 有事件响应的io,这样就不用挨个去遍历了
- 底层如何实现的呢?
面试有问过,我一般都是说红黑树加双向链表实现的,然后就说不下去了,现在做一个epoll的大总结
epoll 使用就不在多说,现在说个话题 为什么 epoll 时间复杂度可以做到O(1) 呢?
我们一般都会说,底层把有事件发生的io放在一个队列里,这个对个队列是一个双向链表,插入的时间复杂为O(1),
没错,在用户态 我们遍历队列的时候,可以保证遍历的每个io都是有事件发生,效率肯定是上去了,那么在内核态,底层是如何把有事件发生的io放到队列呢? 怎么放我们已经知道了,现在问题是在那么多io里,怎么马上挑出有事件的io呢?难道也是在内核态全部遍历一次?我一开始也百思不得其解,查阅很多资料知道,原来在更底层有那么一个机制,刚一个io有时间发生会产生一个软终端,然后我想到了 信号,信号的类型有个是 SIGIO(这里的软中断我也不是很确定是不是io中断)
sigaction(SIGIO, &sigio_action, NULL);
当io有什么事件发生时候,会马上调用这个对应的回调函数,那这样,我并不需要每个io的去问有没有事件发生,我就每个io都设置一个回调函数,那么io有事件了,马上调用回调函数,那么就准备把这个io装在队列里面里,而红黑树的作用就是管理这些io并设置其回调函数,就好像这个api
**epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);**
把新的io加入到红黑树里面,然后注册一个对应的回调函数,我们都知道红黑树的特性是 增删改查时间复杂度稳定 为
logn,有io退出,取消对应的回调函数,然后删除这个io
以上就是当前对epoll底层实现 的初步了解,后续看了源码在来更新
具体流程如下如下所示 (在知乎看到的)
最后放一个epoll 使用,这里用到了reactor模式(大家可以搜下这个),加了比较通俗一点的注释,方便理解
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
#include <sys/epoll.h>
#define Buffersize 1024
enum WS_STATUS
{
WS_INIt,
WS_HANDSHAKE,
WS_DATAFORM,
WS_DATAEND,
};
int handshake(struct sockitem *si, struct reactor *mainloop)
{
}
struct sockitem
{ //
int sockfd;
int (*callback)(int fd, int events, void *arg);
char recvbuffer[Buffersize]; //
char sendbuffer[Buffersize];
int rlength;
int stauts;
};
// mainloop / eventloop --> epoll -->
struct reactor
{
int epfd;
struct epoll_event events[512];
};
struct reactor *eventloop = NULL;
int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg)
{
struct sockitem *si = (struct sockitem *)arg;
memcpy(si->sendbuffer, si->recvbuffer, si->rlength);
send(fd, si->sendbuffer, si->rlength + 1, 0); //
send(fd, "\n", 1, 0);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = fd;
si->callback = recv_cb;
ev.data.ptr = si;
epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);
}
// ./epoll 8080
int recv_cb(int fd, int events, void *arg)
{
//这里就是普通 用户的处理方案了
//int clientfd = events[i].data.fd;
struct sockitem *si = (struct sockitem *)arg;
struct epoll_event ev;
char buffer[1024] = {0};
int ret = recv(fd, buffer, 1024, 0);
if (ret < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
{ //
return -1;
}
else
{
}
ev.events = EPOLLIN;
//ev.data.fd = fd;
epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
close(fd);
free(si);
}
else if (ret == 0)
{ //
//
printf("disconnect %d\n", fd);
ev.events = EPOLLIN;
//ev.data.fd = fd;
epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
close(fd);
free(si);
}
else
{
printf(" Yeah! Recv: %s, %d Bytes\n", buffer, ret);
memcpy(si->recvbuffer, buffer, ret);
//不出意外地话,最后把事情都做完了
//更新自己在 epoll 大家族的信息
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
//ev.data.fd = clientfd;
si->rlength = ret;
si->sockfd = fd;
si->callback = send_cb;
ev.data.ptr = si;
//和 大管家报告,说自己要修改个人信息
epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);
}
}
int accept_cb(int fd, int events, void *arg)
{
//这里是 第一个进 epoll大家庭的用户, 他比较职责比较特殊,算是大管家的小助手,职责是接待新用户
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
//以上是给新用户腾出空间来,登记好信息;并且分给他一个 id好,这个是在这个地方识别新用户的唯一标志
int clientfd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
if (clientfd <= 0)
return -1;
//
char str[INET_ADDRSTRLEN] = {0};
printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
ntohs(client_addr.sin_port));
//然后准备加入 epoll 大家庭, 流程和的第一个加入的一样
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
//ev.data.fd = clientfd;
struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem));
si->sockfd = clientfd;
//这里不同的是, 事件发生时,其对应的解决方案不同(就是其回调函数不同)
si->callback = recv_cb;
si->stauts = WS_HANDSHAKE;
ev.data.ptr = si;
//信息准备好好,最后跟大管家说,有新成员要加入了
epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev);
return clientfd;
}
int main(int argc, char *argv[])
{
if (argc < 2)
{
return -1;
}
int port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
//给socket 绑定一个身份
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
{
return -2;
}
//最大连接数
if (listen(sockfd, 5) < 0)
{
return -3;
}
// struct reactor
// {
// int epfd;
// struct epoll_event events[512];
// };
// reactor 分配空间, eventloop 是最大的管家
eventloop = (struct reactor *)malloc(sizeof(struct reactor));
// epoll opera
// 给epoll->epfd 分配一个权限,可以管理 io
eventloop->epfd = epoll_create(1);
//每个io(文件描述符都应该都身份 ,才能进入 epoll 里,这样 管家 eventloop->epfd 才好方便管理)
//结构体 epoll_event 包含了io的身份
struct epoll_event ev;
// 事件类型
ev.events = EPOLLIN;
//ev.data.fd = sockfd; //int idx = 2000;
//这里为了方便,在引用一个结构体, 详细记录每个io 信息, ev 结构体里有个空指针, 这也是在引用一个结构体的原因
struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem));
//结构体 sockitem 记录该io 状态,是什么类型的io 以及发生了什么事件调用哪个函数
si->sockfd = sockfd;
si->stauts = WS_INIt;
si->callback = accept_cb;
// ev里面的空指针指向 si空间,这一个ev 就可以包含很多信息了,用的时候只需要 强转一下就行了
ev.data.ptr = si;
// 将新的 io 带给 管家 eventloop->epfd ,告诉管家怎么出来 这里是 EPOLL_CTL_ADD 意为加入epoll 大家庭
// 告诉自己是id,以及 其身份信息 ev
epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);
//最先加入的是 监听是否有 新用户接入socket,其职责是 有用户来了,告诉大管家,然后把用户拉进 epoll 大家庭
//pthread_cond_waittime();
while (1)
{
//这里是大管家 的职责,在 epoll 大家庭了谁有事了,就把他拉倒一个小房间里,统计有多少个人有事,然后再来统一处理 nready 为有事io的数量
int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1);
if (nready < -1)
{
break;
}
int i = 0;
//拉倒一个房间后再逐个处理
for (i = 0; i < nready; i++)
{
//事件驱动
//根据每个io的事件的类型来处理,提高效率 (这里的事件类型 为可读可写)
if (eventloop->events[i].events & EPOLLIN)
{
//printf("sockitem\n");
//每个用户进来之前都登记好了个人信息,以及我出事了,该怎么做(对应的处理函数),所以管家也不必着急
//先抽出 用户的信息,在来看 用户他自己写好的处理方案(这里是函数回调)
struct sockitem *si = (struct sockitem *)eventloop->events[i].data.ptr;
si->callback(si->sockfd, eventloop->events[i].events, si);
}
//同上,这里只是不同的事件类型
if (eventloop->events[i].events & EPOLLOUT)
{
struct sockitem *si = (struct sockitem *)eventloop->events[i].data.ptr;
si->callback(si->sockfd, eventloop->events[i].events, si);
}
}
}
}