??xml version="1.0" encoding="utf-8" standalone="yes"?>
1、基本概?br>2、WINDOWS完成端口的特?br>3、完成端口(Completion Ports Q相x据结构和创徏
4、完成端口线E的工作原理
5、Windows完成端口的实例代?br>Linux的EPoll模型
1、ؓ什么select落后
2、内怸提高I/O性能的新Ҏepoll
3、epoll的优?br>4、epoll的工作模?
5、epoll的用方?br>6、Linux下EPOll~程实例
ȝ
WINDOWS完成端口~程
摘要Q开发网l程序从来都不是一件容易的事情Q尽只需要遵守很的一些规?创徏socket,发vq接Q接受连接,发送和接受数据。真正的困难在于Q?
让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windowsq_完成端口q行重叠I/O的技术和Linux?.6版本的内怸
引入的EPOll技术,可以很方便在在在Windows和Linuxq_上开发出支持大量q接的网l服务程序。本文介l在Windows和Linuxq_
上用的完成端口和EPoll模型开发的基本原理Q同时给出实际的例子。本文主要关注C/Sl构的服务器端程序,因ؓ一般来_开发一个大定wQ具可扩?
性的winsockE序一般就是指服务E序?br>
1、基本概?br>
讑֤---windows操作pȝ上允讔R信的Q何东西,比如文g、目录、串行口、ƈ行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑盘、物?
盘{。绝大多C讑֤打交道的函数都是CreateFile/ReadFile/WriteFile{。所以我们不能看?*File函数只惛_文g
讑֤。与讑֤通信有两U方式,同步方式和异步方式。同步方式下Q当调用ReadFile函数Ӟ函数会等待系l执行完所要求的工作,然后才返回;异步方式
下,ReadFileq类函数会直接返回,pȝ自己d成对讑֤的操作,然后以某U方式通知完成操作?br>重叠I/O----思义Q当你调用了?
个函敎ͼ比如ReadFileQ就立刻q回做自q其他动作的时候,同时pȝ也在对I/0讑֤q行你要求的操作Q在q段旉内你的程序和pȝ的内部动作是
重叠的,因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O讑֤的?重叠I/O需要用的一个非帔R要的数据l构OVERLAPPED?br>
2、WINDOWS完成端口的特?br>
Win32重叠I/O(Overlapped
I/O)机制允许发v一个操作,然后在操作完成之后接受到信息。对于那U需要很长时间才能完成的操作来说Q重叠IO机制其有用Q因为发起重叠操作的U程
在重叠请求发出后可以自q做别的事情了。在WinNT和Win2000上,提供的真正的可扩展的I/O模型是使用完成端口QCompletion
PortQ的重叠I/O.完成端口---是一UWINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口?
可,q有讑֤内核对象、事件对象、告警I/0{。但是完成端口内部提供了U程池的理Q可以避免反复创建线E的开销Q同时可以根据CPU的个数灵zȝ军_
U程个数Q而且可以让减线E调度的ơ数从而提高性能其实cM于WSAAsyncSelect和select函数的机制更Ҏ兼容UnixQ但是难以实?
我们惌?#8220;扩展?#8221;。而且windows的完成端口机制在操作pȝ内部已经作了优化Q提供了更高的效率。所以,我们选择完成端口开始我们的服务器程序的
开发?br>1、发h作不一定完成,pȝ会在完成的时候通知你,通过用户在完成端口上的等待,处理操作的结果。所以要有检查完成端口,取操作结果的U?
E。在完成端口上守候的U程pȝ有优化,除非在执行的U程dQ不会有新的U程被激z,以此来减线E切换造成的性能代h。所以如果程序中没有太多的阻?
操作Q没有必要启动太多的U程QCPU数量的两倍,一般这h启动U程?br>2、操作与相关数据的绑定方式:在提交数据的时候用户对数据打相应的标记Q记录操作的cdQ在用户处理操作l果的时候,通过查自己打的标记和pȝ的操作结果进行相应的处理?
3?
操作q回的方?一般操作完成后要通知E序q行后箋处理。但写操作可以不通知用户Q此时如果用户写操作不能马上完成Q写操作的相x据会被暂存到到非交换
~冲ZQ在操作完成的时候,pȝ会自动释攄冲区。此时发起完写操作,使用的内存就可以释放了。此时如果占用非交换~冲太多会ɾpȝ停止响应?br>
3、完成端口(Completion Ports Q相x据结构和创徏
其实可以把完成端口看成系l维护的一个队列,操作pȝ把重叠IO操作完成的事仉知攑ֈ该队列里Q由于是暴露
“操作完成”的事仉知Q所以命名ؓ“完成端口”QCOmpletion
PortsQ。一个socket被创建后Q可以在M时刻和一个完成端口联pv来?br>完成端口相关最重要的是OVERLAPPED数据l构
typedef struct _OVERLAPPED {
ULONG_PTR Internal;//被系l内部赋|用来表示pȝ状?
ULONG_PTR InternalHigh;// 被系l内部赋|传输的字节数
union {
struct {
DWORD Offset;//和OffsetHigh合成一?4位的整数Q用来表CZ文g头部的多字节开?
DWORD OffsetHigh;//操作Q如果不是对文gI/O来操作,则必设定ؓ0
};
PVOID Pointer;
};
HANDLE hEvent;//如果不用,务必设?,否则误一个有效的Event句柄
} OVERLAPPED, *LPOVERLAPPED;
下面是异步方式用ReadFile的一个例?
OVERLAPPED Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;
//假定其他参数都已l被初始?
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
q样完成了异步方式L件的操作Q然后ReadFile函数q回Q由操作pȝ做自q事情Q下面介l几个与OVERLAPPEDl构相关的函?
{待重叠I/0操作完成的函?
BOOL GetOverlappedResult (
HANDLE hFile,
LPOVERLAPPED lpOverlapped,//接受q回的重叠I/0l构
LPDWORD lpcbTransfer,//成功传输了多字节数
BOOL fWait //TRUE只有当操作完成才q回QFALSE直接q回Q如果操作没有完成,通过?/用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE
);
宏HasOverlappedIoCompleted可以帮助我们试重叠I/0操作是否完成Q该宏对OVERLAPPEDl构的Internal成员q行了测试,查看是否{于STATUS_PENDING倹{?/p>
一般来_一个应用程序可以创建多个工作线E来处理完成端口上的通知事g。工作线E的数量依赖于程序的具体需要。但是在理想的情况下Q应该对应一个CPU
创徏一个线E。因为在完成端口理想模型中,每个U程都可以从pȝ获得一?#8220;原子”性的旉片,轮番q行q检查完成端口,U程的切换是额外的开销。在实际开
发的时候,q要考虑q些U程是否牉|到其他堵塞操作的情况。如果某U程q行堵塞操作Q系l则其挂vQ让别的U程获得q行旉。因此,如果有这L情况Q?
可以多创建几个线E来量利用旉?br>应用完成端口Q?br> 创徏完成端口Q完成端口是一个内核对象,使用时他L要和臛_一个有效的讑֤句柄q行兌Q完成端口是一个复杂的内核对象Q创建它的函数是Q?br>HANDLE CreateIoCompletionPort(
IN HANDLE FileHandle,
IN HANDLE ExistingCompletionPort,
IN ULONG_PTR CompletionKey,
IN DWORD NumberOfConcurrentThreads
);
通常创徏工作分两步:
W一步,创徏一个新的完成端口内核对象,可以使用下面的函敎ͼ
HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
{
return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
};
W二步,刚创徏的完成端口和一个有效的讑֤句柄兌hQ可以用下面的函数Q?br> bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
{
HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
return h==hCompPort;
};
说明
1Q?CreateIoCompletionPort函数也可以一ơ性的既创建完成端口对象,又关联到一个有效的讑֤句柄
2Q?CompletionKey是一个可以自己定义的参数Q我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用Q最好要保证l构里面的内存不是分配在栈上Q除非你有十分的把握内存会保留到你要使用的那一刅R?br>3Q?
NumberOfConcurrentThreads通常用来指定要允许同时运行的的线E的最大个数。通常我们指定?Q这Ll会ҎCPU的个数来?
动确定。创建和兌的动作完成后Q系l会完成端口关联的讑֤句柄、完成键作ؓ一条纪录加入到q个完成端口的设备列表中。如果你有多个完成端口,׃有多
个对应的讑֤列表。如果设备句柄被关闭Q则表中自动删除该纪录?br>
4、完成端口线E的工作原理
完成端口可以帮助我们理U程池,但是U程池中的线E需要我们用_beginthreadex来创建,凭什么通知完成端口理我们的新U程呢?{案在函数GetQueuedCompletionStatus。该函数原型Q?
BOOL GetQueuedCompletionStatus(
IN HANDLE CompletionPort,
OUT LPDWORD lpNumberOfBytesTransferred,
OUT PULONG_PTR lpCompletionKey,
OUT LPOVERLAPPED *lpOverlapped,
IN DWORD dwMilliseconds
);
q?
个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的U程被攑օ到完成端
口的{待U程队列中,因此完成端口可以在自己的线E池中帮助我们维护这个线E。完成端口的I/0完成队列中存放了当重叠I/0完成的结?---
一条纪录,该纪录拥有四个字D,前三就对应GetQueuedCompletionStatus函数???参数Q最后一个字D|错误信息
dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作?
当I/0完成队列?
出现了纪录,完成端口会查等待线E队列,该队列中的线E都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等?
U程队列很简单,只是保存了这些线E的ID。完成端口会按照后进先出的原则将一个线E队列的ID攑օ到释攄E列表中Q同时该U程从{待
GetQueuedCompletionStatus函数q回的睡眠状态中变ؓ可调度状态等待CPU的调度。所以我们的U程要想成ؓ完成端口理的线E,
必要调用GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还l护了一个暂停线E列表,具体l节可以参?
《Windows高~程指南》,我们现在知道的知识,已经_了?
完成端口U程间数据传递线E间传递数据最常用的办法是在_beginthreadex函数中将参数传递给U程函数Q或者用全局变量。但是完成端口还有自
q传递数据的ҎQ答案就在于CompletionKey和OVERLAPPED参数?br>CompletionKey被保存在完成端口的设备表
中,是和讑֤句柄一一对应的,我们可以与讑֤句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,q?
样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和讑֤句柄的时候做Q因此不能在以后动态改变?br>OVERLAPPED参数是在每次
调用ReadFileq样的支持重叠I/0的函数时传递给完成端口的。我们可以看刎ͼ如果我们不是Ҏ件设备做操作Q该l构的成员变量就Ҏ们几乎毫无作
用。我们需要附加信息,可以创徏自己的结构,然后OVERLAPPEDl构变量作ؓ我们l构变量的第一个成员,然后传递第一个成员变量的地址l?
ReadFile函数。因为类型匹配,当然可以通过~译。当GetQueuedCompletionStatus函数q回Ӟ我们可以获取到第一个成员变
量的地址Q然后一个简单的强制转换Q我们就可以把它当作完整的自定义l构的指针用,q样可以传递很多附加的数据了。太好了Q只有一点要注意Q如果跨U?
E传递,h意将数据分配到堆上,q且接收端应该将数据用完后释放。我们通常需要将ReadFileq样的异步函数的所需要的~冲区放到我们自定义的结?
中,q样当GetQueuedCompletionStatus被返回时Q我们的自定义结构的~冲区变量中存放了I/0操作的数据?
CompletionKey和OVERLAPPED参数Q都可以通过GetQueuedCompletionStatus函数获得?br>U程的安全退?br> 很多U程Z不止一ơ的执行异步数据处理Q需要用如下语?br>while (true)
{
......
GetQueuedCompletionStatus(...);
......
}
那么如何退出呢Q答案就在于上面曾提到的PostQueudCompletionStatus函数Q我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址Q里面包含一个状态变量,当状态变量ؓ退出标志时Q线E就执行清除动作然后退出?br>
5、Windows完成端口的实例代码:
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
ULONG_PTR *PerHandleKey;
OVERLAPPED *Overlap;
OVERLAPPEDPLUS *OverlapPlus,
*newolp;
DWORD dwBytesXfered;
while (1)
{
ret = GetQueuedCompletionStatus(
hIocp,
&dwBytesXfered,
(PULONG_PTR)&PerHandleKey,
&Overlap,
INFINITE);
if (ret == 0)
{
// Operation failed
continue;
}
OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);
switch (OverlapPlus->OpCode)
{
case OP_ACCEPT:
// Client socket is contained in OverlapPlus.sclient
// Add client to completion port
CreateIoCompletionPort(
(HANDLE)OverlapPlus->sclient,
hIocp,
(ULONG_PTR)0,
0);
// Need a new OVERLAPPEDPLUS structure
// for the newly accepted socket. Perhaps
// keep a look aside list of free structures.
newolp = AllocateOverlappedPlus();
if (!newolp)
{
// Error
}
newolp->s = OverlapPlus->sclient;
newolp->OpCode = OP_READ;
// This function divpares the data to be sent
PrepareSendBuffer(&newolp->wbuf);
ret = WSASend(
newolp->s,
&newolp->wbuf,
1,
&newolp->dwBytes,
0,
&newolp.ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
// Put structure in look aside list for later use
FreeOverlappedPlus(OverlapPlus);
// Signal accept thread to issue another AcceptEx
SetEvent(hAcceptThread);
break;
case OP_READ:
// Process the data read
// Repost the read if necessary, reusing the same
// receive buffer as before
memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED));
ret = WSARecv(
OverlapPlus->s,
&OverlapPlus->wbuf,
1,
&OverlapPlus->dwBytes,
&OverlapPlus->dwFlags,
&OverlapPlus->ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
break;
case OP_WRITE:
// Process the data sent, etc.
break;
} // switch
} // while
} // WorkerThread
查看以上代码Q注意如果Overlapped操作立刻p|Q比如,q回SOCKET_ERROR或其他非WSA_IO_PENDING的错误)Q则 没有M完成通知旉会被攑ֈ完成端口队列里。反之,则一定有相应的通知旉被放到完成端口队列。更完善的关于Winsock的完成端口机Ӟ可以参? MSDN的Microsoft PlatFormSDKQ那里有完成端口的例子。访?a >http://msdn.microsoft.com/library/techart/msdn_servrapp.htm可以获得更多信息?/p>
Linux的EPoll模型
Linux 2.6内核中提高网lI/O性能的新Ҏ-epoll I/O多\复用技术在比较多的TCP|络服务器中有用,x较多的用到select函数?br>
1、ؓ什么select落后
首先Q在Linux内核中,select所用到的FD_SET是有限的Q即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个敎ͼ在我用的2.6.15-25-386内核中,该值是1024Q搜索内核源代码得到Q?br>include/linux/posix_types.h:#define __FD_SETSIZE 1024
?
是_如果惌同时?025个句柄的可读状态是不可能用select实现的。或者同时检?025个句柄的可写状态也是不可能的。其ơ,内核中实?
select是用轮询ҎQ即每次都会遍历所有FD_SET中的句柄Q显Ӟselect函数执行旉与FD_SET中的句柄个数有一个比例关p,?
select要检的句柄数越多就会越Ҏ。当Ӟ在前文中我ƈ没有提及pollҎQ事实上用select的朋友一定也试过pollQ我个h觉得
select和poll大同异Q个人偏好于用select而已?/p>
2、内怸提高I/O性能的新Ҏepoll
epoll是什么?按照man手册的说法:是ؓ处理大批量句柄而作了改q的poll。要使用epoll只需要这三个pȝ调用Qepoll_create(2)Q?epoll_ctl(2)Q?epoll_wait(2)?br>当然Q这不是2.6内核才有的,它是?.5.44内核中被引进?epoll(4) is a new API introduced in Linux kernel 2.5.44)
Linux2.6内核epoll介绍 epoll_wait范围之后应该是一个@环,遍利所有的事gQ? 对,epoll的操作就q么单,d不过4个APIQepoll_create, epoll_ctl, epoll_wait和close? while (TRUE) OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据?br>}
先介l?本书《The Linux Networking
Architecture--Design and Implementation of Network Protocols in the
Linux Kernel》,?.4内核讲解Linux
TCP/IP实现Q相当不?作ؓ一个现实世界中的实玎ͼ很多时候你必须作很多权衡,q时候参考一个久l考验的系l更有实际意义。D个例?linux?
怸sk_buffl构Zq求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多?sk_buff最也?72的字?其实
对于socket应用层程序来_另外一本书《UNIX Network Programming Volume
1》意义更大一?2003q的时候,q本书出了最新的W?版本Q不q主要还是修订第2版本。其中第6章《I/O
Multiplexing》是最重要的。Stevensl出了网lIO的基本模型。在q里最重要的莫q于select模型和Asynchronous
I/O模型.从理Z_AIOg是最高效的,你的IO操作可以立即q回Q然后等待os告诉你IO操作完成。但是一直以来,如何实现没有一个完的?
案。最著名的windows完成端口实现的AIO,实际上也是内部用U程池实现的|了Q最后的l果是IO有个U程池,你应用也需要一个线E池......
很多文档其实已经指出了这带来的线Econtext-switch带来的代仗在linux
q_上,关于|络AIO一直是改动最多的地方Q?.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布Q网l?
模块的AIO一直没有进入稳定内核版?大部分都是用用LE模拟方法,在用了NPTL的linux上面其实和windows的完成端口基本上差不?
??.6内核所支持的AIOҎ盘的AIO---支持io_submit(),io_getevents()以及对Direct
IO的支?是l过VFSpȝbuffer直接写硬盘,对于服务器在内存^Ex上有相当帮??br>所以,剩下的select模型基本上就是我?
在linux上面的唯一选择Q其实,如果加上no-block
socket的配|,可以完成一??AIO的实玎ͼ只不q推动力在于你而不是os而已。不q传l的select/poll函数有着一些无法忍受的~?
点,所以改q一直是2.4-2.5开发版本内核的dQ包?dev/pollQrealtime signal{等。最l,Davide
Libenzi开发的epollq入2.6内核成ؓ正式的解x?br>
3、epoll的优?/strong>
<1>支持一个进E打开大数目的socket描述W?FD)
select
最不能忍受的是一个进E所打开的FD是有一定限制的Q由FD_SETSIZE讄Q默认值是2048。对于那些需要支持的上万q接数目的IM服务器来说显
然太了。这时候你一是可以选择修改q个宏然后重新编译内核,不过资料也同时指样会带来|络效率的下降,二是可以选择多进E的解决Ҏ(传统?
ApacheҎ)Q不q虽然linux上面创徏q程的代h较小Q但仍旧是不可忽视的Q加上进E间数据同步q比不上U程间同步的高效Q所以也不是一U完
的Ҏ。不q?
epoll则没有这个限Ӟ它所支持的FD上限是最大可以打开文g的数目,q个数字一般远大于2048,举个例子,?GB内存的机器上大约?0万左
叻I具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和pȝ内存关系很大?br><2>IO效率不随FD数目增加而线性下?br>?
l的select/poll另一个致命弱点就是当你拥有一个很大的socket集合Q不q由于网lgӞM旉只有部分的socket?z跃"的,?
是select/poll每次调用都会U性扫描全部的集合Q导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"z跃"的socketq行?
?--q是因ؓ在内核实Cepoll是根据每个fd上面的callback函数实现的。那么,只有"z跃"的socket才会d的去调用
callback函数Q其他idle状态socket则不会,在这点上Qepoll实现了一??AIOQ因时候推动力在os内核。在一?
benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境Qepollq不比select/poll有什么效率,?
反,如果q多使用epoll_ctl,效率相比q有E微的下降。但是一旦用idle
connections模拟WAN环境,epoll的效率就q在select/poll之上了?br><3>使用mmap加速内怸用户I间的消息传递?br>q?
点实际上涉及到epoll的具体实C。无论是select,pollq是epoll都需要内核把FD消息通知l用L_如何避免不必要的内存拯很
重要Q在q点上,epoll是通过内核于用L间mmap同一块内存实现的。而如果你x一样从2.5内核关注epoll的话Q一定不会忘记手?
mmapq一步的?br><4>内核微调
q一点其实不epoll的优点了Q而是整个linuxq_的优炏V也怽可以怀?
linuxq_Q但是你无法回避linuxq_赋予你微调内核的能力。比如,内核TCP/IP协议栈用内存池理sk_buffl构Q那么可以在q行?
期动态调整这个内存pool(skb_head_pool)的大?-- 通过echo
XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参?TCP完成3ơ握?
的数据包队列长度)Q也可以Ҏ你^台内存大动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本w大却很小的特D系l上试最新的NAPI|?
卡驱动架构?br>4、epoll的工作模?br>令h高兴的是Q?.6内核的epoll比其2.5开发版本的/dev/epollz了许多Q所以,大部分情况下Q强大的东西往往是简单的。唯一有点ȝ是epoll?U工作方?LT和ET?br>LT(level
triggered)是缺省的工作方式Qƈ且同时支持block和no-block
socket.在这U做法中Q内核告诉你一个文件描q符是否qA了,然后你可以对q个qA的fdq行IO操作。如果你不作M操作Q内核还是会l箋通知?
的,所以,q种模式~程出错误可能性要一炏V传l的select/poll都是q种模型的代表.
ET
(edge-triggered)是高速工作方式,只支持no-block
socket。在q种模式下,当描q符从未qA变ؓqAӞ内核通过epoll告诉你。然后它会假设你知道文g描述W已l就l,q且不会再ؓ那个文g描述
W发送更多的qA通知Q直C做了某些操作D那个文g描述W不再ؓqA状态了(比如Q你在发送,接收或者接收请求,或者发送接收的数据于一定量时导?
了一个EWOULDBLOCK 错误Q。但是请注意Q如果一直不对这个fd作IO操作(从而导致它再次变成未就l?Q内怸会发送更多的通知(only
once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark认?br>epoll只有epoll_create,epoll_ctl,epoll_wait 3个系l调用,具体用法请参?a >http://www.xmailserver.org/linux-patches/nio-improve.html Q在http://www.kegel.com/rn/也有一个完整的例子Q大家一看就知道如何使用?br>Leader/follower模式U程pool实现Q以及和epoll的配合?br>
5?epoll的用方?/strong>
首先通过create_epoll(int
maxfds)来创Z个epoll的句柄,其中maxfdsZepoll所支持的最大句柄数。这个函Cq回一个新的epoll句柄Q之后的所有操?
通过q个句柄来进行操作。在用完之后Q记得用close()来关闭这个创建出来的epoll句柄?
之后在你的网l主循环里面Q每一帧的调用epoll_wait(int epfd, epoll_event events, int max
events, int timeout)来查询所有的|络接口Q看哪一个可以读Q哪一个可以写了。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
?
中kdpfd为用epoll_create创徏之后的句柄,events是一个epoll_event*的指针,当epoll_waitq个函数操作成功
之后Qepoll_events里面储存所有的d事g。max_events是当前需要监听的所有socket句柄数。最后一个timeout?
epoll_wait的超Ӟ?的时候表C马上返回,?1的时候表CZ直等下去Q直到有事g范围QؓL正整数的时候表C等q么长的旉Q如果一直没
有事Ӟ则范围。一般如果网l主循环是单独的U程的话Q可以用-1来等Q这样可以保证一些效率,如果是和主逻辑在同一个线E的话,则可以用0来保证主循环
的效率?/p>
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) { //如果是主socket的事件的话,则表C有新连接进入了Q进行新q接的处理?
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client); // 新q接|于非阻塞模?
ev.events = EPOLLIN | EPOLLET; // q且新q接也加入EPOLL的监听队列?
注意Q这里的参数EPOLLIN | EPOLLETq没有设|对写socket的监听,如果有写操作的话Q这个时候epoll是不会返回事件的Q如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
//
讄好event之后Q将q个新的event通过epoll_ctl加入到epoll的监听队列里面,q里用EPOLL_CTL_ADD来加一个新?
epoll事gQ通过EPOLL_CTL_DEL来减一个epoll事gQ通过EPOLL_CTL_MOD来改变一个事件的监听方式?
fprintf(stderr, "epoll set insertion error: fd=%d0,
client);
return -1;
}
}
else // 如果不是主socket的事件的话,则代表是一个用户socket的事Ӟ则来处理q个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理?
do_use_fd(events[n].data.fd);
}
如果您对epoll的效率还不太了解Q请参考我之前关于|络游戏的网l编E等相关的文章?/p>
以前公司的服务器都是使用HTTPq接Q但是这L话,在手机目前的|络情况下不但显得速度较慢Q而且不稳定。因此大家一致同意用
SOCKET来进行连接。虽然用SOCKET之后Q对于用L费用可能会增?׃是用了CMNET而非CMWAP)Q但是,U着用户体验至上的原则,
怿大家q是能够接受?希望那些玩家月末收到帐单不后能够保持克制...)?br>q次的服务器设计中,最重要的一个突_是用了EPOLL模型Q虽然对之也是一知半解,但是既然在各大PC|游中已l经q了如此严酷的考验Q相信他不会让我们失望,使用后的l果Q确实也是表现相当不错。在q里Q我q是主要大致介绍一下这个模型的l构?br>6、Linux下EPOll~程实例
EPOLL模型g只有一U格式,所以大家只要参考我下面的代码,p够对EPOLL有所了解了,代码的解释都已经在注释中Q?/p>
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//{待EPOLL旉的发生,相当于监听,至于相关的端口,需要在初始化EPOLL的时候绑定?br>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用户q接到绑定的HTTP端口Q徏立新的连接。由于我们新采用了SOCKETq接Q所以基本没用?br>{
OnAcceptHttpEpoll ();
}
else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监到一个SOCKET用户q接Cl定的SOCKET端口Q徏立新的连接?br>{
OnAcceptSockEpoll ();
}
else if (m_events[i].events & EPOLLIN)//如果是已l连接的用户Qƈ且收到数据,那么q行d?br>{
OnReadEpoll (i);
}
catch (int)
{
PRINTF ("CATCH捕获错误\n");
continue;
}
}
m_bOnTimeChecking = TRUE;
OnTimer ();//q行一些定时的操作Q主要就是删除一些短U用L?br>}
其实EPOLL的精华,也就是上q的几段短短的代码,看来时代真的不同了,以前如何接受大量用户q接的问题,现在却被如此L的搞定,真是让h不得不感叹,对哪?/p>
ȝ
Windows完成端口与Linux epoll技术方案是q?个^C实现异步IO和设计开发一个大定wQ具可扩展性的winsockE序指服务程序的很好的选择Q本文对q?中技术的实现原理和实际的使用Ҏ做了一个详l的介绍?/p>