`
yonlist
  • 浏览: 85713 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Linux的epoll模型

阅读更多

Linux的epoll模型
Linux 2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。

1、为什么select落后
首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,搜索内核源代码得到:
include/linux/posix_types.h:#define __FD_SETSIZE         1024
也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。其次,内核中实 现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系, 即select要检测的句柄数越多就会越费时。当然,在前文中我并没有提及poll方法,事实上用select的朋友一定也试过poll,我个人觉得 select和poll大同小异,个人偏好于用select而已。


2、内核中提高I/O性能的新方法epoll
epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。
当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

Linux2.6内核epoll介绍
先介绍2本书《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4内核讲解Linux TCP/IP实现,相当不错.作为一个现实世界中的实现,很多时候你必须作很多权衡,这时候参考一个久经考验的系统更有实际意义。举个例子,linux内 核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多大,sk_buff最小也有272的字节.其实 对于socket应用层程序来说,另外一本书《UNIX Network Programming Volume 1》意义更大一点.2003年的时候,这本书出了最新的第3版本,不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方 案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了,最后的结果是IO有个线程池,你应用也需要一个线程池...... 很多文档其实已经指出了这带来的线程context-switch带来的代价。在linux 平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布,网络 模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多 了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当帮助)。
所以,剩下的select模型基本上就是我们在linux上面的唯一选择,其实,如果加上no-block socket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺 点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtime signal等等。最终,Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案

3、epoll的优点

<1>支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
<2>IO效率不随FD数目增加而线性下降
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的, 但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行 操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。
<3>使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就 很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。
<4>内核微调
这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。 比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小 --- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。


4、epoll的工作模式
令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致 了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用,具体用法请参考
http://www.xmailserver.org/linux-patches/nio-improve.html ,在http://www.kegel.com/rn/也有一个完整的例子,大家一看就知道如何使用了
Leader/follower模式线程pool实现,以及和epoll的配合。

5、 epoll的使用方法
    首先通过create_epoll(int maxfds)来创建一个epoll的句柄,其中maxfds为你epoll所支持的最大句柄数。这个函数会返回一个新的epoll句柄,之后的所有操作 将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。 之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为: 
nfds = epoll_wait(kdpfd, events, maxevents, -1); 
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成 功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没 有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环 的效率。

epoll_wait范围之后应该是一个循环,遍利所有的事件: 
for(n = 0; n < nfds; ++n) { 
                if(events[n].data.fd == listener) { //如果是主socket的事件的话,则表示有新连接进入了,进行新连接的处理。 
                    client = accept(listener, (struct sockaddr *) &local, 
                                    &addrlen); 
                    if(client < 0){ 
                        perror("accept"); 
                        continue; 
                    } 
                    setnonblocking(client); // 将新连接置于非阻塞模式 
                    ev.events = EPOLLIN | EPOLLET; // 并且将新连接也加入EPOLL的监听队列。 
注意,这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET 
                    ev.data.fd = client; 
                    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { 
// 设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,这里用EPOLL_CTL_ADD来加一个新的 epoll事件,通过EPOLL_CTL_DEL来减少一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。 
                        fprintf(stderr, "epoll set insertion error: fd=%d0, 
                                client); 
                        return -1; 
                    } 
                } 
                else // 如果不是主socket的事件的话,则代表是一个用户socket的事件,则来处理这个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理。
                    do_use_fd(events[n].data.fd); 
}

对,epoll的操作就这么简单,总共不过4个API:epoll_create, epoll_ctl, epoll_wait和close。


以前公司的服务器都是使用HTTP连接,但是这样的话,在手机目前的网络情况下不但显得速度较慢,而且不稳定。因此大家一致同意用SOCKET来进行连 接。虽然使用SOCKET之后,对于用户的费用可能会增加(由于是用了CMNET而非CMWAP),但是,秉着用户体验至上的原则,相信大家还是能够接受 的(希望那些玩家月末收到帐单不后能够保持克制...)。
这次的服务器设计中,最重要的一个突破,是使用了EPOLL模型,虽然对之也是一知半解,但是既然在各大PC网游中已经经过了如此严酷的考验,相信他不会让我们失望,使用后的结果,确实也是表现相当不错。在这里,我还是主要大致介绍一下这个模型的结构。


6、Linux下epoll编程实例
EPOLL模型似乎只有一种格式,所以大家只要参考我下面的代码,就能够对EPOLL有所了解了,代码的解释都已经在注释中:

while (TRUE)
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//等待EPOLL时间的发生,相当于监听,至于相关的端口,需要在初始化EPOLL的时候绑定。
if (nfds <= 0)
continue;
m_bOnTimeChecking = FALSE;
G_CurTime = time(NULL);
for (int i=0; i
{
try
{
if (m_events[i].data.fd == m_listen_http_fd)//如果新监测到一个HTTP用户连接到绑定的HTTP端口,建立新的连接。由于我们新采用了SOCKET连接,所以基本没用。
{
OnAcceptHttpEpoll ();
}
else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
{
OnAcceptSockEpoll ();
}
else if (m_events[i].events & EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
OnReadEpoll (i);
}

OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据。
}
catch (int)
{
PRINTF ("CATCH捕获错误\n");
continue;
}
}
m_bOnTimeChecking = TRUE;
OnTimer ();//进行一些定时的操作,主要就是删除一些短线用户等。
}


转载自:http://hi.baidu.com/gongcheng0127/blog/item/7de4ec38b17bd12cb9998f25.html


 

分享到:
评论

相关推荐

    Linux epoll模型详解.xmind

    linux epoll 概念、优缺点、io复用 、脑图、Linux下的服务器模型:

    (二)Linux Epoll模型.mmap

    用思维导图的方式总结了Epoll模型,非常的好理解。文件需要用MindMange打开。

    linux epoll模型

    Linux下的epoll模型是高效处理I/O事件的一种机制,尤其在高并发的网络服务中表现卓越。epoll是由早期的poll和select模型发展而来,它提供了更优秀的性能和可扩展性。在这里,我们有两个示例服务器模型:mychat和...

    Windows完成端口与Linux epoll技术简介.doc

    Windows 完成端口与 Linux epoll 技术是两种常见的高性能 I/O 模型,在 Windows 和 Linux 平台上广泛应用于开发高性能的网络服务程序。下面我们将详细介绍 Windows 完成端口和 Linux epoll 技术的基本概念、特点、...

    linux下Epoll模型实例代码

    这个"linux下Epoll模型实例代码"是一个展示如何在Linux环境下使用Epoll进行I/O事件监控的程序示例。 Epoll的核心概念包括以下几个方面: 1. **Epoll创建**:首先,通过调用`epoll_create()`函数创建一个Epoll实例...

    linux下epoll网络模型介绍

    ### Linux 下 epoll 网络模型介绍 在深入探讨 Linux 下的 epoll 模型之前,我们先了解一下 epoll 的背景以及它为何成为 Linux 内核中 I/O 多路复用的关键技术之一。 #### 1. epoll 的背景与优势 早期 Linux 内核...

    linux epoll多线程编程 例子

    epoll是对传统select和poll模型的改进,它引入了“事件就绪”的概念,并使用了“红黑树”数据结构存储监控的文件描述符(FD),从而在添加、删除和查询FD时具有O(1)的时间复杂度。此外,epoll支持水平触发和边缘触发...

    Windows完成端口与Linuxepoll技术简介.docx

    【Linux epoll模型】 Linux的epoll是针对大量并发连接的一种I/O多路复用技术,自Linux 2.6内核引入,以解决旧有的select和poll方法在处理大量文件描述符时性能下降的问题。 1. 为什么select落后 - select和poll在...

    Linux网络通信epoll模型

    epoll模型作为Linux提供的一种I/O多路复用技术,正是为了解决这一问题而设计的。本文将深入探讨epoll的工作原理、优势以及如何在实际编程中运用。 epoll,全称Event Poll,它是基于文件描述符(file descriptor)的...

    epoll模型的一个例子

    在Linux系统编程中,`epoll`是一种高效、可扩展的I/O多路复用技术,主要用于处理大量的并发连接。`epoll`模型是解决高并发服务器性能瓶颈的有效手段之一,它比传统的`select`和`poll`模型更加先进,能够更有效地管理...

    网络编程epoll模型

    网络编程中的`epoll`模型是Linux操作系统提供的一种高效、高性能的I/O多路复用技术,主要用于管理和调度大量的并发连接。在这个模型中,`epoll`替代了传统的`select`和`poll`,解决了它们在处理大量文件描述符时性能...

    linux_epoll模型

    总的来说,理解并熟练掌握`epoll`模型是Linux系统编程和高性能网络服务开发中的重要技能。通过对`select`和`epoll`的区别进行深入分析,开发者可以更好地优化自己的程序,以适应不断增长的并发连接需求。

    linux epoll socket UDP通信的实现! 看清楚不是tcp哟.zip

    通过`epoll`的事件驱动模型,我们可以优雅地处理大量并发的UDP连接,提高服务器的响应速度和吞吐量。对于需要快速、低延迟且对数据完整性和顺序要求不高的应用,如在线游戏、流媒体服务等,使用`epoll`与`UDP`的组合...

    Epoll模型详解

    ### Epoll模型详解 #### 一、Epoll概述 在Linux网络编程中,早期广泛使用的事件触发机制主要是基于`select`。然而随着技术的发展以及应用需求的提高,`select`逐渐暴露出了一些明显的局限性,比如它对于大量文件...

    linux epoll 例子程序

    在传统的轮询模型中,系统会不断检查每个文件描述符的状态,而epoll则通过事件驱动的方式,只在有实际I/O操作时才进行处理,显著提高了性能。 在"linux epoll 例子程序"中,我们可以看到三个核心文件:`epoll-...

    linux epoll服务器

    总结来说,这个Linux下的epoll TCP服务器示例涵盖了网络编程的基础要素,通过epoll机制实现了高效的事件驱动模型,适合学习和理解I/O多路复用技术在实际应用中的使用。同时,`Client.cpp`文件的存在使我们可以自测...

    Linux下IO模型及epoll介绍

    Linux IO模型 epoll 多进程模型 Socket

    linux epoll 的实现

    Linux下的epoll是一种高效、可扩展的I/O多路复用技术,主要用于处理大量并发连接。它是基于事件驱动的I/O模型,适用于高并发服务器,如Web服务器、数据库服务器等。在epoll中,系统会监控一组文件描述符,当这些文件...

    linux epoll代码

    在Linux系统中,`epoll`是用于I/O多路复用的一种高效机制,尤其适合高并发、大连接数的网络服务。`epoll`在传统的`poll`和`select`基础上进行了优化,解决了它们在处理大量文件描述符时效率低下的问题。本篇文章将...

Global site tag (gtag.js) - Google Analytics