??xml version="1.0" encoding="utf-8" standalone="yes"?>
1. 静态函数库
q类库的名字一般是libxxx.aQ利用静态函数库~译成的文g比较大,因ؓ(f)整个 函数库的所有数据都?x)被整合q目标代码中Q他的优点就显而易见了Q即~译后的执行E序不需要外部的函数库支持,因ؓ(f)所有用的函数都已l被~译q去了。当然这也会(x)成ؓ(f)他的~点Q因为如果静态函数库改变了,那么你的E序必须重新~译?/p>
2. 动态函数库
q类库的名字一般是libxxx.so;相对于静态函数库Q动态函数库在编译的时?q没有被~译q目标代码中Q你的程序执行到相关函数时才调用该函数库里的相应函数Q因此动态函数库所产生的可执行文g比较?yu)。由于函数库没有被整合进你的E序Q而是E序q行时动态的甌q调用,所以程序的q行环境中必L供相应的库。动态函数库的改变ƈ不媄响你的程序,所以动态函数库的升U比较方ѝ?nbsp;
linuxpȝ有几个重要的目录存放相应的函数库Q如/lib /usr/lib?/p>
静态库的操作工P(x)gcc和ar 命o(h)?nbsp;
~写?qing)用静态库
(1)设计库源?pr1.c ?pr2.c
[root@billstone make_lib]# cat pr1.c
void print1()
{
printf("This is the first lib src!\n");
}
[root@billstone make_lib]# cat pr2.c
void print2()
{
printf("This is the second src lib!\n");
}
(2) ~译.c 文g
[bill@billstone make_lib]$ cc -O -c pr1.c pr2.c
[bill@billstone make_lib]$ ls -l pr*.o
-rw-rw-r-- 1 bill bill 804 4 ?nbsp; 15 11:11 pr1.o
-rw-rw-r-- 1 bill bill 804 4 ?nbsp; 15 11:11 pr2.o
(3) 链接静态库
Z在编译程序中正确扑ֈ库文?静态库必须按照 lib[name].a 的规则命?如下例中[name]=pr.
[bill@billstone make_lib]$ ar -rsv libpr.a pr1.o pr2.o
a - pr1.o
a - pr2.o
[bill@billstone make_lib]$ ls -l *.a
-rw-rw-r-- 1 bill bill 1822 4 ?nbsp; 15 11:12 libpr.a
[bill@billstone make_lib]$ ar -t libpr.a
pr1.o
pr2.o
(4) 调用库函C?main.c
[bill@billstone make_lib]$ cat main.c
int main()
{
print1();
print2();
return 0;
}
(5) ~译链接选项
-L ?l 参数攑֜后面.其中,-L 加蝲库文件\?-l 指明库文件名?
[bill@billstone make_lib]$ gcc -o main main.c -L./ -lpr
[bill@billstone make_lib]$ ls -l main*
-rwxrwxr-x 1 bill bill 11805 4 ?nbsp; 15 11:17 main
-rw-rw-r-- 1 bill bill 50 4 ?nbsp; 15 11:15 main.c
(6)执行目标E序
[bill@billstone make_lib]$ ./main
This is the first lib src!
This is the second src lib!
[bill@billstone make_lib]$
~写动态库
(1)设计库代?/strong>
[bill@billstone make_lib]$ cat pr1.c
int p = 2;
void print(){
printf("This is the first dll src!\n");
}
[bill@billstone make_lib]$
(2)生成动态库
[bill@billstone make_lib]$ gcc -O -fpic -shared -o dl.so pr1.c
[bill@billstone make_lib]$ ls -l *.so
-rwxrwxr-x 1 bill bill 6592 4 ?nbsp; 15 15:19 dl.so
[bill@billstone make_lib]$
动态库的隐式调?/strong>
在编译调用库函数代码时指明动态库的位|及(qing)名字, 看下面实?nbsp;
[bill@billstone make_lib]$ cat main.c
int main()
{
print();
return 0;
}
[bill@billstone make_lib]$ gcc -o tdl main.c ./dl.so
[bill@billstone make_lib]$ ./tdl
This is the first dll src!
[bill@billstone make_lib]$
当动态库的位|活名字发生改变? E序无法正常运? 而动态库取代静态库的好处之一则是通过更新动态库而随时升U库的内?
动态库的显式调?/strong>
昑ּ调用动态库需要四个函数的支持, 函数 dlopen 打开动态库, 函数 dlsym 获取动态库中对象基址, ?/strong>?dlerror 获取昑ּ动态库操作中的错误信息, 函数 doclose 关闭动态库.
[bill@billstone make_lib]$ cat main.c
#include <dlfcn.h>
int main()
{
void *pHandle;
void (*pFunc)(); // 指向函数的指?nbsp;
int *p;
pHandle = dlopen("./d1.so", RTLD_NOW); // 打开动态库
if(!pHandle){
printf("Can't find d1.so \n");
exit(1);
}
pFunc = (void (*)())dlsym(pHandle, "print"); // 获取库函?print 的地址
if(pFunc)
pFunc();
else
printf("Can't find function print\n");
p = (int *)dlsym(pHandle, "p"); // 获取库变?p 的地址
if(p)
printf("p = %d\n", *p);
else
printf("Can't find int p\n");
dlclose(pHandle); // 关闭动态库
return 0;
}
[bill@billstone make_lib]$ gcc -o tds main.c –ld1 –L.
此时q不能立?/tdsQ因为在动态函数库使用Ӟ?x)查?usr/lib?lib目录下的动态函数库Q而此时我们生成的库不在里辏V?q个时候有好几U方法可以让他成功运行:(x) 最直接最单的Ҏ(gu)是把libstr_out.so拉到/usr/lib?lib中去?q有一U方?export LD_LIBRARY_PATH=$(pwd) 另外q可以在/etc/ld.so.conf文g里加入我们生成的库的目录Q然?sbin/ldconfig?/etc/ld.so.conf是非帔R要的一个目录,里面存放的是链接器和加蝲器搜索共享库时要(g)查的目录Q默认是?usr/lib /lib中读取的Q所以想要顺利运行,我们也可以把我们库的目录加入到这个文件中q执?sbin/ldconfig 。另外还有个文g需要了?etc/ld.so.cache,里面保存了常用的动态函数库Q且?x)先把他们加载到内存中,因?f)内存的访问速度q远大于盘的访问速度Q这样可以提高Y件加载动态函数库的速度了?/p>
使用ldd命o(h)来查看执行文件依赖于哪些库?/p>
该命令用于判断某个可执行?binary 档案含有什么动态函式库?br />
[root@test root]# ldd [-vdr] [filename]
参数说明Q?br />
--version 打印l(f)dd的版本号
-v --verbose 打印所有信息,例如包括W号的版本信?br />
-d --data-relocs 执行W号重部|Ԍq报告缺的目标对象Q只对ELF格式适用Q?br />
-r --function-relocs 对目标对象和函数执行重新部vQƈ报告~少的目标对象和函数Q只对ELF格式适用Q?br />
--help 用法信息?/p>
如果命o(h)行中l定的库名字包含'/'Q这个程序的libc5版本用它作ؓ(f)库名字;否则它将在标准位|搜索库。运行一个当前目录下的共享库Q加前缀"./"?/p>
@import url(http://www.shnenglu.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);
WINDOWS完成端口~程
摘要Q开发网l程序从来都不是一件容易的事情Q尽只需要遵守很的一些规?创徏socket,发vq接Q接受连接,发送和接受数据。真正的困难在于Q让你的E序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windowsq_完成端口q行重叠I/O的技术和Linux?.6版本的内怸引入?strong>EPOll技术,可以很方便在在在Windows和Linuxq_上开发出支持大量q接的网l服务程序。本文介l在Windows和Linuxq_上用的完成端口?strong>EPoll模型开发的基本原理Q同时给出实际的例子。本文主要关注C/Sl构的服务器端程序,因ؓ(f)一般来_(d)开发一个大定wQ具可扩展性的winsockE序一般就是指服务E序?br />
1、基本概?br />
讑֤---windows操作pȝ上允?dng)R信的Q何东西,比如文g、目录、串行口、ƈ行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑盘、物理磁盘等。绝大多C讑֤打交道的函数都是CreateFile/ReadFile/WriteFile{。所以我们不能看?*File函数只惛_文g讑֤。与讑֤通信有两U方式,同步方式和异步方式。同步方式下Q当调用ReadFile函数Ӟ函数?x)等待系l执行完所要求的工作,然后才返回;异步方式下,ReadFileq类函数?x)直接返回,pȝ自己d成对讑֤的操作,然后以某U方式通知完成操作?br />
重叠I/O----思义Q当你调用了某个函数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和W(xu)in2000上,提供的真正的可扩展的I/O模型是使用完成端口QCompletion PortQ的重叠I/O.完成端口---是一UWINDOWS内核对象?strong>完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可Q还有设备内核对象、事件对象、告警I/0{。但?strong>完成端口内部提供了线E池的管理,可以避免反复创徏U程的开销Q同时可以根据CPU的个数灵zȝ军_U程个数Q而且可以让减线E调度的ơ数从而提高性能其实cM于WSAAsyncSelect和select函数的机制更Ҏ(gu)兼容UnixQ但是难以实现我们想要的“扩展?#8221;。而且windows?strong>完成端口机制在操作系l内部已l作了优化,提供了更高的效率。所以,我们选择完成端口开始我们的服务器程序的开发?br />
1、发h作不一定完成,pȝ?x)在完成的时候通知你,通过用户?strong>完成端口上的{待Q处理操作的l果。所以要有检?strong>完成端口Q取操作l果的线E。在完成端口上守候的U程pȝ有优化,除非在执行的U程dQ不?x)有新的U程被激z,以此来减线E切换造成的性能代h(hun)。所以如果程序中没有太多的阻塞操作,没有必要启动太多的线E,CPU数量的两倍,一般这h启动U程?br />
2、操作与相关数据的绑定方式:(x)在提交数据的时候用户对数据打相应的标记Q记录操作的cdQ在用户处理操作l果的时候,通过(g)查自己打的标记和pȝ的操作结果进行相应的处理?nbsp;
3、操作返回的方式:一般操作完成后要通知E序q行后箋处理。但写操作可以不通知用户Q此时如果用户写操作不能马上完成Q写操作的相x据会(x)被暂存到到非交换~冲ZQ在操作完成的时候,pȝ?x)自动释攄冲区。此时发起完写操作,使用的内存就可以释放了。此时如果占用非交换~冲太多?x)ɾpȝ停止响应?br />
3?strong>完成端口QCompletion Ports Q相x据结构和创徏
其实可以?strong>完成端口看成pȝl护的一个队列,操作pȝ把重叠IO操作完成的事仉知攑ֈ该队列里Q由于是暴露 “操作完成”的事仉知Q所以命名ؓ(f)“完成端口”QCOmpletion PortsQ。一个socket被创建后Q可以在M时刻和一?strong>完成端口联系h?br />
完成端口相关最重要的是OVERLAPPED数据l构
typedef struct _OVERLAPPED {
ULONG_PTR Internal;//被系l内部赋|用来表示pȝ状?nbsp;
ULONG_PTR InternalHigh;// 被系l内部赋|传输的字节数
union {
struct {
DWORD Offset;//和OffsetHigh合成一?4位的整数Q用来表CZ文g头部的多字节开?nbsp;
DWORD OffsetHigh;//操作Q如果不是对文gI/O来操作,则必设定ؓ(f)0
};
PVOID Pointer;
};
HANDLE hEvent;//如果不用,务必设?,否则误一个有效的Event句柄
} OVERLAPPED, *LPOVERLAPPED;
下面是异步方式用ReadFile的一个例?nbsp;
OVERLAPPED Overlapped;
Overlapped.Offset=345;
Overlapped.OffsetHigh=0;
Overlapped.hEvent=0;
//假定其他参数都已l被初始?nbsp;
ReadFile(hFile,buffer,sizeof(buffer),&dwNumBytesRead,&Overlapped);
q样完成了异步方式L件的操作Q然后ReadFile函数q回Q由操作pȝ做自q事情Q下面介l几个与OVERLAPPEDl构相关的函?nbsp;
{待重叠I/0操作完成的函?nbsp;
BOOL GetOverlappedResult (
HANDLE hFile,
LPOVERLAPPED lpOverlapped,//接受q回的重叠I/0l构
LPDWORD lpcbTransfer,//成功传输了多字节数
BOOL fWait //TRUE只有当操作完成才q回QFALSE直接q回Q如果操作没有完成,通过?/用GetLastError ( )函数?x)返回ERROR_IO_INCOMPLETE
);
宏HasOverlappedIoCompleted可以帮助我们试重叠I/0操作是否完成Q该宏对OVERLAPPEDl构的Internal成员q行了测试,查看是否{于STATUS_PENDING倹{?/p>
一般来_(d)一个应用程序可以创建多个工作线E来处理完成端口上的通知事g。工作线E的数量依赖于程序的具体需要。但是在理想的情况下Q应该对应一个CPU创徏一个线E。因为在完成端口理想模型中,每个U程都可以从pȝ获得一?#8220;原子”性的旉片,轮番q行q检?strong>完成端口Q线E的切换是额外的开销。在实际开发的时候,q要考虑q些U程是否牉|到其他堵塞操作的情况。如果某U程q行堵塞操作Q系l则其挂vQ让别的U程获得q行旉。因此,如果有这L(fng)情况Q可以多创徏几个U程来尽量利用时间?br />
应用完成端口Q?br />
创徏完成端口Q?strong>完成端口是一个内核对象,使用时他L要和臛_一个有效的讑֤句柄q行兌Q?strong>完成端口是一个复杂的内核对象Q创建它的函数是Q?br />
HANDLE CreateIoCompletionPort(
IN HANDLE FileHandle,
IN HANDLE ExistingCompletionPort,
IN ULONG_PTR CompletionKey,
IN DWORD NumberOfConcurrentThreads
);
通常创徏工作分两步:(x)
W一步,创徏一个新?strong>完成端口内核对象Q可以用下面的函数Q?br />
HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)
{
return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);
};
W二步,刚创徏?strong>完成端口和一个有效的讑֤句柄兌hQ可以用下面的函数Q?br />
bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)
{
HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);
return h==hCompPort;
};
说明
1Q?CreateIoCompletionPort函数也可以一ơ性的既创?strong>完成端口对象Q又兌C个有效的讑֤句柄
2Q?CompletionKey是一个可以自己定义的参数Q我们可以把一个结构的地址赋给它,然后在合适的时候取出来使用Q最好要保证l构里面的内存不是分配在栈上Q除非你有十分的把握内存?x)保留到你要使用的那一刅R?br />
3Q?NumberOfConcurrentThreads通常用来指定要允许同时运行的的线E的最大个数。通常我们指定?Q这L(fng)l会(x)Ҏ(gu)CPU的个数来自动定。创建和兌的动作完成后Q系l会(x)?strong>完成端口兌的设备句柄、完成键作ؓ(f)一条纪录加入到q个完成端口的设备列表中。如果你有多?strong>完成端口Q就?x)有多个对应的设备列表。如果设备句柄被关闭Q则表中自动删除该纪录?br />
4?strong>完成端口U程的工作原?/strong>
完成端口可以帮助我们理U程池,但是U程池中的线E需要我们用_beginthreadex来创建,凭什么通知完成端口理我们的新U程呢?{案在函数GetQueuedCompletionStatus。该函数原型Q?nbsp;
BOOL GetQueuedCompletionStatus(
IN HANDLE CompletionPort,
OUT LPDWORD lpNumberOfBytesTransferred,
OUT PULONG_PTR lpCompletionKey,
OUT LPOVERLAPPED *lpOverlapped,
IN DWORD dwMilliseconds
);
q个函数试图从指定的完成端口的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候,完成队列中才有纪录。凡是调用这个函数的U程被攑օ?strong>完成端口的等待线E队列中Q因?strong>完成端口可以在自己的线E池中帮助我们维护这个线E?strong>完成端口的I/0完成队列中存放了当重叠I/0完成的结?--- 一条纪录,该纪录拥有四个字D,前三就对应GetQueuedCompletionStatus函数???参数Q最后一个字D|错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作?nbsp;
当I/0完成队列中出CU录Q?strong>完成端口会(x)(g)查等待线E队列,该队列中的线E都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线E队列很单,只是保存了这些线E的ID?strong>完成端口?x)按照后q先出的原则一个线E队列的ID攑օ到释攄E列表中Q同时该U程从{待GetQueuedCompletionStatus函数q回的睡眠状态中变ؓ(f)可调度状态等待CPU的调度。所以我们的U程要想成ؓ(f)完成端口理的线E,必要调用GetQueuedCompletionStatus函数。出于性能的优化,实际?strong>完成端口q维护了一个暂停线E列表,具体l节可以参考《Windows高~程指南》,我们现在知道的知识,已经_了?nbsp;完成端口U程间数据传递线E间传递数据最常用的办法是在_beginthreadex函数中将参数传递给U程函数Q或者用全局变量。但?strong>完成端口q有自己的传递数据的Ҏ(gu)Q答案就在于CompletionKey和OVERLAPPED参数?br />
CompletionKey被保存在完成端口的设备表中,是和讑֤句柄一一对应的,我们可以与讑֤句柄相关的数据保存到CompletionKey中,或者将CompletionKey表示为结构指针,q样可以传递更加丰富的内容。这些内容只能在一开始关?strong>完成端口和设备句柄的时候做Q因此不能在以后动态改变?br />
OVERLAPPED参数是在每次调用ReadFileq样的支持重叠I/0的函数时传递给完成端口的。我们可以看刎ͼ如果我们不是Ҏ(gu)件设备做操作Q该l构的成员变量就Ҏ(gu)们几乎毫无作用。我们需要附加信息,可以创徏自己的结构,然后OVERLAPPEDl构变量作ؓ(f)我们l构变量的第一个成员,然后传递第一个成员变量的地址lReadFile函数。因为类型匹配,当然可以通过~译。当GetQueuedCompletionStatus函数q回Ӟ我们可以获取到第一个成员变量的地址Q然后一个简单的强制转换Q我们就可以把它当作完整的自定义l构的指针用,q样可以传递很多附加的数据了。太好了Q只有一点要注意Q如果跨U程传递,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里面包含一个状态变量,当状态变量ؓ(f)退出标志时Q线E就执行清除动作然后退出?br />
5、Windows完成端口的实例代码:(x)
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完成通知旉?x)被攑ֈ?strong>完成端口队列里。反之,则一定有相应的通知旉被放?strong>完成端口队列。更完善的关于Winsock?strong>完成端口机制Q可以参考MSDN的Microsoft PlatFormSDKQ那里有完成端口的例子。访?a >http://msdn.microsoft.com/library/techart/msdn_servrapp.htm可以获得更多信息?/p>
Linux?strong>EPoll模型
Linux 2.6内核中提高网lI/O性能的新Ҏ(gu)-epoll I/O多\复用技术在比较多的TCP|络服务器中有用,x较多的用到select函数?br />
1、ؓ(f)什么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
也就是说Q如果想要同时检?025个句柄的可读状态是不可能用select实现的。或者同时检?025个句柄的可写状态也是不可能的。其ơ,内核中实现select是用轮询Ҏ(gu)Q即每次(g)都?x)遍历所有FD_SET中的句柄Q显Ӟselect函数执行旉与FD_SET中的句柄个数有一个比例关p,即select要检的句柄数越多就?x)越?gu)。当Ӟ在前文中我ƈ没有提及(qing)pollҎ(gu)Q事实上用select的朋友一定也试过pollQ我个h觉得select和poll大同异Q个人偏好于用select而已?/p>
2、内怸提高I(y)/O性能的新Ҏ(gu)epoll
epoll是什么?按照man手册的说法:(x)是ؓ(f)处理大批量句柄而作了改q的poll。要使用epoll只需要这三个pȝ调用Q?strong>epoll_create(2)Q?nbsp;epoll_ctl(2)Q?nbsp;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介绍
先介l?本书《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,?.4内核讲解Linux TCP/IP实现Q相当不?作ؓ(f)一个现实世界中的实玎ͼ很多时候你必须作很多权衡,q时候参考一个久l考验的系l更有实际意义。D个例?linux内核中sk_buffl构Zq求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多?sk_buff最也?72的字?其实对于socket应用层程序来_(d)另外一本书《UNIX Network Programming Volume 1》意义更大一?2003q的时候,q本书出了最新的W?版本Q不q主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevensl出了网lIO的基本模型。在q里最重要的莫q于select模型和Asynchronous I/O模型.从理Z_(d)AIOg是最高效的,你的IO操作可以立即q回Q然后等待os告诉你IO操作完成。但是一直以来,如何实现没有一个完的Ҏ(gu)。最著名的windows完成端口实现的AIO,实际上也是内部用U程池实现的|了Q最后的l果是IO有个U程池,你应用也需要一个线E池...... 很多文档其实已经指出了这带来的线Econtext-switch带来的代仗在linux q_上,关于|络AIO一直是改动最多的地方Q?.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布Q网l模块的AIO一直没有进入稳定内核版?大部分都是用用L(fng)E模拟方法,在用了NPTL的linux上面其实和windows?strong>完成端口基本上差不多??.6内核所支持的AIOҎ(gu)盘的AIO---支持io_submit(),io_getevents()以及(qing)对Direct IO的支?是l过VFSpȝbuffer直接写硬盘,对于服务器在内存^Ex上有相当帮??br />
所以,剩下的select模型基本上就是我们在linux上面的唯一选择Q其实,如果加上no-block socket的配|,可以完成一??AIO的实玎ͼ只不q推动力在于你而不是os而已。不q传l的select/poll函数有着一些无法忍受的~点Q所以改q一直是2.4-2.5开发版本内核的dQ包?dev/pollQrealtime signal{等。最l,Davide Libenzi开发的epollq入2.6内核成ؓ(f)正式的解x?br />
3?strong>epoll的优?/strong>
<1>支持一个进E打开大数目的socket描述W?FD)
select 最不能忍受的是一个进E所打开的FD是有一定限制的Q由FD_SETSIZE讄Q默认值是2048。对于那些需要支持的上万q接数目的IM服务器来说显然太了。这时候你一是可以选择修改q个宏然后重新编译内核,不过资料也同时指样会(x)带来|络效率的下降,二是可以选择多进E的解决Ҏ(gu)(传统的ApacheҎ(gu))Q不q虽然linux上面创徏q程的代h较小Q但仍旧是不可忽视的Q加上进E间数据同步q比不上U程间同步的高效Q所以也不是一U完的Ҏ(gu)。不q?nbsp;epoll则没有这个限Ӟ它所支持的FD上限是最大可以打开文g的数目,q个数字一般远大于2048,举个例子,?GB内存的机器上大约?0万左叻I具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和pȝ内存关系很大?br />
<2>IO效率不随FD数目增加而线性下?br />
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合Q不q由于网lgӞM旉只有部分的socket?z跃"的,但是select/poll每次调用都会(x)U性扫描全部的集合Q导致效率呈现线性下降。但?strong>epoll不存在这个问题,它只?x)?z跃"的socketq行操作---q是因ؓ(f)在内核实Cepoll是根据每个fd上面的callback函数实现的。那么,只有"z跃"的socket才会(x)d的去调用 callback函数Q其他idle状态socket则不?x),在这点上Q?strong>epoll实现了一??AIOQ因时候推动力在os内核。在一?benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境Q?strong>epollq不比select/poll有什么效率,相反Q如果过多?strong>epoll_ctl,效率相比q有E微的下降。但是一旦用idle connections模拟WAN环境,epoll的效率就q在select/poll之上了?br />
<3>使用mmap加速内怸用户I间的消息传递?br />
q点实际上涉?qing)?strong>epoll的具体实C。无论是select,pollq是epoll都需要内核把FD消息通知l用L(fng)_(d)如何避免不必要的内存拯很重要Q在q点上,epoll是通过内核于用L(fng)间mmap同一块内存实现的。而如果你x一样从2.5内核关?strong>epoll的话Q一定不?x)忘记手?mmapq一步的?br />
<4>内核微调
q一点其实不?strong>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也可以Ҏ(gu)你^台内存大动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本w大却很小的特D系l上试最新的NAPI|卡驱动架构?br />
4?strong>epoll的工作模?br />
令h高兴的是Q?.6内核?strong>epoll比其2.5开发版本的/dev/epollz了许多Q所以,大部分情况下Q强大的东西往往是简单的。唯一有点ȝ?strong>epoll?U工作方?LT和ET?br />
LT(level triggered)是缺省的工作方式Qƈ且同时支持block和no-block socket.在这U做法中Q内核告诉你一个文件描q符是否qA了,然后你可以对q个qA的fdq行IO操作。如果你不作M操作Q内核还是会(x)l箋通知你的Q所以,q种模式~程出错误可能性要一炏V传l的select/poll都是q种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在q种模式下,当描q符从未qA变ؓ(f)qAӞ内核通过epoll告诉你。然后它?x)假设你知道文g描述W已l就l,q且不会(x)再ؓ(f)那个文g描述W发送更多的qA通知Q直C做了某些操作D那个文g描述W不再ؓ(f)qA状态了(比如Q你在发送,接收或者接收请求,或者发送接收的数据于一定量时导致了一个EWOULDBLOCK 错误Q。但是请注意Q如果一直不对这个fd作IO操作(从而导致它再次变成未就l?Q内怸?x)发送更多的通知(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以?qing)?strong>epoll的配合?br />
5?nbsp;epoll的用方?/strong>
首先通过create_epoll(int maxfds)来创Z?strong>epoll的句柄,其中maxfdsZepoll所支持的最大句柄数。这个函C(x)q回一个新?strong>epoll句柄Q之后的所有操作将通过q个句柄来进行操作。在用完之后Q记得用close()来关闭这个创建出来的epoll句柄?之后在你的网l主循环里面Q每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的|络接口Q看哪一个可以读Q哪一个可以写了。基本的语法为:(x)
nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创徏之后的句柄,events是一?strong>epoll_event*的指针,?strong>epoll_waitq个函数操作成功之后Q?strong>epoll_events里面储存所有的d事g。max_events是当前需要监听的所有socket句柄数。最后一个timeout?strong>epoll_wait的超Ӟ?的时候表C马上返回,?1的时候表CZ直等下去Q直到有事g范围Qؓ(f)L正整数的时候表C等q么长的旉Q如果一直没有事Ӟ则范围。一般如果网l主循环是单独的U程的话Q可以用-1来等Q这样可以保证一些效率,如果是和主逻辑在同一个线E的话,则可以用0来保证主循环的效率?/p>
epoll_wait范围之后应该是一个@环,遍利所有的事gQ?nbsp;
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) { //如果是主socket的事件的话,则表C有新连接进入了Q进行新q接的处理?nbsp;
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client); // 新q接|于非阻塞模?nbsp;
ev.events = EPOLLIN | EPOLLET; // q且新q接也加?strong>EPOLL的监听队列?nbsp;
注意Q这里的参数EPOLLIN | EPOLLETq没有设|对写socket的监听,如果有写操作的话Q这个时?strong>epoll是不?x)返回事件的Q如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
// 讄好event之后Q将q个新的event通过epoll_ctl加入?strong>epoll的监听队列里面,q里?strong>EPOLL_CTL_ADD来加一个新?strong>epoll事gQ通过EPOLL_CTL_DEL来减一?strong>epoll事gQ通过EPOLL_CTL_MOD来改变一个事件的监听方式?nbsp;
fprintf(stderr, "epoll set insertion error: fd=%d0,
client);
return -1;
}
}
else // 如果不是主socket的事件的话,则代表是一个用户socket的事Ӟ则来处理q个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理?nbsp;
do_use_fd(events[n].data.fd);
}
对,epoll的操作就q么单,d不过4个APIQ?strong>epoll_create, epoll_ctl, epoll_wait和close?nbsp;
如果(zhn)对epoll的效率还不太了解Q请参考我之前关于|络游戏的网l编E等相关的文章?/p>
以前公司的服务器都是使用HTTPq接Q但是这L(fng)话,在手机目前的|络情况下不但显得速度较慢Q而且不稳定。因此大家一致同意用SOCKET来进行连接。虽然用SOCKET之后Q对于用L(fng)费用可能?x)增?׃是用了CMNET而非CMWAP)Q但是,U着用户体验至上的原则,怿大家q是能够接受?希望那些玩家月末收到帐单不后能够保持克制...)?br />
q次的服务器设计中,最重要的一个突_(d)是用了EPOLL模型Q虽然对之也是一知半解,但是既然在各大PC|游中已l经q了如此严酷的考验Q相信他不会(x)让我们失望,使用后的l果Q确实也是表现相当不错。在q里Q我q是主要大致介绍一下这个模型的l构?br />
6、Linux?strong>EPOll~程实例
EPOLL模型g只有一U格式,所以大家只要参考我下面的代码,p够对EPOLL有所了解了,代码的解释都已经在注释中Q?/p>
while (TRUE)
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//{待EPOLL旉的发生,相当于监听,至于相关的端口,需要在初始?strong>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);
}
OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据?br />
}
catch (int)
{
PRINTF ("CATCH捕获错误\n");
continue;
}
}
m_bOnTimeChecking = TRUE;
OnTimer ();//q行一些定时的操作Q主要就是删除一些短U用L(fng)?br />
}
其实EPOLL的精华,也就是上q的几段短短的代码,看来时代真的不同了,以前如何接受大量用户q接的问题,现在却被如此L的搞定,真是让h不得不感叹,对哪?/p>
ȝ
Windows完成端口与Linux epoll技术方案是q?个^C实现异步IO和设计开发一个大定wQ具可扩展性的winsockE序指服务程序的很好的选择Q本文对q?中技术的实现原理和实际的使用Ҏ(gu)做了一个详l的介绍?/p>
@import url(http://www.shnenglu.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);