96SEO 2026-02-23 14:50 17
找工作忙里偷闲简单研究了一下epoll的源码。

也是由于面试的时候经常被问到我只会说那一套什么epoll_create创建红黑树以O(1)的方式去读取数据它和poll与select的区别等等。
本篇将从epoll的源码层面重新学习epoll。
多路转接(epoll)实现我在这篇文章中实现了一个简单的epoll网络server。
感兴趣的同学可以简单阅读一下我只挑其中关键的代码来讲一下应用层是如何使用epoll的
{if(argc!2){Usage(argv[0]);exit(-1);}uint16_t
port(uint16_t)atoi(argv[1]);int
listen_sockSock::Socket();Sock::Bind(listen_sock,port);Sock::Listen(listen_sock);//建立epoll模型获得epfdint
epfdepoll_create(128);//先添加listen_sock和它所关心的事件到内核中struct
ev;ev.eventsEPOLLIN;ev.data.fdlisten_sock;//虽然epoll_ctl有文件描述符但是revs数组中的元素是epoll_event没有fd因此需要将fd添加都epoll_event的data字段中epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,ev);//事件循环volatile
revs[NUM];//由于epoll_wait的数组是输出型参数因此需要接收while(!quit){int
nepoll_wait(epfd,revs,NUM,-1);//epoll_wait会将epfd中就绪事件的epoll_event结构体放在revs数组中返回值表示数组大小switch(n){case
0:couttimeout.....endl;break;case
errorendl;break;default:cout有事件就绪了endl;//处理就绪事件for(int
sockrevs[i].data.fd;//暂时方案cout文件描述符sock有数据就绪了endl;if(revs[i].eventsEPOLLIN)//读事件就绪{cout文件描述符sock读事件就绪了endl;if(socklisten_sock){int
fdSock::Accept(listen_sock);if(fd0){cout获取新链接成功了endl;//此时还不能读需要添加到epfd的空间中struct
_ev;_ev.eventsEPOLLIN;_ev.data.fdfd;epoll_ctl(epfd,EPOLL_CTL_ADD,fd,_ev);cout已经把fd添加到epfd空间中了endl;}}//正常的读处理else{cout文件描述符sock正常数据准备就绪endl;char
sread(sock,buffer,sizeof(buffer)-1);if(s0){buffer[s]0;coutclient[sock]#bufferendl;}else
sock将被关闭endl;close(sock);epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr);//将该套接字从epoll空间关注的位置删除coutSock:sockdelete
errorendl;close(sock);epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr);coutdelete
}这是我那篇博客的服务器端的代码使用telnet是可以直接访问的通过这段代码我们可以发现调用epoll的过程以及一些细节。
首先就是众所周知的
epoll_create创建一个epoll空间。
接着调用epoll_ctl将一个文件描述符以及对该文件描述符需要关心的事件放进epoll空间中。
然后调用epoll_wait进行等待就好了。
事件就绪会使用epoll_wait这个函数来通知我们。
但仔细看代码还是会发现一些细节在epoll空间建立完成后添加的第一个文件描述符就是listen_sock并且关心它的读事件。
在epoll_wait成功的时候会返回一个就绪事件的数组通过遍历这个数组去对数据进行操作但当该数组元素表示的是listen_sock事件就绪的时候需要在listen_sock中将新监听到的链接accept上来。
这是对监听套接字独有的一种处理方式也说明epoll的回调函数不止一个触发条件(数据就绪orl链接就绪)。
其中这里还涉及到了一个重要的结构体epoll_event这个结构体在内核源码中也经常使用到下面给出它在linux系统中的结构
__EPOLL_PACKED;其中它的events表示的是事件data又是一个结构体它其中的一个重要的东西就是fd。
它表示该epoll_event是哪个套接字的。
其中一个结构名为eventpoll当调用epoll_create的时候内核会自动创建它所以其实所谓的epoll空间仅仅是一个结构体而已。
//红黑树中节点的数量也就是添加了多少个TCP连接事件LIST_HEAD(
};它包含了几个内容有我们熟知的红黑树根节点还有所谓的就绪队列(是一个双链表)以及红黑树与就绪队列的节点数量。
这个是比较关键的一个结构体epoll_ctl将文件描述符以及所关心的事件插入到epoll空间中插入的就是这么个玩意。
};首先它包含了一个红黑树节点的结构其次他又包含了双向链表的结构到这里我们就可以发现红黑树和双向链表中插入的是同一个结构体epitem。
也很容易想到epoll_ctl的本质其实就是使用传入的参数来构造一个epitem。
因此它包含的参数显而易见sockfd表示该epitem对应的套接字epoll_event表示需要关心的该套接字的事件。
-1;//tcp服务我也不懂目前不是重点nty_tcp_manager
nty_socket_allocate(NTY_TCP_SOCK_EPOLL);if
-1;}//2.初始化红黑树指针为空节点数为0ep-rbcnt
0;RB_INIT(ep-rbr);//3.双向链表头指向空LIST_INIT(ep-rdlist);//线程操作暂时不用关心if
{free(ep);nty_free_socket(epsocket-id,
{pthread_mutex_destroy(ep-mtx);free(ep);nty_free_socket(epsocket-id,
{pthread_mutex_destroy(ep-cdmtx);pthread_mutex_destroy(ep-mtx);free(ep);nty_free_socket(epsocket-id,
{pthread_cond_destroy(ep-cond);pthread_mutex_destroy(ep-cdmtx);pthread_mutex_destroy(ep-mtx);free(ep);nty_free_socket(epsocket-id,
-2;}//4.保存ep对象通过epid可以在系统中找到eventpoll结构tcp-ep
}我们发现在epoll_create的时候会传入一个参数但是这个参数似乎没有意义从这里就可以很简单的看出了这个size除了判断一下是不是大于0之外之后什么都没做。
因此这个参数是没有意义的。
1.建立一个eventpoll对象并为其分配空间。
2.将该对象中红黑树根节点指向空节点个数设为0。
3.将该对象中双向链表头节点指向空。
4.将eventpoll对象保存起来。
以后可以通过epid找到它并返回这个epid。
下面来看看epoll_ctl都干了些什么在讲解这个函数之前我们还是先回头看一下我们是怎么向它传参的
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,ev);这是我们将listen_sock加入到epoll空间中的代码可以看到epfd用于寻找eventpoll结构体EPOLL_CTL_ADD表示我是要加入一个epoll节点listen_sock表示我加入的这个节点的文件描述符是listen_sockev表示我关心的事件(使用地址传参为了节省空间)这一切是不是很明确了呢?下面我们来阅读这部分的源码。
sockid);//1.通过传入进来的epid找到对应的eventpoll结构体struct
tcp-fdtable-sockfds[epid];//struct
tcp-fdtable-sockfds[sockid];//nty_trace_epoll(
{pthread_mutex_lock(ep-mtx);//在栈区先创建一个对象接收sockidstruct
sockid;//查找这个节点是否在红黑树中其实也就是根据epollfd查找的这查找传入tmp可能表示的是红黑树节点类型这是一个小细节struct
exist\n);pthread_mutex_unlock(ep-mtx);return
{pthread_mutex_unlock(ep-mtx);errno
-1;}//使用参数构建这个epitem节点epi-sockfd
;pthread_mutex_unlock(ep-mtx);}
{pthread_mutex_lock(ep-mtx);struct
sockid;//查找该节点是否已经存在这也说明是根据sockfd查找的struct
exist\n);pthread_mutex_unlock(ep-mtx);return
exist\n);pthread_mutex_unlock(ep-mtx);return
--;free(epi);pthread_mutex_unlock(ep-mtx);}
tmp);//找到了就将该节点的事件进行修改这里还有一个细节修改之后会默认加入EPOLLERR
EPOLLHUP这两个事件监听一个表示套接字发生错误一个表示套接字被挂断。
if
}每根据一个不同的选择就创建一个结构并查找它是不是在红黑树中可能有点冗余感觉直接在上面创建一个即可。
根据传入的epfd找到对应的eventpoll结构。
根据不同的选择类型进行if-else判断
1.首先所有操作都要先在栈区建立一个epitem的结构然后将sock传入其中。
并使用红黑树查找函数传入该结构查找。
以插入为例没有则在堆区构建结构体对象然后向红黑树中插入使用memcpy来加入事件。
都是先建立一个栈区对象然后红黑树中进行查找的这里可以优化一下。
这个函数就是用于等待事件就绪然后将他插入就绪队列中的其中这里的epoll_event是一个输出型参数它通常表示一个数组的首地址。
这里可以再回顾一下它是怎么进行传参的
nepoll_wait(epfd,revs,NUM,-1);其中epfd显然还是去找eventpoll的revs是一个数组首元素地址(我们建立一个数组传入数组名其实就可以了)NUM是一个整数表示多少个套接字就绪了就可以返回了-1表示的是只要没有文件描述符就绪就永久阻塞。
值得注意的是这里我们没有回调函数的实现也就是说暂时没有套接字就绪了将它插入等待队列中的操作它的实现在后面讲。
{//tcp相关可恶我写完这篇文章一定看看这是个啥东西nty_tcp_manager
tcp-smap[epid];//找到eventpollstruct
//如果rdnum为空并且等待时间不为0的时候会等待一段时间while
deadline;clock_gettime(CLOCK_REALTIME,
{deadline.tv_sec;deadline.tv_nsec
pthread_cond_timedwait(ep-cond,
{nty_trace_epoll(pthread_cond_timewait\n);pthread_mutex_unlock(ep-cdmtx);return
{nty_trace_epoll(pthread_cond_wait\n);pthread_mutex_unlock(ep-cdmtx);return
}pthread_mutex_unlock(ep-cdmtx);pthread_spin_lock(ep-lock);int
//EPOLLET//每次从双向链表的头节点取得一个一个的节点struct
LIST_FIRST(ep-rdlist);//把这个节点从双向链表中删除LIST_REMOVE(epi,
rdlink);//标记这个节点不在双向链表中但是在红黑树中epi-rdy
0;//只有当该节点再次被放入双向链表中的时候才会置为1//把标记的信息拷贝出来拷贝到提供的events参数中memcpy(events[i],
--;}pthread_spin_unlock(ep-lock);return
1.客户端connect()连入服务器处于SYN_RCVD状态时
3.客户端close()断开连接服务器处于FIN_WAIT_1和FIN_WAIT_2状态时
它的作用是向双向链表中添加一个红黑树的节点。
它的参数有一个eventpoll类型注意这里没有传epid有一个sockid有一个event。
sockid;//首先根据sockid找到红黑树中的节点struct
exist\n);assert(0);}//根据epi-rdy判断该节点是否在双向链表里if
{//如果在就将事件填入events中epi-event.events
//如果不在要做的就是将这个节点添加到双向链表中的表头位置nty_trace_epoll(epoll_event_callback
epi-sockfd);pthread_spin_lock(ep-lock);epi-rdy
;pthread_spin_unlock(ep-lock);pthread_mutex_lock(ep-cdmtx);pthread_cond_signal(ep-cond);pthread_mutex_unlock(ep-cdmtx);return
要讲明白水平触发和边缘触发就需要知道都有哪些状态会触发无非也就这四种
可读socket上有数据不可读socket上没有数据了可写socket上有空间可写不可写socket上无空间可写
对于水平触发模式一个事件只要有就会一直被触发。
对于边缘触发模式一个事件只要从无到有就会被触发。
写只要发送缓冲区上还有空间就会一直被触发。
如果程序依赖于可写事件触发去发送数据要移除可写事件避免无意义的触发。
在LT模式下读事件触发后可以按需收取想要的字节数不用把本次接收到的数据收取干净。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback