﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-暮暮息幕幕新-随笔分类-7.Linux</title><link>http://www.cppblog.com/singohgod/category/17452.html</link><description>每天晚上入睡，都将迎来崭新的明天，这就是梦想！-大宝天天见</description><language>zh-cn</language><lastBuildDate>Mon, 17 Oct 2011 09:24:01 GMT</lastBuildDate><pubDate>Mon, 17 Oct 2011 09:24:01 GMT</pubDate><ttl>60</ttl><item><title>[转载]Linux静态库和动态库</title><link>http://www.cppblog.com/singohgod/archive/2011/10/17/158546.html</link><dc:creator>大宝天天见</dc:creator><author>大宝天天见</author><pubDate>Mon, 17 Oct 2011 07:51:00 GMT</pubDate><guid>http://www.cppblog.com/singohgod/archive/2011/10/17/158546.html</guid><wfw:comment>http://www.cppblog.com/singohgod/comments/158546.html</wfw:comment><comments>http://www.cppblog.com/singohgod/archive/2011/10/17/158546.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/singohgod/comments/commentRss/158546.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/singohgod/services/trackbacks/158546.html</trackback:ping><description><![CDATA[<p>库从本质上来说是一种可执行代码的二进制格式，可以被载入内存中执行。库分静态库和动态库两种。&nbsp;</p>
<h1>静态库和动态库的区别</h1>
<p>1. 静态函数库</p>
<p>&nbsp;&nbsp;&nbsp; 这类库的名字一般是libxxx.a；利用静态函数库编译成的文件比较大，因为整个 函数库的所有数据都会被整合进目标代码中，他的优点就显而易见了，即编译后的执行程序不需要外部的函数库支持，因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点，因为如果静态函数库改变了，那么你的程序必须重新编译。</p>
<p>2. 动态函数库</p>
<p>&nbsp;&nbsp;&nbsp; 这类库的名字一般是libxxx.so;相对于静态函数库，动态函数库在编译的时候 并没有被编译进目标代码中，你的程序执行到相关函数时才调用该函数库里的相应函数，因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序，而是程序运行时动态的申请并调用，所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序，所以动态函数库的升级比较方便。&nbsp;<br />
linux系统有几个重要的目录存放相应的函数库，如/lib /usr/lib。</p>
<h1>静态库的使用</h1>
<p>静态库的操作工具：gcc和ar 命令。&nbsp;</p>
<p><strong>编写及使用静态库</strong>&nbsp;</p>
<p><strong>(1)设计库源码 pr1.c 和 pr2.c</strong>&nbsp;</p>
<p>[root@billstone make_lib]# cat pr1.c&nbsp;</p>
<p>void print1()&nbsp;</p>
<p>{&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("This is the first lib src!\n");&nbsp;</p>
<p>}&nbsp;</p>
<p>[root@billstone make_lib]# cat pr2.c&nbsp;</p>
<p>void print2()&nbsp;</p>
<p>{&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("This is the second src lib!\n");&nbsp;</p>
<p>}&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>(2)&nbsp; 编译.c 文件</strong>&nbsp;</p>
<p>[bill@billstone make_lib]$ cc -O -c pr1.c pr2.c&nbsp;</p>
<p>[bill@billstone make_lib]$ ls -l pr*.o&nbsp;</p>
<p>-rw-rw-r--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 804&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 11:11 pr1.o&nbsp;</p>
<p>-rw-rw-r--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 804&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 11:11 pr2.o&nbsp;</p>
<p><strong>(3)&nbsp; 链接静态库</strong>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 为了在编译程序中正确找到库文件,静态库必须按照 lib[name].a 的规则命名,如下例中[name]=pr.&nbsp;</p>
<p>[bill@billstone make_lib]$ ar -rsv libpr.a pr1.o pr2.o&nbsp;</p>
<p>a - pr1.o&nbsp;</p>
<p>a - pr2.o&nbsp;</p>
<p>[bill@billstone make_lib]$ ls -l *.a&nbsp;</p>
<p>-rw-rw-r--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1822&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 11:12 libpr.a&nbsp;</p>
<p>[bill@billstone make_lib]$ ar -t libpr.a&nbsp;</p>
<p>pr1.o&nbsp;</p>
<p>pr2.o&nbsp;</p>
<p><strong>(4)&nbsp; 调用库函数代码 main.c</strong>&nbsp;</p>
<p>[bill@billstone make_lib]$ cat main.c&nbsp;</p>
<p>int main()&nbsp;</p>
<p>{&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print1();&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print2();&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;&nbsp;</p>
<p>}&nbsp;</p>
<p><strong>(5)&nbsp; 编译链接选项</strong>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; -L 及-l 参数放在后面.其中,-L 加载库文件路径,-l 指明库文件名字.&nbsp;</p>
<p>[bill@billstone make_lib]$ gcc -o main main.c -L./ -lpr&nbsp;</p>
<p>[bill@billstone make_lib]$ ls -l main*&nbsp;</p>
<p>-rwxrwxr-x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 11805&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 11:17 main&nbsp;</p>
<p>-rw-rw-r--&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 50&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 11:15 main.c&nbsp;</p>
<p><strong>(6)执行目标程序</strong>&nbsp;</p>
<p>[bill@billstone make_lib]$ ./main&nbsp;</p>
<p>This is the first lib src!&nbsp;</p>
<p>This is the second src lib!&nbsp;</p>
<p>[bill@billstone make_lib]$&nbsp;</p>
<h1>动态库的使用</h1>
<p>编写动态库&nbsp;</p>
<p>(1)<strong>设计库代码</strong>&nbsp;</p>
<p>[bill@billstone make_lib]$ cat pr1.c&nbsp;</p>
<p>int p = 2;&nbsp;</p>
<p>void print(){&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("This is the first dll src!\n");&nbsp;</p>
<p>}&nbsp;</p>
<p>[bill@billstone make_lib]$&nbsp;&nbsp;&nbsp;</p>
<p>(2)<strong>生成动态库</strong>&nbsp;</p>
<p>[bill@billstone make_lib]$ gcc -O -fpic -shared -o dl.so pr1.c&nbsp;</p>
<p>[bill@billstone make_lib]$ ls -l *.so&nbsp;</p>
<p>-rwxrwxr-x&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6592&nbsp;&nbsp;&nbsp; 4 月&nbsp; 15 15:19 dl.so&nbsp;</p>
<p>[bill@billstone make_lib]$&nbsp;</p>
<p><strong>动态库的隐式调用</strong>&nbsp;</p>
<p>&nbsp; 在编译调用库函数代码时指明动态库的位置及名字,&nbsp; 看下面实例&nbsp;</p>
<p>[bill@billstone make_lib]$ cat main.c&nbsp;</p>
<p>int main()&nbsp;</p>
<p>{&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print();&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;&nbsp;</p>
<p>}&nbsp;</p>
<p>[bill@billstone make_lib]$ gcc -o tdl main.c ./dl.so&nbsp;</p>
<p>[bill@billstone make_lib]$ ./tdl&nbsp;</p>
<p>This is the first dll src!&nbsp;</p>
<p>[bill@billstone make_lib]$&nbsp;</p>
<p>当动态库的位置活名字发生改变时,&nbsp; 程序将无法正常运行;&nbsp; 而动态库取代静态库的好处之一则是通过更新动态库而随时升级库的内容.&nbsp;</p>
<p><strong>动态库的显式调用</strong>&nbsp;</p>
<p>&nbsp; 显式调用动态库需要四个函数的支持,&nbsp;&nbsp;<strong>函数 dlopen 打开动态库,&nbsp; 函数 dlsym 获取动态库中对象基址,&nbsp; 函</strong><strong>数 dlerror 获取显式动态库操作中的错误信息,&nbsp; 函数 doclose 关闭动态库.</strong></p>
<p>[bill@billstone make_lib]$ cat main.c&nbsp;</p>
<p>#include &lt;dlfcn.h&gt;&nbsp;</p>
<p>int main()&nbsp;</p>
<p>{&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void *pHandle;&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; void (*pFunc)();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 指向函数的指针&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int *p;&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pHandle = dlopen("./d1.so", RTLD_NOW);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 打开动态库&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(!pHandle){&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Can't find d1.so \n");&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; exit(1);&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFunc = (void (*)())dlsym(pHandle, "print");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 获取库函数 print 的地址&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(pFunc)&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFunc();&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Can't find function print\n");&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = (int *)dlsym(pHandle, "p");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 获取库变量 p 的地址&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(p)&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("p = %d\n", *p);&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Can't find int p\n");&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dlclose(pHandle);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp; 关闭动态库&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;&nbsp;</p>
<p>}&nbsp;</p>
<p>[bill@billstone make_lib]$&nbsp;<strong>gcc -o tds main.c &#8211;ld1 &#8211;L.</strong></p>
<p>此时还不能立即./tds，因为在动态函数库使用时，会查找/usr/lib、/lib目录下的动态函数库，而此时我们生成的库不在里边。 这个时候有好几种方法可以让他成功运行： 最直接最简单的方法就是把libstr_out.so拉到/usr/lib或/lib中去。 还有一种方法 export LD_LIBRARY_PATH=$(pwd) 另外还可以在/etc/ld.so.conf文件里加入我们生成的库的目录，然后/sbin/ldconfig。 /etc/ld.so.conf是非常重要的一个目录，里面存放的是链接器和加载器搜索共享库时要检查的目录，默认是从/usr/lib /lib中读取的，所以想要顺利运行，我们也可以把我们库的目录加入到这个文件中并执行/sbin/ldconfig 。另外还有个文件需要了解/etc/ld.so.cache,里面保存了常用的动态函数库，且会先把他们加载到内存中，因为内存的访问速度远远大于硬盘的访问速度，这样可以提高软件加载动态函数库的速度了。</p>
<h1>库依赖的查看</h1>
<p>使用ldd命令来查看执行文件依赖于哪些库。</p>
<p>该命令用于判断某个可执行的 binary 档案含有什么动态函式库。<br />
[root@test root]# ldd [-vdr] [filename]<br />
参数说明：<br />
--version　　打印ldd的版本号<br />
-v --verbose　　打印所有信息，例如包括符号的版本信息<br />
-d --data-relocs　　执行符号重部署，并报告缺少的目标对象（只对ELF格式适用）<br />
-r --function-relocs　　对目标对象和函数执行重新部署，并报告缺少的目标对象和函数（只对ELF格式适用）<br />
--help 用法信息。</p>
<p>如果命令行中给定的库名字包含'/'，这个程序的libc5版本将使用它作为库名字；否则它将在标准位置搜索库。运行一个当前目录下的共享库，加前缀"./"。</p>
@import url(http://www.cppblog.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);<img src ="http://www.cppblog.com/singohgod/aggbug/158546.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/singohgod/" target="_blank">大宝天天见</a> 2011-10-17 15:51 <a href="http://www.cppblog.com/singohgod/archive/2011/10/17/158546.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]Windows完成端口与Linux epoll技术简介</title><link>http://www.cppblog.com/singohgod/archive/2011/08/03/152376.html</link><dc:creator>大宝天天见</dc:creator><author>大宝天天见</author><pubDate>Wed, 03 Aug 2011 09:43:00 GMT</pubDate><guid>http://www.cppblog.com/singohgod/archive/2011/08/03/152376.html</guid><wfw:comment>http://www.cppblog.com/singohgod/comments/152376.html</wfw:comment><comments>http://www.cppblog.com/singohgod/archive/2011/08/03/152376.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/singohgod/comments/commentRss/152376.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/singohgod/services/trackbacks/152376.html</trackback:ping><description><![CDATA[<p><strong>WINDOWS<strong>完成端口</strong>编程<br />
</strong>1、基本概念<br />
2、WINDOWS<strong>完成端口</strong>的特点<br />
3、<strong>完成端口</strong>（Completion Ports ）相关数据结构和创建<br />
4、<strong>完成端口</strong>线程的工作原理<br />
5、Windows<strong>完成端口</strong>的实例代码<br />
<strong>Linux的<strong>EPoll</strong>模型<br />
</strong>1、为什么select落后<br />
2、内核中提高I/O性能的新方法<strong>epoll</strong><br />
3、<strong>epoll</strong>的优点<br />
4、<strong>epoll</strong>的工作模式&nbsp;<br />
5、<strong>epoll</strong>的使用方法<br />
6、Linux下<strong>EPOll</strong>编程实例<br />
<strong>总结</strong></p>
<p><strong>WINDOWS<strong>完成端口</strong>编程<br />
</strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 摘要：开发网络程序从来都不是一件容易的事情，尽管只需要遵守很少的一些规则;创建socket,发起连接，接受连接，发送和接受数据。真正的困难在于：让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windows平台<strong>完成端口</strong>进行重叠I/O的技术和Linux在2.6版本的内核中引入的<strong>EPOll</strong>技术，可以很方便在在在Windows和Linux平台上开发出支持大量连接的网络服务程序。本文介绍在Windows和Linux平台上使用的<strong>完成端口</strong>和<strong>EPoll</strong>模型开发的基本原理，同时给出实际的例子。本文主要关注C/S结构的服务器端程序，因为一般来说，开发一个大容量，具可扩展性的winsock程序一般就是指服务程序。<br />
<strong><br />
1、基本概念<br />
</strong>&nbsp;&nbsp;&nbsp; 设备---windows操作系统上允许通信的任何东西，比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等。所以我们不能看到**File函数就只想到文件设备。与设备通信有两种方式，同步方式和异步方式。同步方式下，当调用ReadFile函数时，函数会等待系统执行完所要求的工作，然后才返回；异步方式下，ReadFile这类函数会直接返回，系统自己去完成对设备的操作，然后以某种方式通知完成操作。<br />
重叠I/O----顾名思义，当你调用了某个函数（比如ReadFile）就立刻返回做自己的其他动作的时候，同时系统也在对I/0设备进行你要求的操作，在这段时间内你的程序和系统的内部动作是重叠的，因此有更好的性能。所以，重叠I/O是用于异步方式下使用I/O设备的。 重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。<br />
<strong><br />
2、WINDOWS<strong>完成端口</strong>的特点<br />
</strong>&nbsp;&nbsp; Win32重叠I/O(Overlapped I/O)机制允许发起一个操作，然后在操作完成之后接受到信息。对于那种需要很长时间才能完成的操作来说，重叠IO机制尤其有用，因为发起重叠操作的线程在重叠请求发出后就可以自由的做别的事情了。在WinNT和Win2000上，提供的真正的可扩展的I/O模型就是使用<strong>完成端口</strong>（Completion Port）的重叠I/O.<strong>完成端口</strong>---是一种WINDOWS内核对象。<strong>完成端口</strong>用于异步方式的重叠I/0情况下，当然重叠I/O不一定非使用<strong>完成端口</strong>不可，还有设备内核对象、事件对象、告警I/0等。但是<strong>完成端口</strong>内部提供了线程池的管理，可以避免反复创建线程的开销，同时可以根据CPU的个数灵活的决定线程个数，而且可以让减少线程调度的次数从而提高性能其实类似于WSAAsyncSelect和select函数的机制更容易兼容Unix，但是难以实现我们想要的&#8220;扩展性&#8221;。而且windows的<strong>完成端口</strong>机制在操作系统内部已经作了优化，提供了更高的效率。所以，我们选择<strong>完成端口</strong>开始我们的服务器程序的开发。<br />
1、发起操作不一定完成，系统会在完成的时候通知你，通过用户在<strong>完成端口</strong>上的等待，处理操作的结果。所以要有检查<strong>完成端口</strong>，取操作结果的线程。在<strong>完成端口</strong>上守候的线程系统有优化，除非在执行的线程阻塞，不会有新的线程被激活，以此来减少线程切换造成的性能代价。所以如果程序中没有太多的阻塞操作，没有必要启动太多的线程，CPU数量的两倍，一般这样来启动线程。<br />
2、操作与相关数据的绑定方式：在提交数据的时候用户对数据打相应的标记，记录操作的类型，在用户处理操作结果的时候，通过检查自己打的标记和系统的操作结果进行相应的处理。&nbsp;<br />
3、操作返回的方式:一般操作完成后要通知程序进行后续处理。但写操作可以不通知用户，此时如果用户写操作不能马上完成，写操作的相关数据会被暂存到到非交换缓冲区中，在操作完成的时候，系统会自动释放缓冲区。此时发起完写操作，使用的内存就可以释放了。此时如果占用非交换缓冲太多会使系统停止响应。<br />
<strong><br />
3、<strong>完成端口</strong>（Completion Ports ）相关数据结构和创建<br />
</strong>&nbsp;&nbsp;&nbsp; 其实可以把<strong>完成端口</strong>看成系统维护的一个队列，操作系统把重叠IO操作完成的事件通知放到该队列里，由于是暴露 &#8220;操作完成&#8221;的事件通知，所以命名为&#8220;<strong>完成端口</strong>&#8221;（COmpletion Ports）。一个socket被创建后，可以在任何时刻和一个<strong>完成端口</strong>联系起来。<br />
<strong>完成端口</strong>相关最重要的是OVERLAPPED数据结构<br />
typedef struct _OVERLAPPED {&nbsp;<br />
&nbsp;&nbsp;&nbsp; ULONG_PTR Internal;//被系统内部赋值，用来表示系统状态&nbsp;<br />
&nbsp;&nbsp;&nbsp; ULONG_PTR InternalHigh;// 被系统内部赋值，传输的字节数&nbsp;<br />
&nbsp;&nbsp;&nbsp; union {&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct {&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD Offset;//和OffsetHigh合成一个64位的整数，用来表示从文件头部的多少字节开始&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD OffsetHigh;//操作，如果不是对文件I/O来操作，则必须设定为0&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PVOID Pointer;&nbsp;<br />
&nbsp;&nbsp;&nbsp; };&nbsp;<br />
&nbsp;&nbsp;&nbsp; HANDLE hEvent;//如果不使用，就务必设为0,否则请赋一个有效的Event句柄&nbsp;<br />
} OVERLAPPED, *LPOVERLAPPED;&nbsp;<br />
<br />
下面是异步方式使用ReadFile的一个例子&nbsp;<br />
OVERLAPPED Overlapped;&nbsp;<br />
Overlapped.Offset=345;&nbsp;<br />
Overlapped.OffsetHigh=0;&nbsp;<br />
Overlapped.hEvent=0;&nbsp;<br />
//假定其他参数都已经被初始化&nbsp;<br />
ReadFile(hFile,buffer,sizeof(buffer),&amp;dwNumBytesRead,&amp;Overlapped);&nbsp;<br />
这样就完成了异步方式读文件的操作，然后ReadFile函数返回，由操作系统做自己的事情，下面介绍几个与OVERLAPPED结构相关的函数&nbsp;<br />
等待重叠I/0操作完成的函数&nbsp;<br />
BOOL GetOverlappedResult (<br />
HANDLE hFile,<br />
LPOVERLAPPED lpOverlapped,//接受返回的重叠I/0结构<br />
LPDWORD lpcbTransfer,//成功传输了多少字节数<br />
BOOL fWait //TRUE只有当操作完成才返回，FALSE直接返回，如果操作没有完成，通过调//用GetLastError ( )函数会返回ERROR_IO_INCOMPLETE&nbsp;<br />
);<br />
宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完成，该宏对OVERLAPPED结构的Internal成员进行了测试，查看是否等于STATUS_PENDING值。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一般来说，一个应用程序可以创建多个工作线程来处理<strong>完成端口</strong>上的通知事件。工作线程的数量依赖于程序的具体需要。但是在理想的情况下，应该对应一个CPU创建一个线程。因为在<strong>完成端口</strong>理想模型中，每个线程都可以从系统获得一个&#8220;原子&#8221;性的时间片，轮番运行并检查<strong>完成端口</strong>，线程的切换是额外的开销。在实际开发的时候，还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作，系统则将其挂起，让别的线程获得运行时间。因此，如果有这样的情况，可以多创建几个线程来尽量利用时间。<br />
应用<strong>完成端口</strong>：<br />
&nbsp;&nbsp;&nbsp; 创建<strong>完成端口</strong>：<strong>完成端口</strong>是一个内核对象，使用时他总是要和至少一个有效的设备句柄进行关联，<strong>完成端口</strong>是一个复杂的内核对象，创建它的函数是：<br />
HANDLE CreateIoCompletionPort(&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN HANDLE FileHandle,&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN HANDLE ExistingCompletionPort,&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN ULONG_PTR CompletionKey,&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN DWORD NumberOfConcurrentThreads&nbsp;<br />
&nbsp;&nbsp;&nbsp; );&nbsp;<br />
<br />
通常创建工作分两步：<br />
第一步，创建一个新的<strong>完成端口</strong>内核对象，可以使用下面的函数：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads)&nbsp;<br />
{&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads);&nbsp;<br />
};<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
第二步，将刚创建的<strong>完成端口</strong>和一个有效的设备句柄关联起来，可以使用下面的函数：<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey)&nbsp;<br />
{&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0);&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return h==hCompPort;&nbsp;<br />
};&nbsp;<br />
说明&nbsp;<br />
1） CreateIoCompletionPort函数也可以一次性的既创建<strong>完成端口</strong>对象，又关联到一个有效的设备句柄&nbsp;<br />
2） CompletionKey是一个可以自己定义的参数，我们可以把一个结构的地址赋给它，然后在合适的时候取出来使用，最好要保证结构里面的内存不是分配在栈上，除非你有十分的把握内存会保留到你要使用的那一刻。<br />
3） NumberOfConcurrentThreads通常用来指定要允许同时运行的的线程的最大个数。通常我们指定为0，这样系统会根据CPU的个数来自动确定。创建和关联的动作完成后，系统会将<strong>完成端口</strong>关联的设备句柄、完成键作为一条纪录加入到这个<strong>完成端口</strong>的设备列表中。如果你有多个<strong>完成端口</strong>，就会有多个对应的设备列表。如果设备句柄被关闭，则表中自动删除该纪录。<br />
<strong><br />
4、<strong>完成端口</strong>线程的工作原理</strong><br />
<strong>完成端口</strong>可以帮助我们管理线程池，但是线程池中的线程需要我们使用_beginthreadex来创建，凭什么通知<strong>完成端口</strong>管理我们的新线程呢？答案在函数GetQueuedCompletionStatus。该函数原型：&nbsp;<br />
BOOL GetQueuedCompletionStatus(&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN HANDLE CompletionPort,&nbsp;<br />
&nbsp;&nbsp;&nbsp; OUT LPDWORD lpNumberOfBytesTransferred,&nbsp;<br />
&nbsp;&nbsp;&nbsp; OUT PULONG_PTR lpCompletionKey,&nbsp;<br />
&nbsp;&nbsp;&nbsp; OUT LPOVERLAPPED *lpOverlapped,&nbsp;<br />
&nbsp;&nbsp;&nbsp; IN DWORD dwMilliseconds&nbsp;<br />
);&nbsp;<br />
这个函数试图从指定的<strong>完成端口</strong>的I/0完成队列中抽取纪录。只有当重叠I/O动作完成的时候，完成队列中才有纪录。凡是调用这个函数的线程将被放入到<strong>完成端口</strong>的等待线程队列中，因此<strong>完成端口</strong>就可以在自己的线程池中帮助我们维护这个线程。<strong>完成端口</strong>的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录，该纪录拥有四个字段，前三项就对应GetQueuedCompletionStatus函数的2、3、4参数，最后一个字段是错误信息dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成了一个重叠I/0操作。&nbsp;<br />
当I/0完成队列中出现了纪录，<strong>完成端口</strong>将会检查等待线程队列，该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单，只是保存了这些线程的ID。<strong>完成端口</strong>会按照后进先出的原则将一个线程队列的ID放入到释放线程列表中，同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度。所以我们的线程要想成为<strong>完成端口</strong>管理的线程，就必须要调用GetQueuedCompletionStatus函数。出于性能的优化，实际上<strong>完成端口</strong>还维护了一个暂停线程列表，具体细节可以参考《Windows高级编程指南》，我们现在知道的知识，已经足够了。&nbsp;<strong>完成端口</strong>线程间数据传递线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数，或者使用全局变量。但是<strong>完成端口</strong>还有自己的传递数据的方法，答案就在于CompletionKey和OVERLAPPED参数。<br />
CompletionKey被保存在<strong>完成端口</strong>的设备表中，是和设备句柄一一对应的，我们可以将与设备句柄相关的数据保存到CompletionKey中，或者将CompletionKey表示为结构指针，这样就可以传递更加丰富的内容。这些内容只能在一开始关联<strong>完成端口</strong>和设备句柄的时候做，因此不能在以后动态改变。<br />
OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给<strong>完成端口</strong>的。我们可以看到，如果我们不是对文件设备做操作，该结构的成员变量就对我们几乎毫无作用。我们需要附加信息，可以创建自己的结构，然后将OVERLAPPED结构变量作为我们结构变量的第一个成员，然后传递第一个成员变量的地址给ReadFile函数。因为类型匹配，当然可以通过编译。当GetQueuedCompletionStatus函数返回时，我们可以获取到第一个成员变量的地址，然后一个简单的强制转换，我们就可以把它当作完整的自定义结构的指针使用，这样就可以传递很多附加的数据了。太好了！只有一点要注意，如果跨线程传递，请注意将数据分配到堆上，并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中，这样当GetQueuedCompletionStatus被返回时，我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。CompletionKey和OVERLAPPED参数，都可以通过GetQueuedCompletionStatus函数获得。<br />
线程的安全退出<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 很多线程为了不止一次的执行异步数据处理，需要使用如下语句<br />
while (true)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ......<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus(...);&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ......<br />
}<br />
那么如何退出呢，答案就在于上面曾提到的PostQueudCompletionStatus函数，我们可以用它发送一个自定义的包含了OVERLAPPED成员变量的结构地址，里面包含一个状态变量，当状态变量为退出标志时，线程就执行清除动作然后退出。<br />
<strong><br />
5、Windows<strong>完成端口</strong>的实例代码：<br />
</strong>DWORD WINAPI WorkerThread(LPVOID lpParam)<br />
{&nbsp;<br />
ULONG_PTR *PerHandleKey;<br />
OVERLAPPED *Overlap;<br />
OVERLAPPEDPLUS *OverlapPlus,<br />
*newolp;<br />
DWORD dwBytesXfered;<br />
while (1)<br />
{<br />
ret = GetQueuedCompletionStatus(<br />
hIocp,<br />
&amp;dwBytesXfered,<br />
(PULONG_PTR)&amp;PerHandleKey,<br />
&amp;Overlap,<br />
INFINITE);<br />
if (ret == 0)<br />
{<br />
// Operation failed<br />
continue;<br />
}<br />
OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);<br />
switch (OverlapPlus-&gt;OpCode)<br />
{<br />
case OP_ACCEPT:<br />
// Client socket is contained in OverlapPlus.sclient<br />
// Add client to completion port<br />
CreateIoCompletionPort(<br />
(HANDLE)OverlapPlus-&gt;sclient,<br />
hIocp,<br />
(ULONG_PTR)0,<br />
0);<br />
// Need a new OVERLAPPEDPLUS structure<br />
// for the newly accepted socket. Perhaps<br />
// keep a look aside list of free structures.<br />
newolp = AllocateOverlappedPlus();<br />
if (!newolp)<br />
{<br />
// Error<br />
}<br />
newolp-&gt;s = OverlapPlus-&gt;sclient;<br />
newolp-&gt;OpCode = OP_READ;<br />
// This function divpares the data to be sent<br />
PrepareSendBuffer(&amp;newolp-&gt;wbuf);<br />
ret = WSASend(<br />
newolp-&gt;s,<br />
&amp;newolp-&gt;wbuf,<br />
1,<br />
&amp;newolp-&gt;dwBytes,<br />
0,<br />
&amp;newolp.ol,<br />
NULL);<br />
if (ret == SOCKET_ERROR)<br />
{<br />
if (WSAGetLastError() != WSA_IO_PENDING)<br />
{<br />
// Error<br />
}<br />
}<br />
// Put structure in look aside list for later use<br />
FreeOverlappedPlus(OverlapPlus);<br />
// Signal accept thread to issue another AcceptEx<br />
SetEvent(hAcceptThread);<br />
break;<br />
case OP_READ:<br />
// Process the data read&nbsp;<br />
// Repost the read if necessary, reusing the same<br />
// receive buffer as before<br />
memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));<br />
ret = WSARecv(<br />
OverlapPlus-&gt;s,<br />
&amp;OverlapPlus-&gt;wbuf,<br />
1,<br />
&amp;OverlapPlus-&gt;dwBytes,<br />
&amp;OverlapPlus-&gt;dwFlags,<br />
&amp;OverlapPlus-&gt;ol,<br />
NULL);<br />
if (ret == SOCKET_ERROR)<br />
{<br />
if (WSAGetLastError() != WSA_IO_PENDING)<br />
{<br />
// Error<br />
}<br />
}<br />
break;<br />
case OP_WRITE:<br />
// Process the data sent, etc.<br />
break;<br />
} // switch<br />
} // while<br />
} // WorkerThread<br />
</p>
<p>查看以上代码，注意如果Overlapped操作立刻失败（比如，返回SOCKET_ERROR或其他非WSA_IO_PENDING的错误），则没有任何完成通知时间会被放到<strong>完成端口</strong>队列里。反之，则一定有相应的通知时间被放到<strong>完成端口</strong>队列。更完善的关于Winsock的<strong>完成端口</strong>机制，可以参考MSDN的Microsoft PlatFormSDK，那里有<strong>完成端口</strong>的例子。访问<a href="http://msdn.microsoft.com/library/techart/msdn_servrapp.htm">http://msdn.microsoft.com/library/techart/msdn_servrapp.htm</a>可以获得更多信息。</p>
<p><strong>Linux的<strong>EPoll</strong>模型<br />
</strong>Linux 2.6内核中提高网络I/O性能的新方法-<strong>epoll</strong>&nbsp;I/O多路复用技术在比较多的TCP网络服务器中有使用，即比较多的用到select函数。<br />
<br />
<strong>1、为什么select落后<br />
</strong>首先，在Linux内核中，select所用到的FD_SET是有限的，即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数，在我用的2.6.15-25-386内核中，该值是1024，搜索内核源代码得到：<br />
include/linux/posix_types.h:#define __FD_SETSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1024<br />
也就是说，如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。其次，内核中实现select是用轮询方法，即每次检测都会遍历所有FD_SET中的句柄，显然，select函数执行时间与FD_SET中的句柄个数有一个比例关系，即select要检测的句柄数越多就会越费时。当然，在前文中我并没有提及poll方法，事实上用select的朋友一定也试过poll，我个人觉得select和poll大同小异，个人偏好于用select而已。</p>
<p><strong>2、内核中提高I/O性能的新方法<strong>epoll</strong></strong><br />
<strong>epoll</strong>是什么？按照man手册的说法：是为处理大批量句柄而作了改进的poll。要使用<strong>epoll</strong>只需要这三个系统调用：<strong>epoll</strong>_create(2)，&nbsp;<strong>epoll</strong>_ctl(2)，&nbsp;<strong>epoll</strong>_wait(2)。<br />
当然，这不是2.6内核才有的，它是在2.5.44内核中被引进的(<strong>epoll</strong>(4) is a new API introduced in Linux kernel 2.5.44)</p>
<p>Linux2.6内核<strong>epoll</strong>介绍<br />
先介绍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<strong>完成端口</strong>实现的AIO,实际上也是内部用线程池实现的罢了，最后的结果是IO有个线程池，你应用也需要一个线程池...... 很多文档其实已经指出了这带来的线程context-switch带来的代价。在linux 平台上，关于网络AIO一直是改动最多的地方，2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布，网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法，在使用了NPTL的linux上面其实和windows的<strong>完成端口</strong>基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘，对于流服务器在内存平稳性上有相当帮助)。<br />
所以，剩下的select模型基本上就是我们在linux上面的唯一选择，其实，如果加上no-block socket的配置，可以完成一个"伪"AIO的实现，只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点，所以改进一直是2.4-2.5开发版本内核的任务，包括/dev/poll，realtime signal等等。最终，Davide Libenzi开发的<strong>epoll</strong>进入2.6内核成为正式的解决方案<br />
<strong><br />
3、<strong>epoll</strong>的优点</strong><br />
&lt;1&gt;支持一个进程打开大数目的socket描述符(FD)<br />
select 最不能忍受的是一个进程所打开的FD是有一定限制的，由FD_SETSIZE设置，默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核，不过资料也同时指出这样会带来网络效率的下降，二是可以选择多进程的解决方案(传统的Apache方案)，不过虽然linux上面创建进程的代价比较小，但仍旧是不可忽视的，加上进程间数据同步远比不上线程间同步的高效，所以也不是一种完美的方案。不过&nbsp;<strong>epoll</strong>则没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。<br />
&lt;2&gt;IO效率不随FD数目增加而线性下降<br />
传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合，不过由于网络延时，任一时间只有部分的socket是"活跃"的，但是select/poll每次调用都会线性扫描全部的集合，导致效率呈现线性下降。但是<strong>epoll</strong>不存在这个问题，它只会对"活跃"的socket进行操作---这是因为在内核实现中<strong>epoll</strong>是根据每个fd上面的callback函数实现的。那么，只有"活跃"的socket才会主动的去调用 callback函数，其他idle状态socket则不会，在这点上，<strong>epoll</strong>实现了一个"伪"AIO，因为这时候推动力在os内核。在一些 benchmark中，如果所有的socket基本上都是活跃的---比如一个高速LAN环境，<strong>epoll</strong>并不比select/poll有什么效率，相反，如果过多使用<strong>epoll</strong>_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,<strong>epoll</strong>的效率就远在select/poll之上了。<br />
&lt;3&gt;使用mmap加速内核与用户空间的消息传递。<br />
这点实际上涉及到<strong>epoll</strong>的具体实现了。无论是select,poll还是<strong>epoll</strong>都需要内核把FD消息通知给用户空间，如何避免不必要的内存拷贝就很重要，在这点上，<strong>epoll</strong>是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注<strong>epoll</strong>的话，一定不会忘记手工 mmap这一步的。<br />
&lt;4&gt;内核微调<br />
这一点其实不算<strong>epoll</strong>的优点了，而是整个linux平台的优点。也许你可以怀疑linux平台，但是你无法回避linux平台赋予你微调内核的能力。比如，内核TCP/IP协议栈使用内存池管理sk_buff结构，那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX&gt;/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度)，也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。<br />
4、<strong>epoll</strong>的工作模式<br />
令人高兴的是，2.6内核的<strong>epoll</strong>比其2.5开发版本的/dev/<strong>epoll</strong>简洁了许多，所以，大部分情况下，强大的东西往往是简单的。唯一有点麻烦是<strong>epoll</strong>有2种工作方式:LT和ET。<br />
LT(level triggered)是缺省的工作方式，并且同时支持block和no-block socket.在这种做法中，内核告诉你一个文件描述符是否就绪了，然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作，内核还是会继续通知你的，所以，这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表．<br />
ET (edge-triggered)是高速工作方式，只支持no-block socket。在这种模式下，当描述符从未就绪变为就绪时，内核通过<strong>epoll</strong>告诉你。然后它会假设你知道文件描述符已经就绪，并且不会再为那个文件描述符发送更多的就绪通知，直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如，你在发送，接收或者接收请求，或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误）。但是请注意，如果一直不对这个fd作IO操作(从而导致它再次变成未就绪)，内核不会发送更多的通知(only once),不过在TCP协议中，ET模式的加速效用仍需要更多的benchmark确认。<br />
<strong>epoll</strong>只有<strong>epoll</strong>_create,<strong>epoll</strong>_ctl,<strong>epoll</strong>_wait 3个系统调用，具体用法请参考<a href="http://www.xmailserver.org/linux-patches/nio-improve.html">http://www.xmailserver.org/linux-patches/nio-improve.html</a>&nbsp;，在<a href="http://www.kegel.com/rn/">http://www.kegel.com/rn/</a>也有一个完整的例子，大家一看就知道如何使用了<br />
Leader/follower模式线程pool实现，以及和<strong>epoll</strong>的配合。<br />
<br />
<strong>5、&nbsp;<strong>epoll</strong>的使用方法</strong><br />
&nbsp;&nbsp;&nbsp; 首先通过create_<strong>epoll</strong>(int maxfds)来创建一个<strong>epoll</strong>的句柄，其中maxfds为你<strong>epoll</strong>所支持的最大句柄数。这个函数会返回一个新的<strong>epoll</strong>句柄，之后的所有操作将通过这个句柄来进行操作。在用完之后，记得用close()来关闭这个创建出来的<strong>epoll</strong>句柄。 之后在你的网络主循环里面，每一帧的调用<strong>epoll</strong>_wait(int epfd,&nbsp;<strong>epoll</strong>_event events, int max events, int timeout)来查询所有的网络接口，看哪一个可以读，哪一个可以写了。基本的语法为：&nbsp;<br />
nfds =&nbsp;<strong>epoll</strong>_wait(kdpfd, events, maxevents, -1);&nbsp;<br />
其中kdpfd为用<strong>epoll</strong>_create创建之后的句柄，events是一个<strong>epoll</strong>_event*的指针，当<strong>epoll</strong>_wait这个函数操作成功之后，<strong>epoll</strong>_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是<strong>epoll</strong>_wait的超时，为0的时候表示马上返回，为-1的时候表示一直等下去，直到有事件范围，为任意正整数的时候表示等这么长的时间，如果一直没有事件，则范围。一般如果网络主循环是单独的线程的话，可以用-1来等，这样可以保证一些效率，如果是和主逻辑在同一个线程的话，则可以用0来保证主循环的效率。</p>
<p><strong>epoll</strong>_wait范围之后应该是一个循环，遍利所有的事件：&nbsp;<br />
for(n = 0; n &lt; nfds; ++n) {&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(events[n].data.fd == listener) { //如果是主socket的事件的话，则表示有新连接进入了，进行新连接的处理。&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client = accept(listener, (struct sockaddr *) &amp;local,&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;addrlen);&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(client &lt; 0){&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perror("accept");&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; setnonblocking(client); // 将新连接置于非阻塞模式&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.events = EPOLLIN | EPOLLET; // 并且将新连接也加入<strong>EPOLL</strong>的监听队列。&nbsp;<br />
注意，这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听，如果有写操作的话，这个时候<strong>epoll</strong>是不会返回事件的，如果要对写操作也监听的话，应该是EPOLLIN | EPOLLOUT | EPOLLET&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ev.data.fd = client;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (<strong>epoll</strong>_ctl(kdpfd,&nbsp;<strong>EPOLL</strong>_CTL_ADD, client, &amp;ev) &lt; 0) {&nbsp;<br />
// 设置好event之后，将这个新的event通过<strong>epoll</strong>_ctl加入到<strong>epoll</strong>的监听队列里面，这里用<strong>EPOLL</strong>_CTL_ADD来加一个新的<strong>epoll</strong>事件，通过<strong>EPOLL</strong>_CTL_DEL来减少一个<strong>epoll</strong>事件，通过<strong>EPOLL</strong>_CTL_MOD来改变一个事件的监听方式。&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fprintf(stderr, "<strong>epoll</strong>&nbsp;set insertion error: fd=%d0,&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; client);&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else // 如果不是主socket的事件的话，则代表是一个用户socket的事件，则来处理这个用户socket的事情，比如说read(fd,xxx)之类的，或者一些其他的处理。&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; do_use_fd(events[n].data.fd);&nbsp;<br />
}</p>
<p>对，<strong>epoll</strong>的操作就这么简单，总共不过4个API：<strong>epoll</strong>_create,&nbsp;<strong>epoll</strong>_ctl,&nbsp;<strong>epoll</strong>_wait和close。&nbsp;<br />
如果您对<strong>epoll</strong>的效率还不太了解，请参考我之前关于网络游戏的网络编程等相关的文章。</p>
<p><br />
以前公司的服务器都是使用HTTP连接，但是这样的话，在手机目前的网络情况下不但显得速度较慢，而且不稳定。因此大家一致同意用SOCKET来进行连接。虽然使用SOCKET之后，对于用户的费用可能会增加(由于是用了CMNET而非CMWAP)，但是，秉着用户体验至上的原则，相信大家还是能够接受的(希望那些玩家月末收到帐单不后能够保持克制...)。<br />
这次的服务器设计中，最重要的一个突破，是使用了<strong>EPOLL</strong>模型，虽然对之也是一知半解，但是既然在各大PC网游中已经经过了如此严酷的考验，相信他不会让我们失望，使用后的结果，确实也是表现相当不错。在这里，我还是主要大致介绍一下这个模型的结构。<br />
6、Linux下<strong>EPOll</strong>编程实例<br />
<strong>EPOLL</strong>模型似乎只有一种格式，所以大家只要参考我下面的代码，就能够对<strong>EPOLL</strong>有所了解了，代码的解释都已经在注释中：</p>
<p>while (TRUE)<br />
{<br />
int nfds =&nbsp;<strong>epoll</strong>_wait (m_<strong>epoll</strong>_fd, m_events, MAX_EVENTS,&nbsp;<strong>EPOLL</strong>_TIME_OUT);//等待<strong>EPOLL</strong>时间的发生，相当于监听，至于相关的端口，需要在初始化<strong>EPOLL</strong>的时候绑定。<br />
if (nfds &lt;= 0)<br />
continue;<br />
m_bOnTimeChecking = FALSE;<br />
G_CurTime = time(NULL);<br />
for (int i=0; i<br />
{<br />
try<br />
{<br />
if (m_events[i].data.fd == m_listen_http_fd)//如果新监测到一个HTTP用户连接到绑定的HTTP端口，建立新的连接。由于我们新采用了SOCKET连接，所以基本没用。<br />
{<br />
OnAcceptHttpEpoll ();<br />
}<br />
else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口，建立新的连接。<br />
{<br />
OnAcceptSockEpoll ();<br />
}<br />
else if (m_events[i].events &amp; EPOLLIN)//如果是已经连接的用户，并且收到数据，那么进行读入。<br />
{<br />
OnReadEpoll (i);<br />
}</p>
<p>OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据。<br />
}<br />
catch (int)<br />
{<br />
PRINTF ("CATCH捕获错误\n");<br />
continue;<br />
}<br />
}<br />
m_bOnTimeChecking = TRUE;<br />
OnTimer ();//进行一些定时的操作，主要就是删除一些短线用户等。<br />
}<br />
其实<strong>EPOLL</strong>的精华，也就是上述的几段短短的代码，看来时代真的不同了，以前如何接受大量用户连接的问题，现在却被如此轻松的搞定，真是让人不得不感叹，对哪。</p>
<p><br />
<strong>总结<br />
</strong>Windows<strong>完成端口</strong>与Linux&nbsp;<strong>epoll</strong>技术方案是这2个平台上实现异步IO和设计开发一个大容量，具可扩展性的winsock程序指服务程序的很好的选择，本文对这2中技术的实现原理和实际的使用方法做了一个详细的介绍。</p>
@import url(http://www.cppblog.com/CuteSoft_Client/CuteEditor/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/css/cuteeditor.css);<img src ="http://www.cppblog.com/singohgod/aggbug/152376.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/singohgod/" target="_blank">大宝天天见</a> 2011-08-03 17:43 <a href="http://www.cppblog.com/singohgod/archive/2011/08/03/152376.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>