团队做网站分工,网站更改域名没有变更备案,网站开始开发阶段的主要任务,实业 东莞网站建设Linux2.6内核中提高网络I/O性能的新方法-epoll I/O多路复用技术在比较多的TCP网络服务器中有使用#xff0c;即比较多的用到select函数。1、为什么select落后首先#xff0c;在Linux内核中#xff0c;select所用到的FD_SET是有限的#xff0c;即内核中有个参数__FD_SETSIZE…Linux2.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性能的新方法epollepoll是什么按照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/pollrealtime 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察看,一般来说这个数目和系统内存关系很大。2IO效率不随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 | EPOLLETev.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个APIepoll_create, epoll_ctl, epoll_wait和close。如果您对epoll的效率还不太了解请参考我之前关于网络游戏的网络编程等相关的文章。以前公司的服务器都是使用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 i0; infds; 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 ();//进行一些定时的操作主要就是删除一些短线用户等。}其实EPOLL的精华也就是上述的几段短短的代码看来时代真的不同了以前如何接受大量用户连接的问题现在却被如此轻松的搞定真是让人不得不感叹对哪。Epoll模型主要负责对大量并发用户的请求进行及时处理完成服务器与客户端的数据交互。其具体的实现步骤如下(a) 使用epoll_create()函数创建文件描述设定将可管理的最大socket描述符数目。(b) 创建与epoll关联的接收线程应用程序可以创建多个接收线程来处理epoll上的读通知事件线程的数量依赖于程序的具体需要。(c) 创建一个侦听socket描述符ListenSock将该描述符设定为非阻塞模式调用Listen函数在套接字上侦听有无新的连接请求在epoll_event结构中设置要处理的事件类型EPOLLIN工作方式为 epoll_ET以提高工作效率同时使用epoll_ctl()注册事件最后启动网络监视线程。(d) 网络监视线程启动循环epoll_wait()等待epoll事件发生。(e) 如果epoll事件表明有新的连接请求则调用accept函数将用户socket描述符添加到epoll_data联合体同时设定该描述符为非阻塞并在epoll_event结构中设置要处理的事件类型为读和写工作方式为epoll_ET.(f) 如果epoll事件表明socket描述符上有数据可读则将该socket描述符加入可读队列通知接收线程读入数据并将接收到的数据放入到接收数据的链表中经逻辑处理后将反馈的数据包放入到发送数据链表中等待由发送线程发送。