<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_cdo</title><subtitle type="text"/><id>http://feed.cnblogs.com/blog/u/11708/rss</id><updated>2011-04-18T13:51:33Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/11708/rss"/><entry><id>http://www.cnblogs.com/cdo/archive/2011/04/18/2020246.html</id><title type="text">Linux环境进程间通信（五）: 共享内存（上）</title><summary type="text">采用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次数据[1]：一次从输入文件到共享内存区，另一次从共享内存区到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域。而是保持共享区域，直到通信完毕为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。Linux的2.2.x内核支持多种共享内存方式，如mma</summary><published>2011-04-18T13:52:00Z</published><updated>2011-04-18T13:52:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2011/04/18/2020246.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2011/04/18/2020246.html"/><content type="html">&lt;div&gt;&lt;span style="font-family: Simsun; line-height: normal; font-size: medium; "&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;采用共享内存通信的一个显而易见的好处是效率高，因为进程可以直接读写内存，而不需要任何数据的拷贝。对于像管道和消息队列等通信方式，则需要在内核和用户空间进行四次的数据拷贝，而共享内存则只拷贝两次数据[1]：一次从输入文件到共享内存区，另一次从共享内存区到输出文件。实际上，进程之间在共享内存时，并不总是读写少量数据后就解除映射，有新的通信时，再重新建立共享内存区域。而是保持共享区域，直到通信完毕为止，这样，数据内容一直保存在共享内存中，并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此，采用共享内存的通信方式效率是非常高的。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;Linux的2.2.x内核支持多种共享内存方式，如mmap()系统调用，Posix共享内存，以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存，但还没实现Posix共享内存，本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="1"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;1、page cache及swap cache中页面的区分：一个被访问文件的物理页面都驻留在page cache或swap cache中，一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ，它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;2、文件与address_space结构的对应：一个具体的文件在打开后，内核会在内存中为之建立一个struct inode结构，其中的i_mapping域指向一个address_space结构。这样，一个文件就对应一个address_space结构，一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此，当要寻址某个数据时，很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;3、进程调用mmap()时，只是在进程空间内新增了一块相应大小的缓冲区，并设置了相应的访问标识，但并没有建立进程空间到物理页面的映射。因此，第一次访问该空间时，会引发一个缺页异常。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;4、对于共享内存映射情况，缺页异常处理程序首先在swap cache中寻找目标页（符合address_space以及偏移量的物理页），如果找到，则直接返回地址；如果没有找到，则判断该页是否在交换区(swap area)，如果在，则执行一个换入操作；如果上述两种情况都不满足，处理程序将分配新的物理页面，并把它插入到page cache中。进程最终将更新进程页表。&amp;nbsp;&lt;br /&gt;注：对于映射普通文件情况（非共享映射），缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到，则说明文件数据还没有读入内存，处理程序会从磁盘读入相应的页面，并返回相应地址，同时，进程页表也会更新。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;5、所有进程在映射同一个共享内存区域时，情况都一样，在建立线性地址与物理地址之间的映射之后，不论进程各自的返回地址如何，实际访问的必然是同一个共享内存区域对应的物理页面。&amp;nbsp;&lt;br /&gt;注：一个共享内存区域可以看作是特殊文件系统shm中的一个文件，shm的安装点在交换区上。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;上面涉及到了一些数据结构，围绕数据结构理解问题会容易一些。&lt;/p&gt;&lt;div style="clear: both; background-image: url(http://www.ibm.com/i/solid.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; height: 1px; background-position: initial initial; background-repeat: repeat no-repeat; "&gt;&lt;/div&gt;&lt;p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px; "&gt;&lt;a href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon" style="color: #4c6e94; display: inline; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 18px; text-decoration: none; background-image: url(http://www.ibm.com/i/v16/icons/u_bold.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat; "&gt;回页首&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="2"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;二、mmap()及其相关系统调用&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后，进程可以向访问普通内存一样对文件进行访问，不必再调用read()，write（）等操作。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;注：实际上，mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式，进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的，当然mmap()实现共享内存也是其主要应用之一。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N1006A"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;1、mmap()系统调用形式如下：&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )&amp;nbsp;&lt;br /&gt;参数fd为即将映射到进程空间的文件描述字，一般由open()返回，同时，fd可以指定为-1，此时须指定flags参数中的MAP_ANON，表明进行的是匿名映射（不涉及具体的文件名，避免了文件的创建及打开，很显然只能用于具有亲缘关系的进程间通信）。len是映射到调用进程地址空间的字节数，它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或：PROT_READ（可读） , PROT_WRITE （可写）, PROT_EXEC （可执行）, PROT_NONE（不可访问）。flags由以下几个常值指定：MAP_SHARED , MAP_PRIVATE , MAP_FIXED，其中，MAP_SHARED , MAP_PRIVATE必选其一，而MAP_FIXED则不推荐使用。offset参数一般设为0，表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址，一般被指定一个空指针，此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址，进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数，读者可参考mmap()手册页获得进一步的信息。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N10074"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;2、系统调用mmap()用于共享内存的两种方式：&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;（1）使用普通文件提供的内存映射：适用于任何进程之间； 此时，需要打开或创建一个文件，然后再调用mmap()；典型调用代码如下：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;fd=open(name, flag, mode); if(fd&amp;lt;0) 	... 	&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方，我们将在范例中进行具体说明。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;（2）使用特殊文件提供匿名内存映射：适用于具有亲缘关系的进程之间； 由于父子进程特殊的亲缘关系，在父进程中先调用mmap()，然后调用fork()。那么在调用fork()之后，子进程继承父进程匿名映射后的地址空间，同样也继承mmap()返回的地址，这样，父子进程就可以通过映射区域进行通信了。注意，这里不是一般的继承关系。一般来说，子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址，却由父子进程共同维护。&amp;nbsp;&lt;br /&gt;对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时，不必指定具体的文件，只要设置相应的标志即可，参见范例2。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N10089"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;3、系统调用munmap()&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;int munmap( void * addr, size_t len )&amp;nbsp;&lt;br /&gt;该调用在进程地址空间中解除一个映射关系，addr是调用mmap()时返回的地址，len是映射区的大小。当映射关系解除后，对原来映射地址的访问将导致段错误发生。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N10093"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;4、系统调用msync()&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;int msync ( void * addr , size_t len, int flags)&amp;nbsp;&lt;br /&gt;一般说来，进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中，往往在调用munmap（）后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。&lt;/p&gt;&lt;div style="clear: both; background-image: url(http://www.ibm.com/i/solid.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; height: 1px; background-position: initial initial; background-repeat: repeat no-repeat; "&gt;&lt;/div&gt;&lt;p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px; "&gt;&lt;a href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon" style="color: #4c6e94; display: inline; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 18px; text-decoration: none; background-image: url(http://www.ibm.com/i/v16/icons/u_bold.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat; "&gt;回页首&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="3"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;三、mmap()范例&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;下面将给出使用mmap()的两个范例：范例1给出两个进程通过映射普通文件实现共享内存通信；范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方，下面是通过mmap（）映射普通文件实现进程间的通信的范例，我们通过该范例来说明mmap()实现共享内存的特点及注意事项。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N100A5"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;范例1：两个进程通过映射普通文件实现共享内存通信&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;范例1包含两个子程序：map_normalfile1.c及map_normalfile2.c。编译两个程序，可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件，把该文件映射到进程的地址空间，并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间，然后对映射后的地址空间执行读操作。这样，两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;下面是两个程序代码：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;/*-------------map_normalfile1.c-----------*/ #include &amp;lt;sys/mman.h&amp;gt; #include &amp;lt;sys/types.h&amp;gt; #include &amp;lt;fcntl.h&amp;gt; #include &amp;lt;unistd.h&amp;gt; typedef struct{   char name[4];   int  age; }people; main(int argc, char** argv) // map a normal file as shared mem: {   int fd,i;   people *p_map;   char temp;      fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);   lseek(fd,sizeof(people)*5-1,SEEK_SET);   write(fd,"",1);      p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,         MAP_SHARED,fd,0 );   close( fd );   temp = 'a';   for(i=0; i&amp;lt;10; i++)   {     temp += 1;     memcpy( ( *(p_map+i) ).name, &amp;amp;temp,2 );     ( *(p_map+i) ).age = 20+i;   }   printf(" initialize over \n ")；   sleep(10);   munmap( p_map, sizeof(people)*10 );   printf( "umap ok \n" ); } /*-------------map_normalfile2.c-----------*/ #include &amp;lt;sys/mman.h&amp;gt; #include &amp;lt;sys/types.h&amp;gt; #include &amp;lt;fcntl.h&amp;gt; #include &amp;lt;unistd.h&amp;gt; typedef struct{   char name[4];   int  age; }people; main(int argc, char** argv)  // map a normal file as shared mem: {   int fd,i;   people *p_map;   fd=open( argv[1],O_CREAT|O_RDWR,00777 );   p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,        MAP_SHARED,fd,0);   for(i = 0;i&amp;lt;10;i++)   {   printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );   }   munmap( p_map,sizeof(people)*10 ); } &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;map_normalfile1.c首先定义了一个people数据结构，（在这里采用数据结构的方式是因为，共享内存区的数据往往是有固定格式的，这由通信的各个进程决定，采用结构的方式有普遍代表性）。map_normfile1首先打开或创建一个文件，并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始，设置了10个people结构。然后，进程睡眠10秒钟，等待其他进程映射同一个文件，最后解除映射。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;map_normfile2.c只是简单的映射一个文件，并以people数据结构的格式从mmap()返回的地址处读取10个people结构，并输出读取的值，然后解除映射。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后，在一个终端上先运行./map_normalfile2 /tmp/test_shm，程序输出结果如下：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;initialize over umap ok &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;在map_normalfile1输出initialize over 之后，输出umap ok之前，在另一个终端上运行map_normalfile2 /tmp/test_shm，将会产生如下输出(为了节省空间，输出结果为稍作整理后的结果)：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24; name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;在map_normalfile1 输出umap ok后，运行map_normalfile2则输出如下结果：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24; name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0; &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;strong&gt;从程序的运行结果中可以得出的结论&lt;/strong&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;1、	最终被映射文件的内容的长度不会超过文件本身的初始大小，即映射不能改变文件的大小；&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;2、	可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小，但不完全受限于文件大小。打开文件被截短为5个people结构大小，而在map_normalfile1中初始化了10个people数据结构，在恰当时候（map_normalfile1输出initialize over 之后，输出umap ok之前）调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值，后面将给出详细讨论。&amp;nbsp;&lt;br /&gt;注：在linux中，内存的保护是以页为基本单位的，即使被映射文件只有一个字节大小，内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时，进程可以对从mmap()返回地址开始的一个页面大小进行访问，而不会出错；但是，如果对一个页面以外的地址空间进行访问，则导致错误发生，后面将进一步描述。因此，可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;3、	文件一旦被映射后，调用mmap()的进程对返回地址的访问是对某一内存区域的访问，暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义，只有在调用了munmap()后或者msync()时，才把内存中的相应内容写回磁盘文件，所写内容仍然不能超过文件的大小。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="N100E0"&gt;&lt;span style="font-size: 1.2em; font-weight: bold; "&gt;范例2：父子进程通过匿名映射实现共享内存&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;#include &amp;lt;sys/mman.h&amp;gt; #include &amp;lt;sys/types.h&amp;gt; #include &amp;lt;fcntl.h&amp;gt; #include &amp;lt;unistd.h&amp;gt; typedef struct{   char name[4];   int  age; }people; main(int argc, char** argv) {   int i;   people *p_map;   char temp;   p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,        MAP_SHARED|MAP_ANONYMOUS,-1,0);   if(fork() == 0)   {     sleep(2);     for(i = 0;i&amp;lt;5;i++)       printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);     (*p_map).age = 100;     munmap(p_map,sizeof(people)*10); //实际上，进程终止时，会自动解除映射。     exit();   }   temp = 'a';   for(i = 0;i&amp;lt;5;i++)   {     temp += 1;     memcpy((*(p_map+i)).name, &amp;amp;temp,2);     (*(p_map+i)).age=20+i;   }   sleep(5);   printf( "parent read: the first people,s age is %d\n",(*p_map).age );   printf("umap\n");   munmap( p_map,sizeof(people)*10 );   printf( "umap ok\n" ); } &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;考察程序的输出结果，体会父子进程匿名共享内存：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;child read: the 1 people's age is 20 child read: the 2 people's age is 21 child read: the 3 people's age is 22 child read: the 4 people's age is 23 child read: the 5 people's age is 24 parent read: the first people,s age is 100 umap umap ok &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;div style="clear: both; background-image: url(http://www.ibm.com/i/solid.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; height: 1px; background-position: initial initial; background-repeat: repeat no-repeat; "&gt;&lt;/div&gt;&lt;p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px; "&gt;&lt;a href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html#ibm-pcon" style="color: #4c6e94; display: inline; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 18px; text-decoration: none; background-image: url(http://www.ibm.com/i/v16/icons/u_bold.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: initial; font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat; "&gt;回页首&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="4"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;四、对mmap()返回地址的访问&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;前面对范例运行结构的讨论中已经提到，linux采用的是页式管理机制。对于用mmap()映射普通文件来说，进程会在自己的地址空间新增一块空间，空间大小由mmap()的len参数指定，注意，进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说，能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始，能够有效访问的地址空间大小。超过这个空间大小，内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明：&lt;/p&gt;&lt;br /&gt;&lt;img alt="图 1" border="0" height="169" src="http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/image001.gif" width="580" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; " /&gt;&amp;nbsp;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;注意：文件被映射部分而不是整个文件决定了进程能够访问的空间大小，另外，如果指定文件的偏移部分，一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;#include &amp;lt;sys/mman.h&amp;gt; #include &amp;lt;sys/types.h&amp;gt; #include &amp;lt;fcntl.h&amp;gt; #include &amp;lt;unistd.h&amp;gt; typedef struct{ 	char name[4]; 	int  age; }people; main(int argc, char** argv) { 	int fd,i; 	int pagesize,offset; 	people *p_map; 	 	pagesize = sysconf(_SC_PAGESIZE); 	printf("pagesize is %d\n",pagesize); 	fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777); 	lseek(fd,pagesize*2-100,SEEK_SET); 	write(fd,"",1); 	offset = 0;	//此处offset = 0编译成版本1；offset = pagesize编译成版本2 	p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset); 	close(fd); 	 	for(i = 1; i&amp;lt;10; i++) 	{ 		(*(p_map+pagesize/sizeof(people)*i-2)).age = 100; 		printf("access page %d over\n",i); 		(*(p_map+pagesize/sizeof(people)*i-1)).age = 100; 		printf("access page %d edge over, now begin to access page %d\n",i, i+1); 		(*(p_map+pagesize/sizeof(people)*i)).age = 100; 		printf("access page %d over\n",i+1); 	} 	munmap(p_map,sizeof(people)*10); } &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;如程序中所注释的那样，把程序编译成两个版本，两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间（大小为：pagesize*2-99），版本1的被映射部分是整个文件，版本2的文件被映射部分是文件大小减去一个页面后的剩余部分，不到一个页面大小(大小为：pagesize-99)。程序中试图访问每一个页面边界，两个版本都试图在进程空间中映射pagesize*3的字节数。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;版本1的输出结果如下：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;pagesize is 4096 access page 1 over access page 1 edge over, now begin to access page 2 access page 2 over access page 2 over access page 2 edge over, now begin to access page 3 Bus error		//被映射文件在进程空间中覆盖了两个页面，此时，进程试图访问第三个页面 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;版本2的输出结果如下：&lt;/p&gt;&lt;table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; "&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="font-family: arial, nsimsun, sans-serif; background-color: #f7f7f7 !important; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; border-left-style: solid; border-top-color: #cccccc; border-right-color: #cccccc; border-bottom-color: #cccccc; border-left-color: #cccccc; padding-top: 2px; padding-right: 2px; padding-bottom: 5px; padding-left: 2px; "&gt;&lt;pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow-x: auto; overflow-y: auto; background-image: initial !important; background-attachment: initial !important; background-origin: initial !important; background-clip: initial !important; background-color: #f7f7f7 !important; color: #000000; background-position: initial initial !important; background-repeat: initial initial !important; "&gt;pagesize is 4096 access page 1 over access page 1 edge over, now begin to access page 2 Bus error		//被映射文件在进程空间中覆盖了一个页面，此时，进程试图访问第二个页面 &lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;结论：采用系统调用mmap()实现进程间通信是很方便的，在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容，可以参考一下相关重要数据结构来加深理解。在本专题的后面部分，将介绍系统v共享内存的实现。&lt;/p&gt;&lt;br /&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="resources"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;参考资料&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;[1] Understanding the Linux Kernel, 2nd Edition, By Daniel P. Bovet, Marco Cesati , 对各主题阐述得重点突出，脉络清晰。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;[2] UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。对mmap()有详细阐述。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;[3] Linux内核源代码情景分析（上），毛德操、胡希明著，浙江大学出版社，给出了mmap()相关的源代码分析。&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;[4]mmap()手册&lt;/p&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0.3em; padding-right: 5px; padding-bottom: 0.7em; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="author"&gt;&lt;span style="font-size: 1.5em; font-weight: bold; "&gt;关于作者&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;div ibm-portrait-module=""  ibm-alternate-two"="" style="margin-top: 0px; margin-right: 0px; margin-bottom: 1.2em; margin-left: 0px; width: 710px; border-top-width: 1px; border-top-style: solid; border-top-color: #cccccc; "&gt;&lt;div style="background-image: url(http://www.ibm.com/i/v16/t/container-gradient.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; border-bottom-color: #cccccc; border-bottom-style: solid; border-bottom-width: 1px; border-left-color: #cccccc; border-left-style: solid; border-left-width: 1px; border-right-color: #cccccc; border-right-style: solid; border-right-width: 1px; min-height: 55px; background-position: 0px 100%; background-repeat: repeat no-repeat; "&gt;&lt;p style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; font-size: 0.76em; "&gt;&lt;a name="author1"&gt;&lt;/a&gt;郑彦兴，国防科大攻读博士学位。联系方式：&amp;nbsp;&lt;a href="&amp;#109;&amp;#97;&amp;#105;&amp;#108;&amp;#116;&amp;#111;&amp;#58;&amp;#109;&amp;#108;&amp;#105;&amp;#110;&amp;#117;&amp;#120;&amp;#64;&amp;#49;&amp;#54;&amp;#51;&amp;#46;&amp;#99;&amp;#111;&amp;#109;" style="color: #4c6e94; "&gt;mlinux@163.com&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/span&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/cdo/aggbug/2020246.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/cdo/archive/2011/04/18/2020246.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/cdo/archive/2010/12/08/1900707.html</id><title type="text">完成端口详解</title><summary type="text">完成端口详解(1)  通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。 API与响应规模 通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。</summary><published>2010-12-08T14:00:00Z</published><updated>2010-12-08T14:00:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2010/12/08/1900707.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2010/12/08/1900707.html"/><content type="html">&lt;p&gt;完成端口详解(1) &lt;/p&gt;&#xD;
&lt;p&gt;通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了&amp;#8212;&amp;#8212;创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。&lt;/p&gt;&#xD;
&lt;p&gt;API与响应规模&lt;/p&gt;&#xD;
&lt;p&gt;通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过，像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用，但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化，在Windows NT 和 Windows 2000上，使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。&lt;/p&gt;&#xD;
&lt;p&gt;完成端口&lt;/p&gt;&#xD;
&lt;p&gt;一个完成端口其实就是一个通知队列，由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成，某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后，可以在任何时候与某个完成端口进行关联。&lt;/p&gt;&#xD;
&lt;p&gt;通常情况下，我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是，线程数量等于处理器的数量，不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作，以免线程阻塞。每个线程都将分到一定的CPU时间，在此期间该线程可以运行，然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作，操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说，前一个线程没有充分使用其时间片，当发生这样的情况时，应用程序应该准备其它线程来充分利用这些时间片。&lt;/p&gt;&#xD;
&lt;p&gt;完成端口的使用分为两步。首先创建完成端口，如以下代码所示：&lt;/p&gt;&#xD;
&lt;p&gt;HANDLE　　hIocp;&lt;/p&gt;&#xD;
&lt;p&gt;hIocp = CreateIoCompletionPort(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　INVALID_HANDLE_VALUE,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　NULL,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　(ULONG_PTR)0,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　0);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (hIocp == NULL) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　// Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 完成端口创建后，要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数，第一个参数FileHandle设为套接字的句柄，第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 以下代码创建了一个套接字，并把它和前面创建的完成端口关联起来：&lt;/p&gt;&#xD;
&lt;p&gt;SOCKET　　s;&lt;/p&gt;&#xD;
&lt;p&gt;s = socket(AF_INET, SOCK_STREAM, 0);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (s == INVALID_SOCKET) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　// Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (CreateIoCompletionPort((HANDLE)s,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　　　　　　 hIocp,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　　　　　　 (ULONG_PTR)0,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　　　　　　 0) == NULL)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ???&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意，CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的&amp;#8220;完成键(completion key)&amp;#8221;(译者注：完成键可以是任何数据类型)。每当完成通知到来时，应用程序可以读取相应的完成键，因此，完成键可用来给套接字传递一些背景信息。&lt;/p&gt;&#xD;
&lt;p&gt;在创建了完成端口、将一个或多个套接字与之相关联之后，我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus()函数并返回完成通知。&lt;/p&gt;&#xD;
&lt;p&gt;下面，我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时，要把指向一个overlapped结构的指针包括在其参数中。当操作完成后，我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过，单是根据这个指针所指向的overlapped结构，应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪，你可以自己定义一个OVERLAPPED结构，在其中加入所需的跟踪信息。&lt;/p&gt;&#xD;
&lt;p&gt;无论何时调用重叠操作函数时，总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息，当操作结束后，你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后，可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针(译者注：以上两小段一会是OVERLAPPEDPLUS结构，一会是OVERLAPPED结构，本人也看不太懂，请高手赐教)。&lt;/p&gt;&#xD;
&lt;p&gt;OVERLAPPED 结构的定义如下：&lt;/p&gt;&#xD;
&lt;p&gt;typedef struct _OVERLAPPEDPLUS {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　OVERLAPPED　　　　ol;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　SOCKET　　　　　　s, sclient;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　int　　　　　　　 OpCode;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　WSABUF　　　　　　wbuf;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　DWORD　　　　　　 dwBytes, dwFlags;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　// other useful information&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } OVERLAPPEDPLUS;&lt;/p&gt;&#xD;
&lt;p&gt;#define OP_READ　　 0&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; #define OP_WRITE　　1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; #define OP_ACCEPT　 2&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 下面让我们来看看Figure2里工作者线程的情况。&lt;/p&gt;&#xD;
&lt;p&gt;Figure 2 Worker Thread&lt;/p&gt;&#xD;
&lt;p&gt;DWORD WINAPI WorkerThread(LPVOID lpParam)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　ULONG_PTR　　　 *PerHandleKey;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　OVERLAPPED　　　*Overlap;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　OVERLAPPEDPLUS　*OverlapPlus,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　　　*newolp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　DWORD　　　　　 dwBytesXfered;&lt;/p&gt;&#xD;
&lt;p&gt;while (1)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　ret = GetQueuedCompletionStatus(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　hIocp,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　&amp;amp;dwBytesXfered,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　(PULONG_PTR)&amp;amp;PerHandleKey,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　&amp;amp;Overlap,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　INFINITE);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　if (ret == 0)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　// Operation failed&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　switch (OverlapPlus-&amp;gt; OpCode)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　case OP_ACCEPT:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// Client socket is contained in OverlapPlus.sclient&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// Add client to completion port&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　CreateIoCompletionPort(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　(HANDLE)OverlapPlus-&amp;gt; sclient,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　hIocp,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　(ULONG_PTR)0,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　0);&lt;/p&gt;&#xD;
&lt;p&gt;//　Need a new OVERLAPPEDPLUS structure&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　//　for the newly accepted socket. Perhaps&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　//　keep a look aside list of free structures.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　newolp = AllocateOverlappedPlus();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　if (!newolp)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　// Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　newolp-&amp;gt; s = OverlapPlus-&amp;gt; sclient;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　newolp-&amp;gt; OpCode = OP_READ;&lt;/p&gt;&#xD;
&lt;p&gt;// This function prepares the data to be sent&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　PrepareSendBuffer(&amp;amp;newolp-&amp;gt; wbuf);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　ret = WSASend(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　newolp-&amp;gt; s,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　&amp;amp;newolp-&amp;gt; wbuf,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　1,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　&amp;amp;newolp-&amp;gt; dwBytes,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　0,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　&amp;amp;newolp.ol,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　NULL);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　if (ret == SOCKET_ERROR)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　if (WSAGetLastError() != WSA_IO_PENDING)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　// Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　}&lt;/p&gt;&#xD;
&lt;p&gt;// Put structure in look aside list for later use&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　FreeOverlappedPlus(OverlapPlus);&lt;/p&gt;&#xD;
&lt;p&gt;// Signal accept thread to issue another AcceptEx&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　SetEvent(hAcceptThread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　break;&lt;/p&gt;&#xD;
&lt;p&gt;case OP_READ:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// Process the data read　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// &amp;amp;#8226;&amp;amp;#8226;&amp;amp;#8226;&lt;/p&gt;&#xD;
&lt;p&gt;// Repost the read if necessary, reusing the same&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// receive buffer as before&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　memset(&amp;amp;OverlapPlus-&amp;gt; ol, 0, sizeof(OVERLAPPED));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　ret = WSARecv(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　OverlapPlus-&amp;gt; s,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　&amp;amp;OverlapPlus-&amp;gt; wbuf,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　1,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　&amp;amp;OverlapPlus-&amp;gt; dwBytes,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　&amp;amp;OverlapPlus-&amp;gt; dwFlags,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　&amp;amp;OverlapPlus-&amp;gt; ol,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　NULL);&lt;/p&gt;&#xD;
&lt;p&gt;if (ret == SOCKET_ERROR)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　if (WSAGetLastError() != WSA_IO_PENDING)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　　　// Error&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　break;&lt;/p&gt;&#xD;
&lt;p&gt;case OP_WRITE:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　// Process the data sent, etc.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　break;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　} // switch&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　} // while&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }　// WorkerThread&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中每句柄键(PerHandleKey)变量的内容，是在把完成端口与套接字进行关联时所设置的完成键参数；Overlap参数返回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUS结构的指针。&lt;/p&gt;&#xD;
&lt;p&gt;要记住，如果重叠操作调用失败时(也就是说，返回值是SOCKET_ERROR，并且错误原因不是WSA_IO_PENDING)，那么完成端口将不会收到任何完成通知。如果重叠操作调用成功，或者发生原因是WSA_IO_PENDING的错误时，完成端口将总是能够收到完成通知。&lt;/p&gt;&#xD;
&lt;p&gt;Windows NT和Windows 2000的套接字架构对于开发大响应规模的Winsock应用程序而言，对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。&lt;/p&gt;&#xD;
&lt;p&gt;与其它类型操作系统不同，Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面，而是采用了一种更为底层的API，叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理，以便向应用程序提供套接字仿真(在AFD.SYS文件中实现)，同时负责与底层传输驱动程序对话。&lt;/p&gt;&#xD;
&lt;p&gt;完成端口详解(2) [转]&lt;/p&gt;&#xD;
&lt;p&gt;谁来负责管理缓冲区？&lt;/p&gt;&#xD;
&lt;p&gt;正如上面所说的，应用程序通过Winsock来和传输协议驱动程序交谈，而AFD.SYS负责为应用程序进行缓冲区管理。也就是说，当应用程序调用send()或WSASend()函数来发送数据时，AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值)，然后send()或WSASend()函数立即返回。也可以这么说，AFD.SYS在后台负责把数据发送出去。不过，如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小，那么WSASend()函数会阻塞，直至所有数据发送完毕。&lt;/p&gt;&#xD;
&lt;p&gt;从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据，而且没有超出SO_RCVBUF设定的值，AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时，数据将从内部缓冲拷贝到应用程序提供的缓冲区。&lt;/p&gt;&#xD;
&lt;p&gt;多数情况下，这样的架构运行良好，特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是，尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区)，但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果，避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。&lt;/p&gt;&#xD;
&lt;p&gt;举例来说，一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭，然后发出一个阻塞send()调用。在这样的情况下，系统内核会把应用程序的缓冲区锁定，直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法，实际上却并非如此。想想看，即使远端TCP通知数据已经收到，其实也根本不代表数据已经成功送给客户端应用程序，比如对方可能发生资源不足的情况，导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是，在每个线程中每次只能进行一次发送调用，效率极其低下。&lt;/p&gt;&#xD;
&lt;p&gt;把SO_RCVBUF设为0，关闭AFD.SYS的接收缓冲区也不能让性能得到提升，这只会迫使接收到的数据在比Winsock更低的层次进行缓冲，当你发出receive调用时，同样要进行缓冲区拷贝，因此你本来想避免缓冲区拷贝的阴谋不会得逞。&lt;/p&gt;&#xD;
&lt;p&gt;现在我们应该清楚了，关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用，那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用，那么它将没有必要使用内部缓冲区。&lt;/p&gt;&#xD;
&lt;p&gt;高性能的服务器应用程序可以关闭发送缓冲区，同时不会损失性能。不过，这样的应用程序必须十分小心，保证它总是发出多个重叠发送调用，而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作，那浪费掉两次发送中间的空档时间，总之是要保证传输驱动程序在发送完一个缓冲区后，立刻可以转向另一个缓冲区。&lt;/p&gt;&#xD;
&lt;p&gt;资源的限制条件&lt;/p&gt;&#xD;
&lt;p&gt;在设计任何服务器应用程序时，其强健性是主要的目标。也就是说，你的应用程序要能够应对任何突发的问题，例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题，从容地处理突发性事件。&lt;/p&gt;&#xD;
&lt;p&gt;你可以直接控制的、最基本的资源就是网络带宽。通常，使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制，以最大限度地减少包的丢失。&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 然而，在使用TCP连接时，服务器必须十分小心地控制好，防止网络带宽过载超过一定的时间，否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定，这超出了本文讨论的范围。&lt;/p&gt;&#xD;
&lt;p&gt;虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存，或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存，将有助于控制服务器应用程序的内存开销(原文为&amp;#8220;让服务器应用程序留下的脚印小一点&amp;#8221;)，避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为&amp;#8220;让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中&amp;#8221;)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。&lt;/p&gt;&#xD;
&lt;p&gt;在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭，当应用程序收发数据时，应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存，在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存，而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说，你的程序锁定内存时，不要超出系统规定的内存分页极限。&lt;/p&gt;&#xD;
&lt;p&gt;在Windows NT和2000系统上，所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计，不是你计算内存的依据)。如果你的应用程序不注意这一点，当你的发出太多的重叠收发调用，而且I/O没来得及完成时，就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意，系统会锁定包含你的缓冲区所在的整个内存页面，因此缓冲区靠近页边界时是有代价的(译者理解，缓冲区如果正好超过页面边界，那怕是1个字节，超出的这个字节所在的页面也会被锁定)。&lt;/p&gt;&#xD;
&lt;p&gt;另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域，这块内存用来存储一些供各种内核组件访问的数据，其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。&lt;/p&gt;&#xD;
&lt;p&gt;当应用程序创建一个套接字(或者是类似的打开某个文件)时，内核会从未分页池中分配一定数量的内存，而且在绑定、连接套接字时，内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现，如果你发出某些I/O请求时(例如收发数据)，你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作，你可能需要给这个操作添加一个自定义结构，如前文所提及的)。最后这就可能会造成一定的问题，操作系统会限制未分页内存的用量。&lt;/p&gt;&#xD;
&lt;p&gt;在Windows NT和2000这两种操作系统上，给每个连接分配的未分页内存的具体数量是不同的，未来版本的Windows很可能也不同。为了使应用程序的生命期更长，你就不应该计算对未分页池内存的具体需求量。&lt;/p&gt;&#xD;
&lt;p&gt;你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时，某些与你的应用程序毫无关系的内核驱动就会发疯，甚至造成系统崩溃，特别是当系统中有第三方设备或驱动程序时，更容易发生这样的惨剧(而且无法预测)。同时你还要记住，同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序，因此在设计你的应用程序时，对资源量的预估要特别保守和谨慎。&lt;/p&gt;&#xD;
&lt;p&gt;处理资源不足的问题是十分复杂的，因为发生上述情况时你不会收到特别的错误代码，通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误，首先，把你的应用程序工作配置调整到合理的最大值(译者注：所谓工作配置，是指应用程序各部分运行中所需的内存用量，请参考 &lt;a href="http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp"&gt;http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp&lt;/a&gt;&amp;nbsp; ，关于内存优化，译者另有译文)，如果错误继续出现，那么注意检查是否是网络带宽不足的问题。之后，请确认你没有同时发出太多的收发调用。最后，如果还是收到资源不足的错误，那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间，请关闭应用程序中相当部分的连接，等待系统自行渡过和修正这个瞬时的错误。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;完成端口详解(3) [转]&lt;/p&gt;&#xD;
&lt;p&gt;接受连接请求&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 服务器要做的最普通的事情之一就是接受来自客户端的连接请求。在套接字上使用重叠I/O接受连接的惟一API就是AcceptEx()函数。有趣的是，通常的同步接受函数accept()的返回值是一个新的套接字，而AcceptEx()函数则需要另外一个套接字作为它的参数之一。这是因为AcceptEx()是一个重叠操作，所以你需要事先创建一个套接字(但不要绑定或连接它)，并把这个套接字通过参数传给AcceptEx()。以下是一小段典型的使用AcceptEx()的伪代码：&lt;/p&gt;&#xD;
&lt;p&gt;do {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　-等待上一个 AcceptEx 完成&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　-创建一个新套接字并与完成端口进行关联&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　-设置背景结构等等&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　-发出一个 AcceptEx 请求&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }while(TRUE);&lt;/p&gt;&#xD;
&lt;p&gt;作为一个高响应能力的服务器，它必须发出足够的AcceptEx调用，守候着，一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx才够，就取决于你的服务器程序所期待的通信交通类型。比如，如果进入连接率高的情况(因为连接持续时间较短，或者出现交通高峰)，那么所需要守候的AcceptEx当然要比那些偶尔进入的客户端连接的情况要多。聪明的做法是，由应用程序来分析交通状况，并调整AcceptEx守候的数量，而不是固定在某个数量上。&lt;/p&gt;&#xD;
&lt;p&gt;对于Windows2000，Winsock提供了一些机制，帮助你判定AcceptEx的数量是否足够。这就是，在创建监听套接字时创建一个事件，通过WSAEventSelect()这个API并注册FD_ACCEPT事件通知来把套接字和这个事件关联起来。一旦系统收到一个连接请求，如果系统中没有AcceptEx()正在等待接受连接，那么上面的事件将收到一个信号。通过这个事件，你就可以判断你有没有发出足够的AcceptEx()，或者检测出一个非正常的客户请求(下文述)。这种机制对Windows NT 4.0不适用。&lt;/p&gt;&#xD;
&lt;p&gt;使用AcceptEx()的一大好处是，你可以通过一次调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两件事情。也就是说，如果客户端在发出连接的同时传输数据，你的AcceptEx()调用在连接创建并接收了客户端数据后就可以立刻返回。这样可能是很有用的，但是也可能会引发问题，因为AcceptEx()必须等全部客户端数据都收到了才返回。具体来说，如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数，那么AcceptEx()不再是一项原子型的操作，而是分成了两步：接受客户连接，等待接收数据。当缺少一种机制来通知你的应用程序所发生的这种情况：&amp;#8220;连接已经建立了，正在等待客户端数据&amp;#8221;，这将意味着有可能出现客户端只发出连接请求，但是不发送数据。如果你的服务器收到太多这种类型的连接时，它将拒绝连接更多的合法客户端请求。这就是黑客进行&amp;#8220;拒绝服务&amp;#8221;攻击的常见手法。&lt;/p&gt;&#xD;
&lt;p&gt;要预防此类攻击，接受连接的线程应该不时地通过调用getsockopt()函数(选项参数为SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设置为套接字被连接的时间，或者设置为-1(代表套接字尚未建立连接)。这时，WSAEventSelect()的特性就可以很好地利用来做这种检查。如果发现连接已经建立，但是很久都没有收到数据的情况，那么就应该终止连接，方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意，在多数非紧急情况下，如果套接字已经传递给AcceptEx()并开始守候，但还未建立连接，那么你的应用程序不应该关闭它们。这是因为即使关闭了这些套接字，出于提高系统性能的考虑，在连接进入之前，或者监听套接字自身被关闭之前，相应的内核模式的数据结构也不会被干净地清除。&lt;/p&gt;&#xD;
&lt;p&gt;发出AcceptEx()调用的线程，似乎与那个进行完成端口关联操作、处理其它I/O完成通知的线程是同一个，但是，别忘记线程里应该尽力避免执行阻塞型的操作。Winsock2分层结构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思，抱歉)。每个AcceptEx()调用都需要创建一个新套接字，所以最好有一个独立的线程专门调用AcceptEx()，而不参与其它I/O处理。你也可以利用这个线程来执行其它任务，比如事件记录。&lt;/p&gt;&#xD;
&lt;p&gt;有关AcceptEx()的最后一个注意事项：要实现这些API，并不需要其它提供商提供的Winsock2实现。这一点对微软特有的其它API也同样适用，比如TransmitFile()和GetAcceptExSockAddrs()，以及其它可能会被加入到新版Windows的API. 在WindowsNT和2000上，这些API是在微软的底层提供者DLL(mswsock.dll)中实现的，可通过与mswsock.lib编译连接进行调用，或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针。&lt;/p&gt;&#xD;
&lt;p&gt;如果在没有事先获得函数指针的情况下直接调用函数(也就是说，编译时静态连接mswsock.lib，在程序中直接调用函数)，那么性能将很受影响。因为&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; AcceptEx()被置于Winsock2架构之外，每次调用时它都被迫通过WSAIoctl()取得函数指针。要避免这种性能损失，需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。&lt;/p&gt;&#xD;
&lt;p&gt;参见Figure 3 套接字架构：&lt;/p&gt;&#xD;
&lt;p&gt;application&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ||/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; winsock 2.0 dll (ws2_32.dll)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ||/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; layered/Base Providers&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; RSVP | Proxy | Default Microsoft Providers (mswsock.dll/msafd.dll)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ||/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Windows Sockets kernel-mode driver (afd.sys)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ||/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　/&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Tramsport Protocols&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TCP/IP | ATM | Other&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; TransmitFile 和 TransmitPackets &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Winsock&lt;/p&gt;&#xD;
&lt;p&gt;提供两个专门为文件和内存数据传输进行了优化的函数。其中TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用，而TransmitPackets()则将在未来版本的Windows中实现。&lt;/p&gt;&#xD;
&lt;p&gt;TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是，先调用CreateFile()打开一个文件，然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率，因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile()，那么只需要给它一个已打开文件的句柄和要发送的字节数，而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次，然后TransmitFile()时再发生一次。这样效率就高多了。&lt;/p&gt;&#xD;
&lt;p&gt;TransmitPackets()比TransmitFile()更进一步，它允许用户只调用一次就可以发送指定的多个文件和内存缓冲区。函数原型如下：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; BOOL TransmitPackets(&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　SOCKET hSocket,　　　　　　　　　　　　　　 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　LPTRANSMIT_PACKET_ELEMENT lpPacketArray,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　DWORD nElementCount,　　　　　　　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　DWORD nSendSize,　　　　　　　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　LPOVERLAPPED lpOverlapped,　　　　　　　　　&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　DWORD dwFlags　　　　　　　　　　　　　　　 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中，lpPacketArray是一个结构的数组，其中的每个元素既可以是一个文件句柄或者内存缓冲区，该结构定义如下：&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; typedef struct _TRANSMIT_PACKETS_ELEMENT { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　DWORD dwElFlags; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　DWORD cLength; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　union {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　struct {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　LARGE_INTEGER　　 nFileOffset;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　HANDLE　　　　　　hFile;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　};&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　　　　　PVOID　　　　　　 pBuffer;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 　　};&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } TRANSMIT_FILE_BUFFERS;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 其中各字段是自描述型的(self explanatory)。&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; dwElFlags字段：指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE和TF_ELEMENT_MEMORY指定)；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; cLength字段：指定将从数据源发送的字节数(如果是文件，这个字段值为0表示发送整个文件)；&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 结构中的无名联合体：包含文件句柄的内存缓冲区(以及可能的偏移量)。&lt;/p&gt;&#xD;
&lt;p&gt;使用这两个API的另一个好处，是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后，就会在传输层级别断开连接，这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程，将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。&lt;/p&gt;&#xD;
&lt;p&gt;这两个API也都有一个共同的弱点：Windows NT Workstation 或 Windows 2000 专业版中，函数每次只能处理两个调用请求，只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;完成端口详解(4) [转]&lt;/p&gt;&#xD;
&lt;p&gt;放在一起看看&lt;/p&gt;&#xD;
&lt;p&gt;以上各节中，我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢？其实，这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时，那么你越能够避开瓶颈问题。&lt;/p&gt;&#xD;
&lt;p&gt;来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么，服务器将需要创建一个监听套接字，把它与某个完成端口进行关联，为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据，所以如果我们准备好接收缓冲区会使事情变得更为容易。当然，不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。&lt;/p&gt;&#xD;
&lt;p&gt;该设计中有一个重要的问题要考虑，我们应该允许多少个AcceptEx()进行守候。这是因为，每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区，那么内存中将会出现很多被锁定的页面(前文说过了，每个重叠操作都会消耗一小部分未分页内存池，同时还会锁定所有涉及的缓冲区)。这个问题很难回答，没有一个确切的答案。最好的方法是把这个值做成可以调整的，通过反复做性能测试，你就可以得出在典型应用环境中最佳的值。&lt;/p&gt;&#xD;
&lt;p&gt;好了，当你测算清楚后，下面就是发送数据的问题了，考虑的重点是你希望服务器同时处理多少个并发的连接。&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 通常情况下，服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多，所消耗的未分页内存池也越多；等候处理的发送调用越多，被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。&lt;/p&gt;&#xD;
&lt;p&gt;对于上述环境，通常不需要关闭单个套接字的缓冲区，因为只在AcceptEx()中有一次接收数据的操作，而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是，如果客户机与服务器交互的方式变一变，客户机在发送了一次数据之后，还需要发送更多的数据，在这种情况下关闭接收缓冲就不太妙了，除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。&lt;/p&gt;&#xD;
&lt;p&gt;结论&lt;/p&gt;&#xD;
&lt;p&gt;开发大响应规模的Winsock服务器并不是很可怕，其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量，防止出现未分页内存池被耗尽，这才是最主要的挑战。按照我们前面讨论的一些原则，你就可以开发出大响应规模的服务器应用程序。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;本文来自CSDN博客，转载请标明出处：&lt;a href="http://blog.csdn.net/yejiansnake/archive/2008/05/30/2497207.aspx"&gt;http://blog.csdn.net/yejiansnake/archive/2008/05/30/2497207.aspx&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/cdo/aggbug/1900707.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/cdo/archive/2010/12/08/1900707.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/cdo/archive/2010/11/28/1890053.html</id><title type="text">转：Windows消息编程</title><summary type="text">Windows消息编程韩耀旭下载源代码 本文主要包括以下内容：1、简单理解Windows的消息2、通过一个简单的Win32程序理解Windows消息3、通过几个Win32程序实例进一步深入理解Windows消息4、队列消息和非队列消息5、WM_COMMAND和WM_NOTIFY6、MFC的消息映射7、消息反射机制1、简单理解Windows的消息消息，就是指Windows发出的一个通知，告诉应用程序...</summary><published>2010-11-27T17:42:00Z</published><updated>2010-11-27T17:42:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2010/11/28/1890053.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2010/11/28/1890053.html"/><content type="html">&lt;strong&gt;&lt;br /&gt;&#xD;
&lt;table border="0" cellspacing="0" cellpadding="0" width="100%"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&#xD;
&lt;td&gt;&#xD;
&lt;p align="center"&gt;&lt;strong&gt;Windows消息编程&lt;/strong&gt;&lt;br /&gt;韩耀旭&lt;/p&gt;&lt;a href="http://www.vckbase.com/code/downcode.asp?id=3235"&gt;下载源代码&lt;/a&gt; &#xD;
&lt;p&gt;本文主要包括以下内容：&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;1、简单理解Windows的消息&lt;br /&gt;2、通过一个简单的Win32程序理解Windows消息&lt;br /&gt;3、通过几个Win32程序实例进一步深入理解Windows消息&lt;br /&gt;4、队列消息和非队列消息&lt;br /&gt;5、WM_COMMAND和WM_NOTIFY&lt;br /&gt;6、MFC的消息映射&lt;br /&gt;7、消息反射机制&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;1、简单理解Windows的消息&lt;/p&gt;&#xD;
&lt;p&gt;消息，就是指Windows发出的一个通知，告诉应用程序某个事情发生了。&lt;br /&gt;举个例子来说，鼠标单击某应用程序的一个按钮。这时，Windows（操作系统）给应用程序发送这个消息，通知应用程序该按钮被点击，应用程序将进行相应反应。&lt;br /&gt;消息一般用一个32位的数来标识，这个数唯一地标识这个消息。这些消息的标识符一般在头文件winuser.h 中定义，如：&lt;/p&gt;&#xD;
&lt;p&gt;#define WM_PAINT 0x000F&lt;br /&gt;#define WM_QUIT 0x0012&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;其实消息本身是一个MSG结构。MSG结构定义如下：&lt;/p&gt;&#xD;
&lt;p&gt;typedef struct tagMSG { &lt;br /&gt;HWND hwnd; //接受消息的窗口句柄&lt;br /&gt;UINT message; //消息标识符&lt;br /&gt;WPARAM wParam; //32位附加信息&lt;br /&gt;LPARAM lParam; //32位附加信息&lt;br /&gt;DWORD time; //消息创建的时间&lt;br /&gt;POINT pt; //消息创建时鼠标在屏幕坐标系中的位置&lt;br /&gt;} MSG;&lt;/p&gt;&#xD;
&lt;p&gt;也就是说，对于任何一个消息，都有一个MSG变量与之对应，该变量包含了消息的相关信息。而我们在一般情况下，只使用消息的消息标识符，该标识符也唯一地代表了这个消息。&lt;br /&gt;举个例子来说，当我们收到一个字符消息的时候，message成员变量的值就是WM_CHAR，但用户到底输入的是什么字符，那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。&lt;br /&gt;Windows操作系统已经给我们定义了大量的消息，这些消息我们称为系统消息。除了系统消息，我们还可以自己定义消息，即自定义消息。&lt;br /&gt;值小于0x0400的消息都是系统消息，自定义消息一般都大于0x0400。&lt;br /&gt;系统消息取值一般有如下规律，如表1：&lt;/p&gt;&#xD;
&lt;table border="0" cellspacing="1" cellpadding="1" width="507" bgcolor="#99cc99" height="14"&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;范围&lt;/td&gt;&#xD;
&lt;td&gt;意义&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0001&amp;#8212;&amp;#8212;0x0087&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;主要是窗口消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x00A0&amp;#8212;&amp;#8212;0x00A9&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;非客户区消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0100&amp;#8212;&amp;#8212;0x0108&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;键盘消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0111&amp;#8212;&amp;#8212;0x0126&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;菜单消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0132&amp;#8212;&amp;#8212;0x0138&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;颜色控制消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0200&amp;#8212;&amp;#8212;0x020A&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;鼠标消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0211&amp;#8212;&amp;#8212;0x0213&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;菜单循环消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0220&amp;#8212;&amp;#8212;0x0230&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;多文档消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x03E0&amp;#8212;&amp;#8212;0x03E8&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;DDE消息&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0400&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;WM_USER&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;tr bgcolor="#ffffff"&gt;&#xD;
&lt;td&gt;0x0400&amp;#8212;&amp;#8212;0x7FFF&lt;br /&gt;&lt;/td&gt;&#xD;
&lt;td&gt;自定义消息&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&#xD;
&lt;p&gt;表1&lt;/p&gt;&#xD;
&lt;p&gt;在WINUSER.H中，我们有定义：&lt;/p&gt;&#xD;
&lt;p&gt;#define WM_USER 0x0400&lt;/p&gt;&#xD;
&lt;p&gt;对于自定义消息，我们一般采用WM_USER 加一个整数值的方法定义自定义消息，如：&lt;/p&gt;&#xD;
&lt;p&gt;#define WM_RECVDATA WM_USER + 1&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;如果您初次接触Windows编程，或是初次接触Windows消息，对于上述解释可能没有看懂，这也不要着急，后面的实例将会逐步带您对Windows的消息编程有一个了解。&lt;/p&gt;&#xD;
&lt;p&gt;2、通过一个简单的Win32程序理解Windows消息&lt;br /&gt;例程1：一个简单的Win32程序代码（见附带源码 工程M1）&lt;br /&gt;打开VC++ 6.0，新建一个Win32 Application，工程名为M1，在该工程添加C++ Source File，文件名为M1，在该文件中添加如下代码：&lt;/p&gt;&lt;pre&gt;//一个简单的Win32应用程序&#xD;
&#xD;
//通过这个简单的实例讲解Windows消息是如何传递的&#xD;
&#xD;
#include &amp;lt;windows.h&amp;gt;&#xD;
&#xD;
//声明窗口过程函数&#xD;
&#xD;
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);&#xD;
&#xD;
//定义一个全局变量，作为窗口类名&#xD;
&#xD;
TCHAR szClassName[] = TEXT("SimpleWin32");&#xD;
&#xD;
//应用程序主函数&#xD;
&#xD;
int WINAPI WinMain (HINSTANCE hInstance,&#xD;
                                  HINSTANCE hPrevInstance,&#xD;
                                  LPSTR szCmdLine,&#xD;
                                  int iCmdShow)&#xD;
{&#xD;
&#xD;
    //窗口类&#xD;
    WNDCLASS wndclass;&#xD;
&#xD;
    //当窗口水平方向的宽度和垂直方向的高度变化时重绘整个窗口&#xD;
    wndclass.style = CS_HREDRAW|CS_VREDRAW;&#xD;
&#xD;
    //关联窗口过程函数&#xD;
    wndclass.lpfnWndProc = WndProc;&#xD;
    wndclass.cbClsExtra = 0;&#xD;
    wndclass.cbWndExtra = 0;&#xD;
    wndclass.hInstance = hInstance;//实例句柄&#xD;
    wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//图标&#xD;
    wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);//光标&#xD;
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//画刷&#xD;
    wndclass.lpszMenuName  = NULL;//菜单&#xD;
    wndclass.lpszClassName = szClassName;//类名称&#xD;
&#xD;
    //注册窗口类&#xD;
    if(!RegisterClass (&amp;amp;wndclass))&#xD;
    {&#xD;
           MessageBox (NULL, TEXT ("RegisterClass Fail!"), &#xD;
                   szClassName, MB_ICONERROR);&#xD;
           return 0;&#xD;
    }&#xD;
&#xD;
 &#xD;
&#xD;
    //建立窗口&#xD;
    HWND hwnd;&#xD;
    hwnd = CreateWindow(szClassName,//窗口类名称&#xD;
           TEXT ("The Simple Win32 Application"),//窗口标题 &#xD;
           WS_OVERLAPPEDWINDOW,//窗口风格,即通常我们使用的windows窗口样式&#xD;
           CW_USEDEFAULT,//指定窗口的初始水平位置,即屏幕坐标系的窗口的左上角的X坐标&#xD;
           CW_USEDEFAULT,//指定窗口的初始垂直位置,即屏幕坐标系的窗口的左上角的Y坐标&#xD;
           CW_USEDEFAULT,//窗口的宽度&#xD;
           CW_USEDEFAULT,//窗口的高度&#xD;
           NULL,//父窗口句柄&#xD;
           NULL,//窗口菜单句柄&#xD;
           hInstance,//实例句柄&#xD;
           NULL);&#xD;
&#xD;
    ShowWindow(hwnd,iCmdShow);//显示窗口&#xD;
    UpdateWindow(hwnd);//立即显示窗口&#xD;
&#xD;
    //消息循环&#xD;
    MSG msg;&#xD;
    while(GetMessage(&amp;amp;msg,NULL,0,0))//从消息队列中取消息 &#xD;
    {&#xD;
           TranslateMessage (&amp;amp;msg);              //转换消息&#xD;
           DispatchMessage (&amp;amp;msg);               //派发消息&#xD;
    }&#xD;
    return msg.wParam;&#xD;
}&#xD;
&#xD;
//消息处理函数&#xD;
&#xD;
//参数:窗口句柄，消息，消息参数，消息参数&#xD;
&#xD;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)&#xD;
{&#xD;
    //处理感兴趣的消息&#xD;
    switch (message)&#xD;
    {&#xD;
    case WM_DESTROY:&#xD;
           //当用户关闭窗口，窗口销毁，程序需结束，发退出消息，以退出消息循环&#xD;
           PostQuitMessage(0);&#xD;
           return 0;&#xD;
    }&#xD;
    //其他消息交给由系统提供的缺省处理函数&#xD;
    return ::DefWindowProc (hwnd, message, wParam, lParam);&#xD;
}&#xD;
&lt;/pre&gt;这是一个非常简单的Win32小程序，编译运行会显示一个窗口，关闭窗口程序会结束运行。 代码中已经做了简单注解，这里我们不作过多说明。我在这里再着重讲解一下消息循环部分。 &lt;pre&gt;//消息循环&#xD;
&#xD;
MSG msg;&#xD;
while(GetMessage(&amp;amp;msg,NULL,0,0))//从消息队列中取消息 &#xD;
{&#xD;
      TranslateMessage (&amp;amp;msg);              //转换消息&#xD;
      DispatchMessage (&amp;amp;msg);               //派发消息&#xD;
}&lt;/pre&gt;&#xD;
&lt;p&gt;这段代码是消息循环部分，它的作用是循环检测消息队列（不懂消息队列？没关系，后面会详细说明）中的消息并进行处理。这段代码涉及GetMessage，TranslateMessage，DispatchMessage这三个函数，相关函数还有PeekMessage,WaitMessage。在此，我们先对这五个函数简单讲解。&lt;/p&gt;&#xD;
&lt;p&gt;1、GetMessage&lt;/p&gt;&#xD;
&lt;p&gt;函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);&lt;/p&gt;&#xD;
&lt;p&gt;参数：&lt;/p&gt;&#xD;
&lt;p&gt;lpMsg：一个指向MSG结构的指针，该结构用于存放从消息队列里取出的消息。&lt;br /&gt;hWnd：窗口句柄。如果该参数是非零值，则GetMessage只检索该窗口（也包括其子窗口）消息，如果为零，则GetMessage检索整个进程内的消息。&lt;br /&gt;wMsgFilterMin：指定被检索的最小消息值，也就是消息范围的下界限参数。&lt;br /&gt;wMsgFilterMax：上界限参数。如果wMsgFilterMin和wMsgFilterMax都为零，则不进行消息过滤，GetMessage检索所有有效的消息。&lt;/p&gt;&#xD;
&lt;p&gt;返回值&lt;/p&gt;&#xD;
&lt;p&gt;GetMessage检索到WM_QUIT消息，返回值是零；其它情况，返回非零值。&lt;/p&gt;&#xD;
&lt;p&gt;函数功能：&lt;/p&gt;&#xD;
&lt;p&gt;这个API函数用来从消息队列中&amp;#8220;摘取&amp;#8221;一个消息，放到lpMsg所指的变量里。（注：如果所取窗口的消息队列中没有消息，则程序会暂停在GetMessage(&amp;#8230;) 函数里，不会返回。）&lt;br /&gt;再通俗一点讲解GetMessage函数：&lt;br /&gt;当程序执行GetMessage()的时候，会检查消息队列，如果有消息在消息队列里，它取出该消息，将该消息填充到lpMsg所指的MSG结构，并返回TRUE值。如果此时消息队列里没有消息（消息队列为空），它会将线程阻塞，也就是将控制权交给系统，直到消息队列中有内容时，才唤醒线程继续执行。&lt;br /&gt;对于GetMessage()函数，还有一点需要说明，就是当从消息队列中取出的消息是WM_QUIT时，函数返回值是0。我们一般利用这一点退出消息循环，结束程序。&lt;/p&gt;&#xD;
&lt;p&gt;如语句：&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;while(GetMessage(&amp;amp;msg,NULL,0,0))&#xD;
&#xD;
&amp;#8230;&amp;#8230;&#xD;
&lt;/pre&gt;&#xD;
&lt;p&gt;2 、PeekMessage&lt;/p&gt;&#xD;
&lt;p&gt;函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;BOOL PeekMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT wRemoveMsg);&lt;/p&gt;&#xD;
&lt;p&gt;参数：&lt;/p&gt;&#xD;
&lt;p&gt;lpMsg、hWnd、wMsgFilterMin、wMsgFilterMax这四个参数的意义和GetMessage对应参数的意义相同，在此不再赘述。&lt;br /&gt;wRemoveMsg：这个参数决定读消息时是否删除消息，可选值有PM_NOREMOVE和PM_REMOVE。如果您选PM_NOREMOVE，执行该函数后消息仍然留在消息队列（我称为读消息）；如果您选PM_REMOVE，执行该函数后将在消息队列中移除该消息（同GetMessage()）。&lt;/p&gt;&#xD;
&lt;p&gt;返回值：&lt;/p&gt;&#xD;
&lt;p&gt;消息队列中有消息，返回值为TRUE；消息队列中没有消息，返回值为FALSE。&lt;/p&gt;&#xD;
&lt;p&gt;函数功能：&lt;/p&gt;&#xD;
&lt;p&gt;PeekMessage()也是从消息队列中取消息，但它是GetMessage()不同，主要在以下两点：&lt;/p&gt;&#xD;
&lt;p&gt;（一）、GetMessage()只能从消息队列中取走消息，也就是说，GetMessage()执行后，该消息将从消息队列中移除。&lt;br /&gt;PeekMessage()可以从消息队列中取走消息。也可以读消息，让消息继续留在消息队列里。&lt;/p&gt;&#xD;
&lt;p&gt;（二）、当消息队列中没有消息时，GetMessage()将会阻塞线程，等待消息；而PeekMessage()与GetMessage()不同，它执行后会立刻返回，消息队列中有消息时，返回值为TRUE；消息队列中没有消息时，返回值为FALSE。&lt;/p&gt;&#xD;
&lt;p&gt;3 、WaitMessage&lt;/p&gt;&#xD;
&lt;p&gt;函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;BOOL WaitMessage(VOID);&lt;/p&gt;&#xD;
&lt;p&gt;函数功能：&lt;/p&gt;&#xD;
&lt;p&gt;这个函数的作用是当消息队列中没有消息时，将控制权交给其它线程。该函数将会使线程挂起，直到消息队列中又有新消息。&lt;br /&gt;这个函数专门和PeekMessage配合使用，当消息队列中没有消息时，挂起线程，等待消息队列中新消息的到来，这样可以减轻CPU的运算负担。&lt;/p&gt;&#xD;
&lt;p&gt;4 、TranslateMessage &lt;/p&gt;&#xD;
&lt;p&gt;函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;BOOL TranslateMessage（CONST MSG*lpMsg）；&lt;/p&gt;&#xD;
&lt;p&gt;参数：&lt;/p&gt;&#xD;
&lt;p&gt;IpMsg：指向MSG结构的指针，该结构是函数GetMessage或PeekMessage从消息队列里取得的消息。&lt;br /&gt;　 函数功能：该函数将虚拟键消息转换为字符消息。字符消息被寄送到调用线程的消息队列里，当下一次线程调用函数GetMessage或PeekMessage时被读出。&lt;br /&gt;什么是虚拟键码呢？Windows为了方便输入管理，减少程序对设备的依赖性，将键盘上所有的按键都用一个两位十六进制数对应，这些数称为虚拟键码。虚拟键码一般以VK_开头，如：Esc键对应的虚拟键码是VK_ESCAPE；空格键对应的虚拟键码是VK_SPACE；VK_LWIN与左边的Windows徽标键相对应。&lt;br /&gt;当一个按键被按下时，会触发WM_KEYDOWN消息， WM_KEYDOWN消息的wParam参数值就是虚拟键值。通过这个值就可以判断哪个键被按下了。&lt;br /&gt;为什么我们要把虚拟键码转换为字符码呢？&lt;br /&gt;比如我们按下了&amp;#8216;A&amp;#8217;键，此时我们得到的字符可能是&amp;#8216;A&amp;#8217;，也可能是小写的&amp;#8216;a&amp;#8217;，这由当时的大写状态（Caps Lock）以及是否同时按下了Shift键有关。TranslateMessage（）函数的作用就是不用我们考虑这些问题，而是根据这些情况，自动返回一个ASCII码值，以方便用户使用。&lt;br /&gt;并不是所有的虚拟键码值都会Translate成字符码。字母、数字键都有字符码相对应，而像方向箭头键、F1&amp;#8212;F12功能键这些按键就没有字符码相对应。当虚拟键码需要转化成字符码时，TranslateMessage（）函数就在消息队列里放一条WM_CHAR消息，WM_CHAR消息的wParam参数值就是转换后的ASCII码值。&lt;/p&gt;&#xD;
&lt;p&gt;5、DispatchMessage &lt;/p&gt;&#xD;
&lt;p&gt;函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;LONG DispatchMessage(CONST MSG *lpmsg);&lt;/p&gt;&#xD;
&lt;p&gt;函数功能：&lt;br /&gt;它的作用很简单，就是分派消息到窗口的消息处理函数去执行。&lt;br /&gt;了解了这5个函数，消息循环这段代码就不难理解：&lt;/p&gt;&#xD;
&lt;p&gt;GetMessage()从消息队列中取消息，对取出的消息进行转换(TranslateMessage)，对于能够将虚拟键码转化成字符码的消息，会在消息队列里放一条WM_CHAR消息，最后将消息发送到相应的消息处理函数进行处理。循环执行这个处理过程，直到收到WM_QUIT消息，才退出循环，结束程序。&lt;/p&gt;&#xD;
&lt;p&gt;3、通过几个Win32程序实例进一步深入理解Windows消息&lt;/p&gt;&#xD;
&lt;p&gt;例程2：对比使用GetMessage和PeekMessage处理消息循环（见附带源码 工程M2）&lt;br /&gt;同工程M1，新建工程M2，将工程M1的源代码全部拷贝到M2，并将消息循环部分的代码改为：&lt;/p&gt;&lt;pre&gt;//消息循环&#xD;
&#xD;
MSG msg;&#xD;
&#xD;
while(true)&#xD;
{&#xD;
      if(PeekMessage(&amp;amp;msg,NULL,0,0,PM_REMOVE)) //从消息队列中取消息&#xD;
      {&#xD;
              if(msg.message == WM_QUIT)&#xD;
                    break;&#xD;
              TranslateMessage (&amp;amp;msg);           //转换消息&#xD;
              DispatchMessage (&amp;amp;msg);            //派发消息&#xD;
       }&#xD;
       else&#xD;
              WaitMessage();&#xD;
&#xD;
} //End of while(true)&#xD;
&lt;/pre&gt;&#xD;
&lt;p&gt;编译、运行工程M2，观察运行效果，可以看出，使用PeekMessage处理消息循环同样能够达到与GetMessage相同的效果。&lt;br /&gt;PeekMessage处理消息循环比GetMessage还要灵活，尤其体现在游戏编程中。游戏编程者不希望玩家在没有键盘或鼠标输入时游戏是静止不动的，他们希望怪兽从后面冲出来，围攻玩家，追捕玩家。为了做到这样的效果，需要这样一种消息循环：当遇到需要处理的消息时去处理消息，其余的时间都让程序代码自动产生激烈的场面。&lt;br /&gt;下面的例程3将模拟这种消息循环。&lt;br /&gt;例程3：模拟演示游戏编程如何进行消息处理（见附带源码工程M3）。&lt;br /&gt;详细的代码参看工程M3，编译并执行，您会发现程序不停地自己画圆，这模拟游戏自动产生激烈的场面。当您按下上、下、左、右箭头键，您就会发现您在相应的方向画线，这模拟游戏程序及时处理玩家的消息。&lt;/p&gt;&#xD;
&lt;p&gt;4、队列消息和非队列消息&lt;br /&gt;Windows把消息分为两种：一种是需要立即处理的消息，另一种是不需要立即处理的消息。&lt;br /&gt;对于需要立即处理的消息，Windows直接把它送给窗口的消息处理函数进行处理，这类消息我们叫做非队列消息；&lt;br /&gt;而对于不需要立即处理的消息，Windows会把它发送给应用程序的消息队列进行排队，由应用程序逐个进行处理，我们把这类消息叫做队列消息。&lt;br /&gt;为了更清楚地说明这个问题，我们参看图1：&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image001.gif" width="588" height="511"  alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;图1&lt;/p&gt;&#xD;
&lt;p&gt;图1的解释：&lt;/p&gt;&#xD;
&lt;p&gt;1、Windows操作系统有一个消息队列，它存放操作系统收到的消息。如：当按键被按下，键盘会发送一个消息到操作系统的消息队列。&lt;br /&gt;2、操作系统把系统消息队列中的消息分派到各个应用程序的消息队列。如果它是第1个应用程序的消息，操作系统把它发给第1个应用程序，把它放在第1个应用程序的消息队列；如果它是第2个应用程序的消息，发送给第2个程序的消息队列。&lt;br /&gt;3、应用程序的消息循环从自己的消息队列中取消息，取出的消息调用窗口过程函数进行处理。&lt;br /&gt;4、PostMessage是寄送消息，函数执行后立即返回。寄送的消息是队列消息，放在程序的消息队列中排队处理。一般来说，新寄送的消息排在消息队列的末尾，这样可以保证窗口以先进先出的顺序处理消息。&lt;br /&gt;SendMessage是发送消息，它发出的消息是非队列消息，直接调用窗口过程函数处理。SendMessage函数一直等消息处理完成后才返回。&lt;/p&gt;&#xD;
&lt;p&gt;我们有必要再专门学习一下SendMessage和PostMessage函数。&lt;/p&gt;&#xD;
&lt;p&gt;SendMessage的函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);&lt;/p&gt;&#xD;
&lt;p&gt;这个函数向窗口发送一条消息，一直等到消息被处理之后才返回。也就是说，接收消息的窗口的窗口函数立即被调用。函数的返回值由接收消息的窗口的窗口函数返回。&lt;/p&gt;&#xD;
&lt;p&gt;PostMessage的函数原型：&lt;/p&gt;&#xD;
&lt;p&gt;BOOL PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam);&lt;/p&gt;&#xD;
&lt;p&gt;该函数把一条消息放置到创建hWnd窗口的线程的消息队列中，该函数不等消息被处理就马上将控制返回。&lt;br /&gt;从上面这两个函数，我们可以看出消息的发送方式和寄送方式的区别：被发送的消息会被立即处理，处理完毕后函数才返回；被寄送的消息不会被立即处理，他被放到一个先进先出的队列中，按次序等候处理，而且函数放置消息后立即返回。&lt;br /&gt;以寄送方式发送的消息通常是与用户输入事件相对应的，因为这些事件不是十分紧迫，可以进行缓冲处理，例如鼠标、键盘消息都是寄送消息。应用程序调用系统函数，系统一般会发送非队列消息。例如，当程序调用SetWindowPos，系统会发送WM_WINDOWPOSCHANGED消息。&lt;br /&gt;例程M4，测试消息队列的容量（见附带源码工程M4）&lt;br /&gt;代码中已经作了注解，编译、运行程序，您就会发现消息队列的最大容量是10000。&lt;br /&gt;例程M5，用记事本查看消息队列和窗口过程函数处理的消息&lt;br /&gt;这个例程的出发点是利用记事本分别捕获消息队列中的消息和窗口过程函数处理过的消息。&lt;br /&gt;该例程还演示了PostMessage和SendMessage的不同。&lt;br /&gt;由于该例程相对复杂一些，例程中的注解也相对多一些。编译、运行程序，弹出如下窗口： &lt;/p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image002.png" width="275" height="264"  alt="" /&gt;&lt;br /&gt;&#xD;
&lt;p&gt;关闭该窗口，退出运行，检查M5例程所在的路径，您就会发现多了两个文件MessageQueue.txt和MessageWndProc.txt，MessageQueue.txt文件中记录的是应用程序M5从运行到关闭消息队列中处理过的消息；MessageWndProc.txt中记录的M5窗口过程函数处理过的消息。&lt;br /&gt;打开MessageQueue.txt文件，如下图：&lt;/p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image004.png" width="444" height="318"  alt="" /&gt;&lt;br /&gt;&#xD;
&lt;p&gt;文件中记录了消息队列中的各个消息以及消息的ID号，其中有一条消息是WM_POSTMESSAGE，这说明PostMessage寄送的WM_POSTMESSAGE消息确实放到了消息队列中。&lt;br /&gt;再打开MessageWndProc.txt文件，如下图：&lt;/p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image006.png" width="441" height="318"  alt="" /&gt; &lt;br /&gt;&#xD;
&lt;p&gt;文件中记录了窗口过程处理的各个消息和消息的ID号，其中有两条消息WM_POSTMESSAGE和WM_SENDMESSAGE，这说明了两个问题：WM_POSTMESSAGE消息从消息队列取出，再次派发到窗口过程函数处理；SendMessage发送的WM_SENDMESSAGE消息，没有经过消息队列，直接送到窗口过程函数处理。&lt;/p&gt;&#xD;
&lt;p&gt;5、WM_COMMAND和WM_NOTIFY&lt;/p&gt;&#xD;
&lt;p&gt;控件通知消息，是指这样一种消息，一个窗口内的控件发生了一些事情，需要通知父窗口。当用户与控件窗口交互时，控件通知消息就会从控件窗口发送到它的主窗口，这种消息一般不是为了处理用户命令，而是为了让主窗口能够改变控件。&lt;br /&gt;WM_COMMAND和WM_NOTIFY都是控件通知消息。&lt;br /&gt;在最初的Windows 3.x中，还没有WM_NOTIFY，只存在WM_COMMAND消息，wParam参数中包含一个通知码和控件ID，lParam中包含控件句柄。这样一来，wParam和lParam都被填充了，没有额外的空间来传递一些其它信息，如鼠标按下的位置和时间。&lt;br /&gt;为了解决这个问题，Windows 3.x就提出了一个解决策略，那就是给一些消息添加一些附加消息，比如控件自画用到的DRAWITEMSTRUCT等，这样，不同的消息附加的内容不同，结果是非常混乱。&lt;br /&gt;在Win32中，微软又提出了一个更好的解决方案，引进了NMHDR结构。这个结构的引进把消息统一起来，利用它可以传递各种复杂的消息。&lt;br /&gt;NMHDR结构内容如下：&lt;/p&gt;&#xD;
&lt;p&gt;NMHDR&lt;br /&gt;{&lt;br /&gt;HWND hWndFrom;//相当于原WM_COMMAND消息的lParam&lt;br /&gt;UINT idFrom; //相当于原WM_COMMAND消息的wParam(LOWORD)&lt;br /&gt;UINT code; //相当于原WM_COMMAND消息的wParam(HIWORD)通知码&lt;br /&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;使用这个结构，WM_NOTIFY还可以附带更多的信息，您可以定义一个更大的结构，这个结构的第一个元素就是NMHDR结构，在该元素的后面您还可以放置其它附加信息。由于在这个大结构中，第一个成员是NMHDR，这样一来，我们就可以利用指向NMHDR的指针来指向这个结构，不论后面有没有其它内容。&lt;br /&gt;可见，WM_NOTIFY和WM_COMMAND相比，是一种更灵活的消息格式，lParam中放的是一个称为NMHDR结构的指针。在wParam中放的则是控件的ID。最初Windows 3.x就有的控件，如Edit，Combo，List，Button等，发送的控件通知消息的格式是WM_COMMAND；而后期的Win32通用控件，如List View，Image List，IP Address，Tree View，Toolbar等，发送的都是WM_NOTIFY控件通知消息。&lt;br /&gt;另外，当用户选择菜单的一个命令项，也会发送WM_COMMAND消息。&lt;br /&gt;当用户选择菜单的一个命令项或控件给父窗口发送通知消息，都可以使用WM_COMMAND消息。为了区分这两种情况，规定它们有以下区别，如表2：&lt;/p&gt;&#xD;
&lt;p&gt;消息来源&lt;br /&gt;wParam (high word)&lt;br /&gt;wParam (low word)&lt;br /&gt;lParam&lt;br /&gt;&lt;br /&gt;菜单&lt;br /&gt;0&lt;br /&gt;菜单标识符 (IDM_*)&lt;br /&gt;0&lt;br /&gt;&lt;br /&gt;控件&lt;br /&gt;控件定义的通知码&lt;br /&gt;控件ID&lt;br /&gt;控件窗口的句柄&lt;br /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;表2&lt;/p&gt;&#xD;
&lt;p&gt;例程M6，演示菜单发出WM_COMMAND消息和子控件发送WM_COMMAND消息的区别（见附带源码工程M6）&lt;br /&gt;打开VC++ 6.0，新建Win32 Application工程M6，然后在该工程中新建C++ Source File，文件名为M6，M6的文件内容具体见例程M6。&lt;br /&gt;在例程M6所在的路径打开M6文件夹，新建一个文本文档，如下图：&lt;/p&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image008.png" width="359" height="248"  alt="" /&gt;&lt;br /&gt;将&amp;#8220;新建文本文档.txt&amp;#8221;改名为&amp;#8220;M6.rc&amp;#8221;，如下图：&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image010.png" width="342" height="241"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;右键单击M6.rc，在弹出的快捷菜单中使用&amp;#8220;写字板&amp;#8221;打开，如下图：&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image012.png" width="479" height="313"  alt="" /&gt;&lt;br /&gt;添加的内容具体见M6.rc，保存后退出。编译、运行工程M6，弹出如下窗口：&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image014.png" width="347" height="255"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;分别单击&amp;#8220;FirstButton&amp;#8221;按钮和&amp;#8220;Menu1&amp;#8221;菜单，会弹出相应的提示消息框。&lt;br /&gt;M6中对于WM_COMMAND消息的处理，源代码如下：&lt;br /&gt;&lt;pre&gt;case WM_COMMAND:&#xD;
    {&#xD;
      if(lParam == 0)&#xD;
      {&#xD;
        switch(LOWORD(wParam))&#xD;
        {&#xD;
        case IDM_MENU1:&#xD;
          MessageBox(NULL,"MENU1菜单被点击","M6",MB_OK);&#xD;
          break;&#xD;
&#xD;
        case IDM_EXIT:&#xD;
          DestroyWindow(hwnd);&#xD;
          break;&#xD;
        }&#xD;
      }&#xD;
&#xD;
      else //处理子控件触发的WM_COMMAND控件通知消息&#xD;
      {&#xD;
        //(LOWORD(wParam))是控件ID&#xD;
        switch(LOWORD(wParam))&#xD;
        {&#xD;
        case ButtonID1:&#xD;
          if(HIWORD(wParam) == BN_CLICKED)&#xD;
          {&#xD;
            MessageBox(NULL,"按钮被点击","M6",MB_OK);&#xD;
          }&#xD;
          break;&#xD;
        }&#xD;
      }&#xD;
    }&#xD;
    break;&lt;/pre&gt;&lt;br /&gt;&#xD;
&lt;p&gt;对于WM_COMMAND消息，因为菜单和子控件都能触发。我们首先判断lParam，如果lParam为0，是菜单触发的WM_COMMAND消息；如果lParam不为0，是子控件触发的WM_COMMAND控件通知消息。对于菜单触发的WM_COMMAND消息，我们再通过(LOWORD(wParam))（菜单的标识ID）判断是哪个菜单触发的消息；对于控件触发的WM_COMMAND消息，我们通过(LOWORD(wParam))（控件ID）知道是哪个控件触发的消息，而且通过(HIWORD(wParam))（控件定义的通知码）知道控件到底触发了什么消息。&lt;br /&gt;本例程我们纯手工添加并编辑资源文件M6.rc，之所以这样做是为了让您了解资源文件的实质。实际编程中，您完全可以利用资源编辑器更加方便地添加、编辑资源文件，后面的例程将会演示说明。&lt;br /&gt;例程M7，演示WM_NOTIFY控件通知消息（见附带源码 工程M7）&lt;br /&gt;WM_NOTIFY消息是通用控件发送给其父窗口的消息，其中参数wParam 是发送消息的通用控件的ID，参数lParam 是一个指针，这个指针指向一个 NMHDR 结构，该结构包含了通知码和其它附加信息。 &lt;/p&gt;&#xD;
&lt;p&gt;下面我们看结构NMHDR：&lt;/p&gt;&#xD;
&lt;p&gt;typedef struct tagNMHDR {&lt;br /&gt;//发送消息的控件的句柄，相当于原WM_COMMAND消息的lParam&lt;br /&gt;HWND hwndFrom;&lt;br /&gt;//发送消息的控件的ID，相当于原WM_COMMAND消息的wParam(LOWORD)&lt;br /&gt;UINT idFrom; &lt;br /&gt;//通知码，也就是发送的具体消息，相当于原WM_COMMAND消息的wParam(HIWORD)通知码&lt;br /&gt;UINT code; &lt;br /&gt;} NMHDR;&lt;/p&gt;&#xD;
&lt;p&gt;打开VC++ 6.0，新建Win32 Application工程M7，然后在该工程中新建C++ Source File，文件名为M7，M7的文件内容具体见例程M7。&lt;br /&gt;下面，我们利用资源编辑器添加资源。单击&amp;#8220;文件&amp;#8221;-&amp;gt;&amp;#8220;新建&amp;#8221;，在&amp;#8220;新建&amp;#8221;对话框中选中&amp;#8220;Resource Script&amp;#8221;，文件名为&amp;#8220;M7&amp;#8221;，如下图：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image016.png" width="741" height="486"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;单击&amp;#8220;确定&amp;#8221;，添加M7资源文件。&lt;br /&gt;右击&amp;#8220;M7.RC&amp;#8221;文件夹，选中&amp;#8220;Insert&amp;#8230;&amp;#8221;菜单项，如下图：&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image018.png" width="478" height="182"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;弹出&amp;#8220;插入资源&amp;#8221;对话框，&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image020.png" width="465" height="340"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;选中&amp;#8220;Dialog&amp;#8221;，点击&amp;#8220;新建&amp;#8221;按钮，新建一个对话框资源。&lt;br /&gt;右击新建的&amp;#8220;IDD_DIALOG1&amp;#8221;，在属性对话框中将ID改为&amp;#8220;IDC_DIALOG&amp;#8221;，关闭属性框。&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image022.png" width="581" height="291"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;双击&amp;#8220;IDC_DIALOG&amp;#8221;，打开该对话框，调整至合适大小，在对话框上添加一个列表控件(List Control)，将该列表控件的ID设置为IDC_LIST，如下图：&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image024.png" width="632" height="431"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;并且把列表控件改为&amp;#8220;Report&amp;#8221;类型，如下图：&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image026.png" width="527" height="190"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;编辑并运行程序，程序运行会弹出如下对话框：&lt;br /&gt;&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image028.png" width="621" height="312"  alt="" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;分别用鼠标双击第一行或第二行，会弹出相应消息框。&lt;br /&gt;程序代码都有详细注释，您可以阅读代码，细细体会WM_NOTIFY控件通知消息。&lt;/p&gt;&#xD;
&lt;p&gt;6、MFC的消息映射&lt;/p&gt;&#xD;
&lt;p&gt;使用MFC编程时，消息发送和处理的本质和Win32相同，但是，它对消息处理进行了封装，简化了程序员编程时消息处理的复杂性，它通过消息映射机制来处理消息，程序员不必去设计和实现自己的窗口过程。&lt;br /&gt;说白了，MFC中的消息映射机制实质是一张巨大的消息及其处理函数对应表。消息映射基本上分为两大部分：&lt;br /&gt;在头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),它放在类的末尾，是一个public属性的；与之对应的是在实现部分(.cpp)增加了一个消息映射表，内容如下：&lt;/p&gt;&#xD;
&lt;p&gt;BEGIN_MASSAGE_MAP(当前类，当前类的基类)&lt;br /&gt;//{{AFX_MSG_MAP(CMainFrame)&lt;/p&gt;&#xD;
&lt;p&gt;消息的入口项&lt;/p&gt;&#xD;
&lt;p&gt;//}}AFX_MSG_MAP&lt;/p&gt;&#xD;
&lt;p&gt;END_MESSAGE_MAP()&lt;/p&gt;&#xD;
&lt;p&gt;但是仅是这两项还不足以完成一条消息，要是一个消息工作，必须还有以下3个部分去协作：&lt;br /&gt;1、在类的定义中加入相应的函数声明；&lt;br /&gt;2、在类的消息映射表中加入相应的消息映射入口项；&lt;br /&gt;3、在类的实现中加入相应的函数体；&lt;/p&gt;&#xD;
&lt;p&gt;&lt;br /&gt;消息的添加&lt;/p&gt;&#xD;
&lt;p&gt;（1）、利用Class Wizard实现自动添加&lt;br /&gt;在菜单中选择View -&amp;gt; Class Wizard激活Class Wizard，选择Message Map标签，从Class name组合框中选取我们想要添加消息的类。在Object IDs列表框中，选取类的名称。此时，Messages列表框显示该类的可重载成员函数和窗口消息。可重载成员函数显示在列表的上部，以实际虚构成员函数的大小写字母来表示。其他为窗口消息，以大写字母出现。选中我们要添加的消息，单击Add Funtion按钮，Class Wizard自动将该消息添加进来。&lt;br /&gt;有时候，我们想要添加的消息在Message列表中找不到，我们可以利用Class Wizard上Class Info标签以扩展消息列表。在该页中，找到Message Filter组合框，通过它可以改变首页中Messages列表框中的选项。&lt;/p&gt;&#xD;
&lt;p&gt;（2）、手动添加消息&lt;br /&gt;如果Messages列表框中确实没有我们想要的消息，就需要我们手工添加：&lt;br /&gt;1）在类的.h文件中添加处理函数的声明，紧接着在//}}AFX_MSG行之后加入声明，注意，一定要以afx_msg开头。&lt;br /&gt;通常，添加处理函数声明的最好的地方是源代码中Class Wizard维护的表的下面，在它标记其领域的{{ }}括弧外面。这些括弧中的任何东西都有可能会被Class Wizard销毁。&lt;br /&gt;2）接着，在用户类的.cpp文件中找到//}}AFX_MSG_MAP行，紧接在它之后加入消息入口项。同样，也放在{{ }}外面。&lt;br /&gt;3)最后，在该文件中添加消息处理函数的实体。&lt;/p&gt;&#xD;
&lt;p&gt;对于能够使用Class Wizard添加的消息，尽量使用Class Wizard添加，以减少我们的工作量；对于不能使用Class Wizard添加的消息和自定义消息，需要手动添加。总体说来，MFC的消息编程对用户来说，相对比较简单，在此不再使用实例演示。&lt;/p&gt;&#xD;
&lt;p&gt;7、消息反射机制&lt;br /&gt;什么叫消息反射？&lt;br /&gt;父窗口将控件发给它的通知消息，反射回控件进行处理（即让控件处理这个消息），这种通知消息让控件自己处理的机制叫做消息反射机制。&lt;br /&gt;通过前面的学习我们知道，一般情况下，控件向父窗口发送通知消息，由父窗口处理这些通知消息。这样，父窗口（通常是一个对话框）会对这些消息进行处理，换句话说，控件的这些消息处理必须在父窗口类体内，每当我们添加子控件的时候，就要在父窗口类中复制这些代码。很明显，这对代码的维护和移植带来了不便，而且，明显背离C++的对象编程原则。 &lt;br /&gt;从4.0版开始，MFC提供了一种消息反射机制(Message Reflection)，可以把控件通知消息反射回控件。具体地讲，对于反射消息，如果控件有该消息的处理函数，那么就由控件自己处理该消息，如果控件不处理该消息，则框架会把该消息继续送给父窗口，这样父窗口继续处理该消息。可见，新的消息反射机制并不破坏原来的通知消息处理机制。&lt;/p&gt;&#xD;
&lt;p&gt;消息反射机制为控件提供了处理通知消息的机会，这是很有用的。如果按传统的方法，由父窗口来处理这个消息，则加重了控件对象对父窗口的依赖程度，这显然违背了面向对象的原则。若由控件自己处理消息，则使得控件对象具有更大的独立性，大大方便了代码的维护和移植。&lt;br /&gt;实例M8：简单地演示MFC的消息反射机制。（见附带源码 工程M8）&lt;br /&gt;打开VC++ 6.0，新建一个基于对话框的工程M8。&lt;br /&gt;在该工程中，新建一个CMyEdit类，基类是CEdit。接着，在该类中添加三个变量，如下：&lt;/p&gt;&#xD;
&lt;p&gt;private:&lt;br /&gt;CBrush m_brBkgnd;&lt;br /&gt;COLORREF m_clrBkgnd;&lt;br /&gt;COLORREF m_clrText;&lt;/p&gt;&#xD;
&lt;p&gt;在CMyEdit::CMyEdit()中，给这三个变量赋初值：&lt;/p&gt;&#xD;
&lt;p&gt;{&lt;br /&gt;m_clrBkgnd = RGB( 255, 255, 0 );&lt;br /&gt;m_clrText = RGB( 0, 0, 0 );&lt;br /&gt;m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );&lt;br /&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;打开ClassWizard，类名为CMyEdit，Messages处选中&amp;#8220;=WM_CTLCOLOR&amp;#8221;，您是否发现，WM_CTLCOLOR消息前面有一个等号，它表示该消息是反射消息，也就是说，前面有等号的消息是可以反射的消息。&lt;br /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image030.png" width="740" height="489"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;消息反射函数代码如下：&lt;br /&gt;&lt;/p&gt;&lt;pre&gt;HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor) &#xD;
{&#xD;
&#xD;
    // TODO: Change any attributes of the DC here&#xD;
    pDC-&amp;gt;SetTextColor( m_clrText );//设置文本颜色&#xD;
    pDC-&amp;gt;SetBkColor( m_clrBkgnd );//设置背景颜色&#xD;
&#xD;
     //请注意，在我们改写该函数的内容前，函数返回NULL，即return NULL;&#xD;
&#xD;
    //函数返回NULL将会执行父窗口的CtlColor函数，而不执行控件的CtlColor函数&#xD;
&#xD;
    //所以，我们让函数返回背景刷，而不返回NULL，目的就是为了实现消息反射&#xD;
    return m_brBkgnd; //返回背景刷&#xD;
}&#xD;
&lt;/pre&gt;在IDD_M8_DIALOG对话框中添加一个Edit控件，使用ClassWizard给该Edit控件添加一个CMyEdit类型的变量m_edit1，把Edit控件和CMyEdit关联起来。&lt;br /&gt;&lt;img src="http://www.vckbase.com/document/journal/vckbase55/images/image032.png" width="423" height="356"  alt="" /&gt;&lt;br /&gt;&lt;br /&gt;编译，运行程序，观察运行效果。&lt;br /&gt;就写这些吧，水平有限，希望能对您有所帮助。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/strong&gt;&lt;img src="http://www.cnblogs.com/cdo/aggbug/1890053.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/cdo/archive/2010/11/28/1890053.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/cdo/archive/2010/11/23/1885910.html</id><title type="text">关于23种设计模式的有趣见解(转)</title><summary type="text">1、FACTORY&amp;#8212;追MM少不了请吃饭了，麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西，虽然口味有所不同，但不管你带MM去麦当劳或肯德基，只管向服务员说&amp;#8220;来四个鸡翅&amp;#8221;就行了。麦当劳和肯德基就是生产鸡翅的Factory 工厂模式：客户类和工厂类分开。消费者任何时候需要某种产品，只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时，工厂类也要做相应...</summary><published>2010-11-23T13:33:00Z</published><updated>2010-11-23T13:33:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2010/11/23/1885910.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2010/11/23/1885910.html"/><content type="html">1、FACTORY&amp;#8212;追MM少不了请吃饭了，麦当劳的鸡翅和肯德基的鸡翅都是MM爱吃的东西，虽然口味有所不同，但不管你带MM去麦当劳或肯德基，只管向服务员说&amp;#8220;来四个鸡翅&amp;#8221;就行了。麦当劳和肯德基就是生产鸡翅的Factory &lt;br /&gt;&lt;br /&gt;工厂模式：客户类和工厂类分开。消费者任何时候需要某种产品，只需向工厂请求即可。消费者无须修改就可以接纳新产品。缺点是当产品修改时，工厂类也要做相应的修改。如：如何创建及如何向客户端提供。 &lt;br /&gt;&lt;br /&gt;2、BUILDER&amp;#8212;MM最爱听的就是&amp;#8220;我爱你&amp;#8221;这句话了，见到不同地方的MM,要能够用她们的方言跟她说这句话哦，我有一个多种语言翻译机，上面每种语言都有一个按键，见到MM我只要按对应的键，它就能够用相应的语言说出&amp;#8220;我爱你&amp;#8221;这句话了，国外的MM也可以轻松搞掂，这就是我的&amp;#8220;我爱你&amp;#8221;builder。（这一定比美军在伊拉克用的翻译机好卖） &lt;br /&gt;&lt;br /&gt;建造模式：将产品的内部表象和产品的生成过程分割开来，从而使一个建造过程生成具有不同的内部表象的产品对象。建造模式使得产品内部表象可以独立的变化，客户不必知道产品内部组成的细节。建造模式可以强制实行一种分步骤进行的建造过程。 &lt;br /&gt;&lt;br /&gt;3、FACTORY METHOD&amp;#8212;请MM去麦当劳吃汉堡，不同的MM有不同的口味，要每个都记住是一件烦人的事情，我一般采用Factory Method模式，带着MM到服务员那儿，说&amp;#8220;要一个汉堡&amp;#8221;，具体要什么样的汉堡呢，让MM直接跟服务员说就行了。 &lt;br /&gt;&lt;br /&gt;工厂方法模式：核心工厂类不再负责所有产品的创建，而是将具体创建的工作交给子类去做，成为一个抽象工厂角色，仅负责给出具体工厂类必须实现的接口，而不接触哪一个产品类应当被实例化这种细节。 &lt;br /&gt;&lt;br /&gt;4、PROTOTYPE&amp;#8212;跟MM用QQ聊天，一定要说些深情的话语了，我搜集了好多肉麻的情话，需要时只要copy出来放到QQ里面就行了，这就是我的情话prototype了。（100块钱一份，你要不要） &lt;br /&gt;&lt;br /&gt;原始模型模式：通过给出一个原型对象来指明所要创建的对象的类型，然后用复制这个原型对象的方法创建出更多同类型的对象。原始模型模式允许动态的增加或减少产品类，产品类不需要非得有任何事先确定的等级结构，原始模型模式适用于任何的等级结构。缺点是每一个类都必须配备一个克隆方法。 &lt;br /&gt;&lt;br /&gt;5、SINGLETON&amp;#8212;俺有6个漂亮的老婆，她们的老公都是我，我就是我们家里的老公Sigleton，她们只要说道&amp;#8220;老公&amp;#8221;，都是指的同一个人，那就是我(刚才做了个梦啦，哪有这么好的事) &lt;br /&gt;&lt;br /&gt;单例模式：单例模式确保某一个类只有一个实例，而且自行实例化并向整个系统提供这个实例单例模式。单例模式只应在有真正的&amp;#8220;单一实例&amp;#8221;的需求时才可使用。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;结构型模式&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;6、ADAPTER&amp;#8212;在朋友聚会上碰到了一个美女Sarah，从香港来的，可我不会说粤语，她不会说普通话，只好求助于我的朋友kent了，他作为我和Sarah之间的Adapter，让我和Sarah可以相互交谈了(也不知道他会不会耍我) &lt;br /&gt;&lt;br /&gt;适配器（变压器）模式：把一个类的接口变换成客户端所期待的另一种接口，从而使原本因接口原因不匹配而无法一起工作的两个类能够一起工作。适配类可以根据参数返还一个合适的实例给客户端。 &lt;br /&gt;&lt;br /&gt;7、BRIDGE&amp;#8212;早上碰到MM，要说早上好，晚上碰到MM，要说晚上好；碰到MM穿了件新衣服，要说你的衣服好漂亮哦，碰到MM新做的发型，要说你的头发好漂亮哦。不要问我&amp;#8220;早上碰到MM新做了个发型怎么说&amp;#8221;这种问题，自己用BRIDGE组合一下不就行了 &lt;br /&gt;&lt;br /&gt;桥梁模式：将抽象化与实现化脱耦，使得二者可以独立的变化，也就是说将他们之间的强关联变成弱关联，也就是指在一个软件系统的抽象化和实现化之间使用组合/聚合关系而不是继承关系，从而使两者可以独立的变化。 &lt;br /&gt;&lt;br /&gt;8、COMPOSITE&amp;#8212;Mary今天过生日。&amp;#8220;我过生日，你要送我一件礼物。&amp;#8221;&amp;#8220;嗯，好吧，去商店，你自己挑。&amp;#8221;&amp;#8220;这件T恤挺漂亮，买，这条裙子好看，买，这个包也不错，买。&amp;#8221;&amp;#8220;喂，买了三件了呀，我只答应送一件礼物的哦。&amp;#8221;&amp;#8220;什么呀，T恤加裙子加包包，正好配成一套呀，小姐，麻烦你包起来。&amp;#8221;&amp;#8220;&amp;#8230;&amp;#8230;&amp;#8221;，MM都会用Composite模式了，你会了没有？ &lt;br /&gt;&lt;br /&gt;合成模式：合成模式将对象组织到树结构中，可以用来描述整体与部分的关系。合成模式就是一个处理对象的树结构的模式。合成模式把部分与整体的关系用树结构表示出来。合成模式使得客户端把一个个单独的成分对象和由他们复合而成的合成对象同等看待。 &lt;br /&gt;&lt;br /&gt;9、DECORATOR&amp;#8212;Mary过完轮到Sarly过生日，还是不要叫她自己挑了，不然这个月伙食费肯定玩完，拿出我去年在华山顶上照的照片，在背面写上&amp;#8220;最好的的礼物，就是爱你的Fita&amp;#8221;，再到街上礼品店买了个像框（卖礼品的MM也很漂亮哦），再找隔壁搞美术设计的Mike设计了一个漂亮的盒子装起来&amp;#8230;&amp;#8230;，我们都是Decorator，最终都在修饰我这个人呀，怎么样，看懂了吗？ &lt;br /&gt;&lt;br /&gt;装饰模式：装饰模式以对客户端透明的方式扩展对象的功能，是继承关系的一个替代方案，提供比继承更多的灵活性。动态给一个对象增加功能，这些功能可以再动态的撤消。增加由一些基本功能的排列组合而产生的非常大量的功能。 &lt;br /&gt;&lt;br /&gt;10、FACADE&amp;#8212;我有一个专业的Nikon相机，我就喜欢自己手动调光圈、快门，这样照出来的照片才专业，但MM可不懂这些，教了半天也不会。幸好相机有Facade设计模式，把相机调整到自动档，只要对准目标按快门就行了，一切由相机自动调整，这样MM也可以用这个相机给我拍张照片了。 &lt;br /&gt;&lt;br /&gt;门面模式：外部与一个子系统的通信必须通过一个统一的门面对象进行。门面模式提供一个高层次的接口，使得子系统更易于使用。每一个子系统只有一个门面类，而且此门面类只有一个实例，也就是说它是一个单例模式。但整个系统可以有多个门面类。 &lt;br /&gt;&lt;br /&gt;11、FLYWEIGHT&amp;#8212;每天跟MM发短信，手指都累死了，最近买了个新手机，可以把一些常用的句子存在手机里，要用的时候，直接拿出来，在前面加上MM的名字就可以发送了，再不用一个字一个字敲了。共享的句子就是Flyweight，MM的名字就是提取出来的外部特征，根据上下文情况使用。 &lt;br /&gt;&lt;br /&gt;享元模式：FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部，不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态，它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来，将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象，而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。 &lt;br /&gt;&lt;br /&gt;12、PROXY&amp;#8212;跟MM在网上聊天，一开头总是&amp;#8220;hi,你好&amp;#8221;,&amp;#8220;你从哪儿来呀？&amp;#8221;&amp;#8220;你多大了？&amp;#8221;&amp;#8220;身高多少呀？&amp;#8221;这些话，真烦人，写个程序做为我的Proxy吧，凡是接收到这些话都设置好了自动的回答，接收到其他的话时再通知我回答，怎么样，酷吧。 &lt;br /&gt;&lt;br /&gt;代理模式：代理模式给某一个对象提供一个代理对象，并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下，客户不想或者不能够直接引用一个对象，代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象，而仅仅持有一个被代理对象的接口，这时候代理对象不能够创建被代理对象，被代理对象必须有系统的其他角色代为创建并传入。 &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;行为模式&lt;/strong&gt; &lt;br /&gt;&lt;br /&gt;13、CHAIN OF RESPONSIBLEITY&amp;#8212;晚上去上英语课，为了好开溜坐到了最后一排，哇，前面坐了好几个漂亮的MM哎，找张纸条，写上&amp;#8220;Hi,可以做我的女朋友吗？如果不愿意请向前传&amp;#8221;，纸条就一个接一个的传上去了，糟糕，传到第一排的MM把纸条传给老师了，听说是个老处女呀，快跑! &lt;br /&gt;&lt;br /&gt;责任链模式：在责任链模式中，很多对象由每一个对象对其下家的引用而接 &lt;br /&gt;&lt;br /&gt;起来形成一条链。请求在这个链上传递，直到链上的某一个对象决定处理此请求。客户并不知道链上的哪一个对象最终处理这个请求，系统可以在不影响客户端的情况下动态的重新组织链和分配责任。处理者有两个选择：承担责任或者把责任推给下家。一个请求可以最终不被任何接收端对象所接受。 &lt;br /&gt;&lt;br /&gt;14、COMMAND&amp;#8212;俺有一个MM家里管得特别严，没法见面，只好借助于她弟弟在我们俩之间传送信息，她对我有什么指示，就写一张纸条让她弟弟带给我。这不，她弟弟又传送过来一个COMMAND，为了感谢他，我请他吃了碗杂酱面，哪知道他说：&amp;#8220;我同时给我姐姐三个男朋友送COMMAND，就数你最小气，才请我吃面。&amp;#8221;，:-( &lt;br /&gt;&lt;br /&gt;命令模式：命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开，委派给不同的对象。命令模式允许请求的一方和发送的一方独立开来，使得请求的一方不必知道接收请求的一方的接口，更不必知道请求是怎么被接收，以及操作是否执行，何时被执行以及是怎么被执行的。系统支持命令的撤消。 &lt;br /&gt;&lt;br /&gt;15、INTERPRETER&amp;#8212;俺有一个《泡MM真经》，上面有各种泡MM的攻略，比如说去吃西餐的步骤、去看电影的方法等等，跟MM约会时，只要做一个Interpreter，照着上面的脚本执行就可以了。 &lt;br /&gt;&lt;br /&gt;解释器模式：给定一个语言后，解释器模式可以定义出其文法的一种表示，并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。解释器模式将描述怎样在有了一个简单的文法后，使用模式设计解释这些语句。在解释器模式里面提到的语言是指任何解释器对象能够解释的任何组合。在解释器模式中需要定义一个代表文法的命令类的等级结构，也就是一系列的组合规则。每一个命令对象都有一个解释方法，代表对命令对象的解释。命令对象的等级结构中的对象的任何排列组合都是一个语言。 &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;16、ITERATOR&amp;#8212;我爱上了Mary，不顾一切的向她求婚。 &lt;br /&gt;&lt;br /&gt;Mary：&amp;#8220;想要我跟你结婚，得答应我的条件&amp;#8221; &lt;br /&gt;&lt;br /&gt;我：&amp;#8220;什么条件我都答应，你说吧&amp;#8221; &lt;br /&gt;&lt;br /&gt;Mary：&amp;#8220;我看上了那个一克拉的钻石&amp;#8221; &lt;br /&gt;&lt;br /&gt;我：&amp;#8220;我买，我买，还有吗？&amp;#8221; &lt;br /&gt;&lt;br /&gt;Mary：&amp;#8220;我看上了湖边的那栋别墅&amp;#8221; &lt;br /&gt;&lt;br /&gt;我：&amp;#8220;我买，我买，还有吗？&amp;#8221; &lt;br /&gt;&lt;br /&gt;Mary：&amp;#8220;你的小弟弟必须要有50cm长&amp;#8221; &lt;br /&gt;&lt;br /&gt;我脑袋嗡的一声，坐在椅子上，一咬牙：&amp;#8220;我剪，我剪，还有吗？&amp;#8221; &lt;br /&gt;&lt;br /&gt;&amp;#8230;&amp;#8230; &lt;br /&gt;&lt;br /&gt;迭代子模式：迭代子模式可以顺序访问一个聚集中的元素而不必暴露聚集的内部表象。多个对象聚在一起形成的总体称之为聚集，聚集对象是能够包容一组对象的容器对象。迭代子模式将迭代逻辑封装到一个独立的子对象中，从而与聚集本身隔开。迭代子模式简化了聚集的界面。每一个聚集对象都可以有一个或一个以上的迭代子对象，每一个迭代子的迭代状态可以是彼此独立的。迭代算法可以独立于聚集角色变化。 &lt;br /&gt;&lt;br /&gt;17、MEDIATOR&amp;#8212;四个MM打麻将，相互之间谁应该给谁多少钱算不清楚了，幸亏当时我在旁边，按照各自的筹码数算钱，赚了钱的从我这里拿，赔了钱的也付给我，一切就OK啦，俺得到了四个MM的电话。 &lt;br /&gt;&lt;br /&gt;调停者模式：调停者模式包装了一系列对象相互作用的方式，使得这些对象不必相互明显作用。从而使他们可以松散偶合。当某些对象之间的作用发生改变时，不会立即影响其他的一些对象之间的作用。保证这些作用可以彼此独立的变化。调停者模式将多对多的相互作用转化为一对多的相互作用。调停者模式将对象的行为和协作抽象化，把对象在小尺度的行为上与其他对象的相互作用分开处理。 &lt;br /&gt;&lt;br /&gt;18、MEMENTO&amp;#8212;同时跟几个MM聊天时，一定要记清楚刚才跟MM说了些什么话，不然MM发现了会不高兴的哦，幸亏我有个备忘录，刚才与哪个MM说了什么话我都拷贝一份放到备忘录里面保存，这样可以随时察看以前的记录啦。 &lt;br /&gt;&lt;br /&gt;备忘录模式：备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下，将一个对象的状态捉住，并外部化，存储起来，从而可以在将来合适的时候把这个对象还原到存储起来的状态。 &lt;br /&gt;&lt;br /&gt;19、OBSERVER&amp;#8212;想知道咱们公司最新MM情报吗？加入公司的MM情报邮件组就行了，tom负责搜集情报，他发现的新情报不用一个一个通知我们，直接发布给邮件组，我们作为订阅者（观察者）就可以及时收到情报啦 &lt;br /&gt;&lt;br /&gt;观察者模式：观察者模式定义了一种一队多的依赖关系，让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时，会通知所有观察者对象，使他们能够自动更新自己。 &lt;br /&gt;&lt;br /&gt;20、STATE&amp;#8212;跟MM交往时，一定要注意她的状态哦，在不同的状态时她的行为会有不同，比如你约她今天晚上去看电影，对你没兴趣的MM就会说&amp;#8220;有事情啦&amp;#8221;，对你不讨厌但还没喜欢上的MM就会说&amp;#8220;好啊，不过可以带上我同事么？&amp;#8221;，已经喜欢上你的MM就会说&amp;#8220;几点钟？看完电影再去泡吧怎么样？&amp;#8221;，当然你看电影过程中表现良好的话，也可以把MM的状态从不讨厌不喜欢变成喜欢哦。 &lt;br /&gt;&lt;br /&gt;状态模式：状态模式允许一个对象在其内部状态改变的时候改变行为。这个对象看上去象是改变了它的类一样。状态模式把所研究的对象的行为包装在不同的状态对象里，每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候，其行为也随之改变。状态模式需要对每一个系统可能取得的状态创立一个状态类的子类。当系统的状态变化时，系统便改变所选的子类。 &lt;br /&gt;&lt;br /&gt;21、STRATEGY&amp;#8212;跟不同类型的MM约会，要用不同的策略，有的请电影比较好，有的则去吃小吃效果不错，有的去海边浪漫最合适，单目的都是为了得到MM的芳心，我的追MM锦囊中有好多Strategy哦。 &lt;br /&gt;&lt;br /&gt;策略模式：策略模式针对一组算法，将每一个算法封装到具有共同接口的独立的类中，从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。策略模式把行为和环境分开。环境类负责维持和查询行为类，各种算法在具体的策略类中提供。由于算法和环境独立开来，算法的增减，修改都不会影响到环境和客户端。 &lt;br /&gt;&lt;br /&gt;22、TEMPLATE METHOD&amp;#8212;&amp;#8212;看过《如何说服女生上床》这部经典文章吗？女生从认识到上床的不变的步骤分为巧遇、打破僵局、展开追求、接吻、前戏、动手、爱抚、进去八大步骤(Template method)，但每个步骤针对不同的情况，都有不一样的做法，这就要看你随机应变啦(具体实现)； &lt;br /&gt;&lt;br /&gt;模板方法模式：模板方法模式准备一个抽象类，将部分逻辑以具体方法以及具体构造子的形式实现，然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法，从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架，而将逻辑的细节留给具体的子类去实现。 &lt;br /&gt;&lt;br /&gt;23、VISITOR&amp;#8212;情人节到了，要给每个MM送一束鲜花和一张卡片，可是每个MM送的花都要针对她个人的特点，每张卡片也要根据个人的特点来挑，我一个人哪搞得清楚，还是找花店老板和礼品店老板做一下Visitor，让花店老板根据MM的特点选一束花，让礼品店老板也根据每个人特点选一张卡，这样就轻松多了； &lt;br /&gt;&lt;br /&gt;访问者模式：访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话，接受这个操作的数据结构可以保持不变。访问者模式适用于数据结构相对未定的系统，它把数据结构和作用于结构上的操作之间的耦合解脱开，使得操作集合可以相对自由的演化。访问者模式使得增加新的操作变的很容易，就是增加一个新的访问者类。访问者模式将有关的行为集中到一个访问者对象中，而不是分散到一个个的节点类中。当使用访问者模式时，要将尽可能多的对象浏览逻辑放在访问者类中，而不是放到它的子类中。访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。&lt;br /&gt;&lt;img src="http://www.cnblogs.com/cdo/aggbug/1885910.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/cdo/archive/2010/11/23/1885910.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/cdo/archive/2010/03/16/1687695.html</id><title type="text">QQREADER4F259A1BAEEAC3FE</title><summary type="text">QQREADER4F259A1BAEEAC3FE</summary><published>2010-03-16T14:49:00Z</published><updated>2010-03-16T14:49:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2010/03/16/1687695.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2010/03/16/1687695.html"/><content type="html">QQREADER4F259A1BAEEAC3FE&lt;img src="http://www.cnblogs.com/cdo/aggbug/1687695.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/cdo/archive/2010/03/16/1687695.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/cdo/archive/2008/06/29/1232133.html</id><title type="text">挂钩 NtResumeThread 实现全局Hook</title><summary type="text">创建时间：2008-05-25文章属性：原创文章提交：BITS (zhouzhenster_at_gmail.com)挂钩 NtResumeThread 实现全局Hookzhouzhenster@gmail.comzhouzhen[E.S.T]挂钩一直是Hack 编程中永恒的主题，基本高级的Rootkit 程序多多少少都会使用Hook 技术。似乎Hook 都被讲烂了，不论是Ring3 的还是Rin...</summary><published>2008-06-29T13:22:00Z</published><updated>2008-06-29T13:22:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2008/06/29/1232133.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2008/06/29/1232133.html"/></entry><entry><id>http://www.cnblogs.com/cdo/archive/2008/06/29/1232132.html</id><title type="text">static_cast、dynamic_cast、reinterpret_cast、和const_cast</title><summary type="text">(转自http://hi.baidu.com/seekvista/blog/item/87845a60ae248845ebf8f8dd.html)关于强制类型转换的问题，很多书都讨论过，写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换，而是使用标准C++的类型转换符：static_cast, dynamic_cast。标准C++中有四个类型转换符...</summary><published>2008-06-29T13:21:00Z</published><updated>2008-06-29T13:21:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2008/06/29/1232132.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2008/06/29/1232132.html"/></entry><entry><id>http://www.cnblogs.com/cdo/archive/2007/08/09/849874.html</id><title type="text">TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞</title><summary type="text">下载源代码这个标题用了两个顿号三个名称，其实说得是同一个东西，只是网上有不同的说法罢了，另外好像还有人叫TCP打孔（我的朋友小妞听说后问“要打孔啊，要不要我帮你去借个电钻过来啊？”“~！·￥%……·！”）。闲话少说，我们先看一下技术背景：Internet的迅速发展以及IPv4 地址数量的限制使得网络地址翻译(NAT,Network Address Trans2lation)设备得到广泛应用。NAT...</summary><published>2007-08-09T13:10:00Z</published><updated>2007-08-09T13:10:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2007/08/09/849874.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2007/08/09/849874.html"/></entry><entry><id>http://www.cnblogs.com/cdo/archive/2006/12/09/586948.html</id><title type="text">几大开源项目</title><summary type="text">7-Zip 4.32：文件压缩工具，可与Windows资源管理器集成 A Note 4.2.1：可在Windows桌面放置便笺，并可提供闹钟提醒功能 Abakt 0.9：能够以压缩方式对文档进行备份 Abiword 2.27：Windows写字板的替代程序，功能有所加强 Aethera 1.21：提供日历、通讯录、任务表及提醒功能，并且内置了e-mail客户端 Ant Movie Catalog ...</summary><published>2006-12-08T22:25:00Z</published><updated>2006-12-08T22:25:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2006/12/09/586948.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2006/12/09/586948.html"/></entry><entry><id>http://www.cnblogs.com/cdo/archive/2006/11/30/577080.html</id><title type="text">脚本实例</title><summary type="text">任务1：检索计算机名老实告诉你吧，通过脚本来检索计算机名的途径数不胜数。但是因为WMI将要成为检索信息的主流技术，所以我们所展示的是一段返回目标计算机名称的WMI脚本。strComputer="."SetobjWMIService=GetObject("winmgmts:\\"&amp;amp;strComputer&amp;amp;"\root\cimv2")SetcolComputers=objWMIServ...</summary><published>2006-11-29T16:01:00Z</published><updated>2006-11-29T16:01:00Z</updated><author><name>Cdo</name><uri>http://www.cnblogs.com/cdo/</uri></author><link rel="alternate" href="http://www.cnblogs.com/cdo/archive/2006/11/30/577080.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/cdo/archive/2006/11/30/577080.html"/></entry></feed>
