﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-yehao's Blog-文章分类-网络编程</title><link>http://www.cppblog.com/yehao/category/16707.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 21 Nov 2013 11:21:41 GMT</lastBuildDate><pubDate>Thu, 21 Nov 2013 11:21:41 GMT</pubDate><ttl>60</ttl><item><title>如何实现在一个 Socket 应用程序中同时支持 IPv4 和 IPv6</title><link>http://www.cppblog.com/yehao/articles/204311.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 18 Nov 2013 07:55:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/204311.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/204311.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/204311.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/204311.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/204311.html</trackback:ping><description><![CDATA[<strong style="font-family: arial, nsimsun, sans-serif;">简介：</strong><span style="font-family: arial, nsimsun, sans-serif;">&nbsp;当今的网络主流是 IPv4 网络，但随着 IP 地址的日益短缺，IPv6 网络开始渐渐盛行，因此传统的网络编程也需要做一些改进来适应 IPv6 和 IPv4 共存的网络环境。 本文介绍了一种设计模式来根据用户输入的地址或者域名建立合适的网络连接，并且屏蔽了网络连接细节，提供给用户一个统一的接口进行二次开发。 在文中还给出了一个基于 OpenSSL https 安全连接的应用来说明该方法的使用细节。<br /></span><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="major1"><span style="font-size: 1.5em; font-weight: bold;">两个要解决的问题</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">现代网络中，IPv4, IPv6 共存的情况日益增加，而这两种协议的地址格式，地址解析的 API 各不同，程序员必须面对如下两个问题并且合理地解决这些问题。</p><ol style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;">怎么准确识别用户输入的地址或者域名是属于 IPv4 网络还是 IPv6 网络？&nbsp;<br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;">怎么屏蔽网络连接细节，提供给用户一个统一的接口？&nbsp;<br /><br /></li></ol><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="major2"><span style="font-size: 1.5em; font-weight: bold;">IPv4 与 IPv6 对比</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">目前我们使用的第二代互联网 IPv4 技术，它的最大问题是网络地址资源有限，IPv6 是&#8220;Internet Protocol Version 6&#8221;的缩写，它是 IETF 设计的用于替代现行版本 IP 协议 -IPv4- 的下一代 IP 协议。与 IPV4 相比，IPv6 具有更大的地址空间。IPv4 中规定 IP 地址长度为 32 位；而 IPv6 中 IP 地址的长度为 128 位。</p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">在 IPv4 网络下，网络编程主要依靠的是 socket 连接。在客户端，其基本步骤如下，创建一个 socket，使用 socket 连接服务器，最后通过 TCP 或者 UDP 协议进行数据读写。如果把这套方法移植到 IPv6 网络下，就需要在原来的基础上引入新的协议族、新的数据结构以及新的地址域名转换函数等。具体的一些差异如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig1" style="color: #996699;">图 1</a>所示：</p><br style="font-family: Simsun; font-size: medium;" /><a name="fig1" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 1. IPv4 与 IPv6 区别</strong></a><br style="font-family: Simsun; font-size: medium;" /><img alt="IPv4 与 IPv6 区别" height="221" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/figure1.jpg" width="458" style="border: 0px; padding: 0.3em 5px 0.7em; font-family: Simsun; font-size: medium;" />&nbsp;<br style="font-family: Simsun; font-size: medium;" /><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">在这里要稍微介绍下 getaddrinfo（）函数，它提供独立于协议的名称解析。函数的前两个参数分别是节点名和服务名。节点名可以是主机名，也可以是地址串 (IPv4 的点分十进制数表示或 IPv6 的十六进制数字串 )。服务名可以是十进制的端口号，也可以是已定义的服务名称，如 ftp、http 等。函数的第三个参数 hints 是 addrinfo 结构的指针，由调用者填写关于它所想返回的信息类型的线索。函数的返回值是一个指向 addrinfo 结构的链表指针 res。详见&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig2" style="color: #996699;">图 2</a>。</p><br style="font-family: Simsun; font-size: medium;" /><a name="fig2" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 2. getaddrinfo 函数说明</strong></a><br style="font-family: Simsun; font-size: medium;" /><img alt="getaddrinfo 函数说明" height="214" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/figure2.jpg" width="458" style="border: 0px; padding: 0.3em 5px 0.7em; font-family: Simsun; font-size: medium;" />&nbsp;<br style="font-family: Simsun; font-size: medium;" /><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">getaddrinfo 函数之前通常需要对以下 6 个参数进行以下设置：nodename、servname、hints 的 ai_flags、ai_family、ai_socktype、ai_protocol。在 6 项参数中，对函数影响最大的是 nodename，sername 和 hints.ai_flag。而 ai_family 只是有地址为 v4 地址或 v6 地址的区别。而 ai_protocol 一般是为 0 不作改动。其中 ai_flags、ai_family、ai_socktype。说明如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig3" style="color: #996699;">图 3</a>所示：</p><br style="font-family: Simsun; font-size: medium;" /><a name="fig3" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 3. getaddrinfo 参数说明</strong></a><br style="font-family: Simsun; font-size: medium;" /><img alt="getaddrinfo 参数说明" height="357" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/figure3.jpg" width="524" style="border: 0px; padding: 0.3em 5px 0.7em; font-family: Simsun; font-size: medium;" />&nbsp;<br style="font-family: Simsun; font-size: medium;" /><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">getaddrinfo 函数在 IPv6 和 IPv4 网络下都能实现独立于协议的名称解析，而且它返回的指向 addrinfo 结构的链表中会存放所有由输入参数 nodename 解析出的所有对应的 IP 信息，包括 IP 地址，协议族信息等。所以只要对 const struct addrinfo* hints 进行一些配置，就可以利用这个函数来识别连接目标的网络协议属性，进而根据其网络协议族而进行准确的连接操作。这样就解决了我们提出的第一个问题。具体的函数实现如下&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing1" style="color: #996699;">清单 1</a>所示：</p><br style="font-family: Simsun; font-size: medium;" /><a name="listing1" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 1. 使用 getaddrinfo 识别 IPv4 和 IPv6&nbsp;</strong></a><br style="font-family: Simsun; font-size: medium;" /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em; color: #000000; font-family: Simsun;"><tbody><tr><td style="font-family: arial, nsimsun, sans-serif; border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="width: 694px; margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">				  BaseSocket* BaseSocket::CreateSmartSocket(char* ipaddr)   { 		     struct addrinfo *answer, hint, *curr;      bzero(&amp;hint, sizeof(hint));      hint.ai_family = AF_UNSPEC;      hint.ai_socktype = SOCK_STREAM;      char ipstr2[128];      struct sockaddr_in  *sockaddr_ipv4;      struct sockaddr_in6 *sockaddr_ipv6;      	         int ret = getaddrinfo(ipaddr, NULL,&amp;hint, &amp;answer);      if (ret != 0) {          return NULL;      }      DeleteSmartSocket();       for (curr = answer; curr != NULL; curr = curr-&gt;ai_next) {          switch (curr-&gt;ai_family){              case AF_UNSPEC:                  //do something here                  break;              case AF_INET:                  sockaddr_ipv4 = reinterpret_cast&lt;struct sockaddr_in *&gt;( curr-&gt;ai_addr);                  inet_ntop(AF_INET, &amp;sockaddr_ipv4-&gt;sin_addr, ipstr2,sizeof(ipstr2));                  smartSocketmap[typeIPv4]=new  SocketV4(ipstr2);                  break;              case AF_INET6:                  sockaddr_ipv6 = reinterpret_cast&lt;struct sockaddr_in6 *&gt;( curr-&gt;ai_addr);                  inet_ntop(AF_INET6, &lt;sockaddr_ipv6-&gt;sin6_addr, ipstr2,sizeof(ipstr2));                  smartSocketmap[typeIPv6]=new  SocketV6(ipstr2);                  break;          }      } 	         freeaddrinfo(answer);      if(!smartSocketmap.empty())      {          smartSocket=new BaseSocket();      }       return smartSocket;   }   </pre></td></tr></tbody></table><br style="font-family: Simsun; font-size: medium;" /><div style="clear: both; background-image: url(http://1.www.s81c.com/i/solid.gif); height: 1px; font-family: Simsun; font-size: medium; background-repeat: repeat no-repeat;"></div><p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px;"><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#ibm-pcon" style="color: #996699; display: inline; margin: 0px; padding: 0px 0px 0px 18px; text-decoration: none; background-image: url(http://1.www.s81c.com/i/v16/icons/u_bold.gif); font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat;">回页首</a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="major3"><span style="font-size: 1.5em; font-weight: bold;">代码结构设计</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">对于用户来说，他们只想实现网络连接，而并不希望了解太多网络连接上冗繁的细节。如何屏蔽 IPv4 和 IPv6 网络的差异性，让用户使用统一的函数接口来完成操作，就成为我们的第二个课题。 程序中申明了一个基类叫 BaseSocket，继承于它的两个子类 SocketV4 和 SocketV6 分别负责有关 IPv4、IPv6 网络环境下的各种操作。详见&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig4" style="color: #996699;">图 4</a>。</p><br style="font-family: Simsun; font-size: medium;" /><a name="fig4" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 4. 类继承图</strong></a><br style="font-family: Simsun; font-size: medium;" /><img alt="类继承图" height="204" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/classdiagram1.jpg" width="476" style="border: 0px; padding: 0.3em 5px 0.7em; font-family: Simsun; font-size: medium;" />&nbsp;<br style="font-family: Simsun; font-size: medium;" /><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">在设计 BaseSocket 类的时候，并没有把它作为一个单纯的基类来使用，而是把它设计成了一个 SocketV4 和 SocketV6 的代理类。我们都知道，C++ 支持向上类型转换（取一个对象的地址，并将其作为基类的地址使用），结合虚函数能够实现多态性，我们就在这里使用一个基类的指针使其指向不同的子类实例，并把这些指针放到一个容器内。这样设计的初衷是希望外部使用者只使用类的公共接口，享受类的服务，而无需关注类的内部实现细节。具体来说，就是在 IPv4、IPv6 同时存在的网络环境下，用户只需要享用 BaseSocket 类提供出的公共服务，而无需关注具体网络环境下网络操作的差异性。 为了达到上述的目的，BaseSocket 类的设计主要做了了以下几点处理：</p><ol style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>把构造和析构函数隐藏起来（单件模式）</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">在 BaseSocket 的函数申明中，通常作为类公共成员的构造函数和析构函数被塑造成了 protected 成员函数。而开放给用户创建真正操作对象的函数却是 CreateSmartSocket()。CreateSmartSocket() 函数会动态地根据网络环境创建合适的子类 SocketV4 或者 SocketV6，使用的方法就是调用上文中提到的 getaddrinfo() 函数。生成的子类对象都存储在静态 smartSocketmap 中。这样设计的原因是在于如果不这样做，用户就必须根据不同的网络来创建属于 IPv4 或者 IPv6 网络的 socket 子类，然后分别调用他们的成员函数，这样繁琐又不利于用户代码的维护和扩展。smartSocketmap 以这样的设计为用户构建对象创作的一个统一的接口，在不同网络下，只需要维护一套统一的代码，而无需为不同网络下的实现细节而费神。</p><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>向用户提供代理对象</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">在 BaseSocket 类中申明了两个 BaseSocket 类型的指针，一个是 smartSocket，另外一个是 m_cursocket。BaseSocket* smartSocket 是一个静态的全局代理指针，用户只通过它来进行网络操作。在客户程序中，只存在一份由 CreateSmartSocket() 函数创建的 smartSocket 的副本，这是因为在每次需要网络连接时网络环境相应是固定的，不会由 v4 网络突然转变成 v6 网络，一个副本在运行时已经满足使用需求。CreateSmartSocket() 函数会先去侦测存储空间堆上是否已经存在 smartSocket 指针，存在的话就会调用 DeleteSmartSocket() 删除之前创建的副本，然后再创建一个新的 smartSocket 指针，提供给用户使用。而 m_cursocket 是指向真正操作对象（子类）的指针。值得注意的是，m_cursocket 指针是隐藏在 BaseSocket 类中的，而 smartSocket 正是 BaseSocket 类为 m_cursocket 封装的一层代理指针。用户所知的仅仅是调用了 smartSocket 的某个成员函数，而实际上，程序通过把 m_cursocket 定位到 map smartSocketmap 中的某一项，获得了真正的 SocketV4 或者 SocketV6 对象。</p><br /><br /><a name="fig5"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 5. 对象结构图</strong></a><br /><img alt="对象结构图" height="350" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/objectdiagram1.jpg" width="500" style="border: 0px; padding: 0.3em 5px 0.7em;" />&nbsp;<br /><br /><br /><br /><a name="fig6"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 6. 获取 smartSocket 对象流程图</strong></a><br /><img alt="获取 smartSocket 对象流程图" height="781" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/flowchart1.jpg" width="500" style="border: 0px; padding: 0.3em 5px 0.7em;" />&nbsp;<br /><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;"><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig5" style="color: #996699;">图 5</a>和&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig6" style="color: #996699;">图 6</a>就展示了程序如何根据用户输入的地址信息判断网络类型，继而创建 smartSocket 对象的过程。</p><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>使用虚函数（实现多态性）</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">在基类中，主要操作的函数都被申明为虚函数。如果编译器发现一个类中有被声明为 virtual 的函数，就会为其生成一个虚函数表，也就是 VTABLE。VTABLE 实际上是一个函数指针的数组，每个虚函数占用这个数组的一个位置。派生类有自己的 VTABLE，但是派生类的 VTABLE 与基类的 VTABLE 有相同的函数排列顺序，同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候，编译器还会在每个实例的内存布局中增加一个 vptr 字段，该字段指向本类的 VTABLE。C++ 对于虚函数的调用采用晚捆绑，从而能够实现多态性。 在程序中，m_cursocket 虽然是一个基类指针，但它指向的却是一个子类对象地址。由于这样的转换是子类向上转换，所以是安全的。指向正确的子类对象后，如果需要调用成员函数，就能通过本实例中的 vptr 指针指向本类的 VTABLE，由此获得正确的子类成员函数的地址来进行操作。</p><br /><br /><a name="fig7"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">图 7. 利用 smartSocket 对象进行网络连接流程图</strong></a><br /><img alt="利用 smartSocket 对象进行网络连接流程图" height="756" src="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/flowchart2.jpg" width="500" style="border: 0px; padding: 0.3em 5px 0.7em;" />&nbsp;<br /><br /><br /></li></ol><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#fig7" style="color: #996699;">图 7</a>描述了 m_cursocket 如何进行类型转换，获得准确的子类对象，并且调用子类 Connect 函数的过程。</p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">综上所述，通过以上三点，就可以降低用户程序和网络操作 Socket 部分的耦合性。让用户容易地实现他们所需要的网络连接，而不必要太关注网络环境的细节。同样，因为耦合性降低，有关 Socket 代码部分的更新和维护也相对方便，不会牵一发而动全身。</p><br style="font-family: Simsun; font-size: medium;" /><div style="clear: both; background-image: url(http://1.www.s81c.com/i/solid.gif); height: 1px; font-family: Simsun; font-size: medium; background-repeat: repeat no-repeat;"></div><p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px;"><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#ibm-pcon" style="color: #996699; display: inline; margin: 0px; padding: 0px 0px 0px 18px; text-decoration: none; background-image: url(http://1.www.s81c.com/i/v16/icons/u_bold.gif); font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat;">回页首</a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="major4"><span style="font-size: 1.5em; font-weight: bold;">OpenSSL 之 https 应用案例</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">下面我们展示一个网络连接实例，在这个实例中，我们会使用到 SSL 连接。众所周知，有些 server 或者网站会启用 SSL 进行安全连接，那么对于这一类的网络连接就不是简单的使用 socket 可以解决的，我们必须借用 OpenSSL 来帮助我们实现。通常我们的底层数据是用 OpenSSL 的 BIO 对象来处理的，借助 BIO_new_ssl(), BIO_new_accept() 等函数轻松实现 IPv4 环境下的网络安全连接。然而这些方法在 IPv6 的环境下却没有实现很好的支持。为此，我们需要另辟蹊径来达成我们的目标。经过一段时间对 OpenSSL 文档的研究，我们发现以下方法既可以实现我们安全连接的目的，又可以同时支持 IPv4 和 IPv6 两种网络环境，具有比较好的可扩展性。这个方法十分简单，那就是手工创建一个 socket，该 socket 连接了一个 IPv6 或者 IPv4 地址，然后将 socket 绑定到某个 SSL 对象上就可以实现 SSL 的连接了。</p><br style="font-family: Simsun; font-size: medium;" /><a name="listing2" style="font-family: Simsun; font-size: medium;"><strong style="font-size: 0.76em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">具体的实现方法如下列程序片段所示</strong></a><br style="font-family: Simsun; font-size: medium;" /><ol style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>网络连接</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">这个部分首先调用 BaseSocket::GetSmartSocket（）创建了一个 HTTPsSocket，HTTPsSocket 得到的就是代理指针 smartSocket 的地址，然后试图调用 HTTPsSocket-&gt;sockConnect() 连接 SSL 端口。从调用过程可见用户根本不用关心他现在所处的网络是 IPv4 还是 IPv6 网络，只要调用统一的函数接口就可以了。如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing2" style="color: #996699;">清单 2</a>所示。</p><br /><br /><a name="listing3"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 2. smartSocket 建立网络连接</strong></a><br /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em;"><tbody><tr><td style="border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">						    /* Create a HTTPsSocket */   if((HTTPsSocket=BaseSocket::GetSmartSocket())==NULL)      HTTPsSocket =BaseSocket::CreateSmartSocket(const_cast&lt;char *&gt;(SPName.c_str()));   if(HTTPsSocket==NULL)   {      return -1;   }    /* Connect the HTTPsSocket to SSLport */   if (HTTPsSocket-&gt;isConnected())   {      HTTPsSocket-&gt;disconnect();   }   if(HTTPsSocket-&gt;sockConnect(SSLport))   {      return SOCKETERR;   }   // END OF CONNECT TO THE SSLport  			</pre></td></tr></tbody></table><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>初始化 SSL</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">这里是为了实现安全连接，对 openSSL 对象进行创建和初始化配置。如下&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing3" style="color: #996699;">清单 3</a>所示。</p><br /><br /><a name="listing4"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 3. 初始化 OpenSSL</strong></a><br /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em;"><tbody><tr><td style="border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">						  /* Lets get nice error messages */   SSL_load_error_strings();    /* Setup all the global SSL stuff */   SSL_library_init(); 	  ctx=SSL_CTX_new(SSLv23_client_method());   if(ctx==NULL)   {      return -1;   }    /* Lets make a SSL structure */   if (!(ssl = SSL_new(ctx)))   {      return -1;   }  			</pre></td></tr></tbody></table><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>绑定 smartSocket 到 SSL</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">通过把 smartSocket 的系统 fd 绑定到生成的 ssl 对象上，就可以在 HTTPsSocket 上加载 openSSL 协议了。最后使用 SSL_connect() 函数进行连接。如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing4" style="color: #996699;">清单 4</a>所示。</p><br /><br /><a name="listing5"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 4. 绑定 smartSocket 到 SSL</strong></a><br /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em;"><tbody><tr><td style="border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">						  /* Lets bind socket fd to the SSL structure */   int fd=HTTPsSocket-&gt;getSocketFD();   SSL_set_fd(ssl, fd);    /* Lets use a SSL connect */   if (SSL_connect (ssl) &lt;= 0 )   {      return -1;   }  			</pre></td></tr></tbody></table><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>接收数据</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">利用先建立起的 smartSocket 连接进行数据接收。如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing5" style="color: #996699;">清单 5</a>所示。</p><br /><br /><a name="listing6"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 5. 接收数据</strong></a><br /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em;"><tbody><tr><td style="border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">						  int each = 0; 	  /*If it is openSSL enabled connection, then use SSL_read to get the message,   otherwise use  default HTTPSocket-&gt;sockRecv() function*/ 	  if(SSLenabled)   { 	     each = SSL_read(ssl, buff, len );   }   else   {      each = HTTPSocket-&gt;sockRecv((unsigned char *)buff, len, 0 );   }   return each; 	 			</pre></td></tr></tbody></table><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 3px;"><strong>发送数据</strong><br /><p style="margin: 0px; padding: 0.3em 5px 0px 0px; font-size: 1em;">利用先建立起的 smartSocket 连接进行数据发送。如&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#listing6" style="color: #996699;">清单 6</a>所示。</p><br /><br /><a name="listing7"><strong style="font-size: 1em; padding: 0.3em 5px 0.7em; font-family: arial, sans-serif;">清单 6. 发送数据</strong></a><br /><table width="50%" cellpadding="0" cellspacing="0" border="0" style="font-size: 0.8em;"><tbody><tr><td style="border-style: solid; border-color: #cccccc; padding: 2px 2px 5px; background-color: #f7f7f7 !important;"><pre style="margin-top: 0px; margin-bottom: 0px; font-family: 'Andale Mono', 'Lucida Console', Monaco, Liberation, fixed, monospace; font-size: 11px; overflow: auto;">						  /*If it is openSSL enabled connection, then use SSL_write to send the message,   otherwise use  default HTTPSocket-&gt;sockSend() function*/ 	  int trans = 0, each = 0;   if(SSLenabled)   {      while (trans &lt; len)      {          if((each = SSL_write(ssl, buf + trans, len - trans)) == 0)          {              return SOCKETERR ;          }          trans += each ;      }   }   else   {      while (trans &lt; len)      {          if((each=HTTPSocket-&gt;sockSend((unsigned char *)buf+trans,len-trans,0))==0)          {              return SOCKETERR ;          }          trans += each ;      }   }   return trans;  			</pre></td></tr></tbody></table><br /><br /></li></ol><div style="clear: both; background-image: url(http://1.www.s81c.com/i/solid.gif); height: 1px; font-family: Simsun; font-size: medium; background-repeat: repeat no-repeat;"></div><p ibm-back-to-top"="" style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 5px; font-size: 0.76em; clear: both; text-align: right; height: 15px;"><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-ipv4v6-sockapp/#ibm-pcon" style="color: #996699; display: inline; margin: 0px; padding: 0px 0px 0px 18px; text-decoration: none; background-image: url(http://1.www.s81c.com/i/v16/icons/u_bold.gif); font-weight: bold; background-position: 0px -1px; background-repeat: no-repeat no-repeat;">回页首</a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="major5"><span style="font-size: 1.5em; font-weight: bold;">小结</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;">本文介绍了一种屏蔽 socket 网络连接细节的代码设计，该方法可以很好地适应 IPv4 和 IPv6 的网络环境，而且它提供给用户一个统一的接口，把用户从 v4 或者 v6 网络的连接细节中解放出來。</p><br style="font-family: Simsun; font-size: medium;" /><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="resources"><span style="font-size: 1.5em; font-weight: bold;">参考资料</span></a></p><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><strong style="font-size: 1em;">学习</strong></p><ul style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;"><a href="http://msdn.microsoft.com/en-us/library/ms738649(VS.85).aspx" style="color: #996699;">IPv6 Guide for Windows Sockets Applications</a><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;"><a href="http://msdn.microsoft.com/en-us/library/ms740624(VS.85).aspx" style="color: #996699;">Using the Checkv4.exe Utility</a><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;"><a href="http://msdn.microsoft.com/en-us/library/ms737534(VS.85).aspx" style="color: #996699;">Appendix A: IPv4-only Source Code</a><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;"><a href="http://msdn.microsoft.com/en-us/library/ms737535(VS.85).aspx" style="color: #996699;">Appendix B: IP-version Agnostic Source Code</a><br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">在&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/" style="color: #996699;">developerWorks Linux 专区</a>&nbsp;寻找为 Linux 开发人员（包括&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/newto/" style="color: #996699;">Linux 新手入门</a>）准备的更多参考资料，查阅我们&nbsp;<a href="http://www.ibm.com/developerworks/cn/linux/best2009/index.html" style="color: #996699;">最受欢迎的文章和教程</a>。&nbsp;<br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">在 developerWorks 上查阅所有&nbsp;<a href="http://www.ibm.com/developerworks/cn/views/linux/libraryview.jsp?search_by=Linux+%E6%8A%80%E5%B7%A7" style="color: #996699;">Linux 技巧</a>&nbsp;和&nbsp;<a href="http://www.ibm.com/developerworks/cn/views/linux/libraryview.jsp?type_by=%E6%95%99%E7%A8%8B" style="color: #996699;">Linux 教程</a>。&nbsp;<br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">随时关注 developerWorks&nbsp;<a href="http://www.ibm.com/developerworks/cn/offers/techbriefings/" style="color: #996699;">技术活动</a>和<a href="http://www.ibm.com/developerworks/cn/swi/" style="color: #996699;">网络广播</a>。&nbsp;<br /><br /></li></ul><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><strong style="font-size: 1em;">获得产品和技术</strong></p><ul style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">下载&nbsp;<a href="http://www.ibm.com/developerworks/cn/downloads/" style="color: #996699;">IBM 软件试用版</a>，体验强大的 DB2&#174;，Lotus&#174;，Rational&#174;，Tivoli&#174;和 WebSphere&#174;软件。<br /><br /></li></ul><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><strong style="font-size: 1em;">讨论</strong></p><ul style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 5px; font-size: 0.76em; font-family: Simsun;"><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;"><a href="http://www.ibm.com/developerworks/forums/dw_forum.jsp?forum=375&amp;cat=5" style="color: #996699;">参与论坛讨论</a>。<br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">查看&nbsp;<a href="http://www.ibm.com/developerworks/blogs/" style="color: #996699;">developerWorks 博客</a>的最新信息。<br /><br /></li><li style="font-family: arial, nsimsun, sans-serif; margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-right: 5px; padding-bottom: 3px;">欢迎加入&nbsp;<a href="http://www.ibm.com/developerworks/cn/mydeveloperworks/" style="color: #996699;">My developerWorks 中文社区</a>。<br /><br /></li></ul><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 0.3em 5px 0.7em; font-size: 0.76em;"><a name="author"><span style="font-size: 1.5em; font-weight: bold;">作者简介</span></a></p><div ibm-portrait-module=""  ibm-alternate-two"="" style="margin: 0px 0px 1.2em; width: 710px; border-top-width: 1px; border-top-style: solid; border-top-color: #cccccc; font-family: Simsun; font-size: medium;"><div style="background-image: url(http://1.www.s81c.com/i/v16/t/container-gradient.gif); 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;"><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 5px; font-size: 0.76em;"><a name="author1"></a>陈鲁，2010 年 4 月加入 IBM CSTL，熟悉 C/C++、XML、Windows/Linux makefile。</p></div><div style="background-image: url(http://1.www.s81c.com/i/v16/t/container-gradient.gif); 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;"><p style="font-family: arial, nsimsun, sans-serif; margin: 0px; padding: 5px; font-size: 0.76em;"><a name="author2"></a>孙妍，2009 年 3 月加入 IBM CSTL，一直从事 preboot DSA 的 GUI 界面的开发工作。她熟悉 C/C++、JavaScript、HTML 和 Dojo。</p></div></div><img src ="http://www.cppblog.com/yehao/aggbug/204311.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2013-11-18 15:55 <a href="http://www.cppblog.com/yehao/articles/204311.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于网络字节序</title><link>http://www.cppblog.com/yehao/articles/167301.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 07 Mar 2012 03:13:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/167301.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/167301.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/167301.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/167301.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/167301.html</trackback:ping><description><![CDATA[<p style="color: #333333; font-family: Arial; line-height: 26px; text-align: left; "><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">不同的</span>&nbsp;<span style="font-size: 9pt; color: black; ">CPU</span>&nbsp;<span style="font-size: 9pt; color: black; ">有不同的字节序类型</span>&nbsp;<span style="font-size: 9pt; color: black; ">这些字节序是指整数在内存中保存的顺序</span>&nbsp;<span style="font-size: 9pt; color: black; ">这个叫做主机序</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">最常见的有两种</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">1</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">．</span>&nbsp;<span style="font-size: 9pt; color: black; ">Little endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">：将低序字节存储在起始地址</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">2</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">．</span>&nbsp;<span style="font-size: 9pt; color: black; ">Big endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">：将高序字节存储在起始地址</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><br /><span style="font-size: small; ">LE little-endian&nbsp;<br /></span></span><span style="font-size: 9pt; color: black; "><span style="font-size: small; ">最符合人的思维的字节序</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">地址低位存储值的低位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">地址高位存储值的高位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">怎么讲是最符合人的思维的字节序，是因为从人的第一观感来说</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">低位值小，就应该放在内存地址小的地方，也即内存地址低位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">反之，高位值就应该放在内存地址大的地方，也即内存地址高位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /><br />BE big-endian&nbsp;<br /></span><span style="font-size: 9pt; color: black; ">最直观的字节序</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">地址低位存储值的高位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">地址高位存储值的低位</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">为什么说直观，不要考虑对应关系</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">只需要把内存地址从左到右按照由低到高的顺序写出</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">把值按照通常的高位到低位的顺序写出</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">两者对照，一个字节一个字节的填充进去</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /><br /></span><span style="font-size: 9pt; color: black; ">例子：在内存中双字</span>&nbsp;<span style="font-size: 9pt; color: black; ">0x01020304(DWORD)</span>&nbsp;<span style="font-size: 9pt; color: black; ">的存储方式</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /><br /></span><span style="font-size: 9pt; color: black; ">内存地址</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br />4000 4001 4002 4003&nbsp;<br />LE 04 03 02 01&nbsp;<br />BE 01 02 03 04&nbsp;<br /><br /></span><span style="font-size: 9pt; color: black; ">例子：如果我们将</span>&nbsp;<span style="font-size: 9pt; color: black; ">0x1234abcd</span>&nbsp;<span style="font-size: 9pt; color: black; ">写入到以</span>&nbsp;<span style="font-size: 9pt; color: black; ">0x0000</span>&nbsp;<span style="font-size: 9pt; color: black; ">开始的内存中，则结果为</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; big-endian&nbsp;&nbsp; little-endian<br />0x0000&nbsp;&nbsp; 0x12&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xcd<br />0x0001&nbsp;&nbsp; 0x23&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0xab<br />0x0002&nbsp;&nbsp; 0xab&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x34<br />0x0003&nbsp;&nbsp; 0xcd&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0x12<br />x86</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">系列</span>&nbsp;<span style="font-size: 9pt; color: black; ">CPU</span>&nbsp;<span style="font-size: 9pt; color: black; ">都是</span>&nbsp;<span style="font-size: 9pt; color: black; ">little-endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">的字节序</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">.&nbsp;<br /><br /></span><span style="font-size: 9pt; color: black; ">网络字节顺序是</span>&nbsp;<span style="font-size: 9pt; color: black; ">TCP/IP</span>&nbsp;<span style="font-size: 9pt; color: black; ">中规定好的一种数据表示格式，它与具体的</span>&nbsp;<span style="font-size: 9pt; color: black; ">CPU</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型、操作系统等无关，从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用</span>&nbsp;<span style="font-size: 9pt; color: black; ">big endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">排序方式。</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><br /></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">为了进行转换</span>&nbsp;<span style="font-size: 9pt; color: black; ">bsd socket</span>&nbsp;<span style="font-size: 9pt; color: black; ">提供了转换的函数</span>&nbsp;<span style="font-size: 9pt; color: black; ">有下面四个</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">htons&nbsp;</span></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">把</span>&nbsp;<span style="font-size: 9pt; color: black; ">unsigned short</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型从主机序转换到网络序</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">htonl&nbsp;</span></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">把</span>&nbsp;<span style="font-size: 9pt; color: black; ">unsigned long</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型从主机序转换到网络序</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">ntohs&nbsp;</span></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">把</span>&nbsp;<span style="font-size: 9pt; color: black; ">unsigned short</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型从网络序转换到主机序</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">ntohl&nbsp;</span></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">把</span>&nbsp;<span style="font-size: 9pt; color: black; ">unsigned long</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型从网络序转换到主机序</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><br /></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">在使用</span>&nbsp;<span style="font-size: 9pt; color: black; ">little endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">的系统中</span>&nbsp;<span style="font-size: 9pt; color: black; ">这些函数会把字节序进行转换</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: 9pt; color: black; ">在使用</span>&nbsp;<span style="font-size: 9pt; color: black; ">big endian</span>&nbsp;<span style="font-size: 9pt; color: black; ">类型的系统中</span>&nbsp;<span style="font-size: 9pt; color: black; ">这些函数会定义成空宏</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><br /></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">同样</span>&nbsp;<span style="font-size: 9pt; color: black; ">在网络程序开发时</span>&nbsp;<span style="font-size: 9pt; color: black; ">或是跨平台开发时</span>&nbsp;<span style="font-size: 9pt; color: black; ">也应该注意保证只用一种字节序</span>&nbsp;<span style="font-size: 9pt; color: black; ">不然两方的解释不一样就会产生</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">bug.<br /><br /></span><span style="font-size: 9pt; color: black; ">注：</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">1</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">、网络与主机字节转换函数</span>&nbsp;<span style="font-size: 9pt; color: black; ">:htons ntohs htonl ntohl (s&nbsp;</span><span style="font-size: 9pt; color: black; ">就是</span>&nbsp;<span style="font-size: 9pt; color: black; ">short l</span>&nbsp;<span style="font-size: 9pt; color: black; ">是</span>&nbsp;<span style="font-size: 9pt; color: black; ">long h</span>&nbsp;<span style="font-size: 9pt; color: black; ">是</span>&nbsp;<span style="font-size: 9pt; color: black; ">host n</span>&nbsp;<span style="font-size: 9pt; color: black; ">是</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">network)<br />2</span>&nbsp;<span style="font-size: 9pt; color: black; ">、不同的</span>&nbsp;<span style="font-size: 9pt; color: black; ">CPU</span>&nbsp;<span style="font-size: 9pt; color: black; ">上运行不同的操作系统，字节序也是不同的，参见下表。</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /></span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">处理器</span>&nbsp;<span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span style="font-size: 9pt; color: black; ">操作系统</span>&nbsp;<span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span style="font-size: 9pt; color: black; ">字节排序</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">Alpha&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><span style="font-size: small; ">全部</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp; Little endian<br />HP-PA&nbsp;&nbsp;&nbsp;&nbsp; NT&nbsp;&nbsp;&nbsp;&nbsp; Little endian<br />HP-PA&nbsp;&nbsp;&nbsp;&nbsp; UNIX&nbsp;&nbsp;&nbsp;&nbsp; Big endian<br />Intelx86&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span style="font-size: 9pt; color: black; ">全部</span>&nbsp;<span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp; Little endian &lt;-----x86</span>&nbsp;<span style="font-size: 9pt; color: black; ">系统是小端字节序系统</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">Motorola680x()&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><span style="font-size: small; ">全部</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp; Big endian<br />MIPS&nbsp;&nbsp;&nbsp;&nbsp; NT&nbsp;&nbsp;&nbsp;&nbsp; Little endian<br />MIPS&nbsp;&nbsp;&nbsp;&nbsp; UNIX&nbsp;&nbsp;&nbsp;&nbsp; Big endian<br />PowerPC&nbsp;&nbsp;&nbsp;&nbsp; NT&nbsp;&nbsp;&nbsp;&nbsp; Little endian<br />PowerPC&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span style="font-size: 9pt; color: black; ">非</span>&nbsp;<span style="font-size: 9pt; color: black; ">NT&nbsp;&nbsp;&nbsp;&nbsp; Big endian&nbsp;&nbsp; &lt;-----PPC</span>&nbsp;<span style="font-size: 9pt; color: black; ">系统是大端字节序系统</span>&nbsp;</span><span style="font-size: 9pt; color: black; "><br /><span style="font-size: small; ">RS/6000&nbsp;&nbsp;&nbsp;&nbsp; UNIX&nbsp;&nbsp;&nbsp;&nbsp; Big endian<br />SPARC&nbsp;&nbsp;&nbsp;&nbsp; UNIX&nbsp;&nbsp;&nbsp;&nbsp; Big endian<br />IXP1200 ARM</span>&nbsp;</span><span style="font-size: small; "><span style="font-size: 9pt; color: black; ">核心</span>&nbsp;<span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;<span style="font-size: 9pt; color: black; ">全部</span>&nbsp;<span style="font-size: 9pt; color: black; ">&nbsp;&nbsp;&nbsp;&nbsp; Little endian</span></span></p><p style="color: #333333; font-family: Arial; line-height: 26px; text-align: left; "><span style="font-size: 9pt; color: black; "><span style="font-size: small; ">2.</span></span></p><p style="color: #333333; font-family: Arial; line-height: 26px; text-align: left; "></p><table border="0" cellspacing="0" cellpadding="0" width="760" align="center" bgcolor="#ffffff" style="color: #333333; font-family: Arial; font-size: 14px; line-height: 26px; text-align: left; border-collapse: collapse; "><tbody><tr><td align="center"><table border="0" cellspacing="0" cellpadding="0" width="740" style="border-collapse: collapse; "><tbody><tr><td width="740"><div style="margin-top: 15px; margin-right: 15px; margin-bottom: 15px; margin-left: 15px; "><div><div text"=""><p><strong><span style="font-size: small; ">一、字节序定义</span></strong></p><p><span style="font-size: small; ">字节序，顾名思义字节的顺序，再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。</span></p><p><span style="font-size: small; ">其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。</span></p><p><span style="font-size: small; ">在所有的介绍字节序的文章中都会提到字节序分为两类：Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下：<br />a) Little-Endian就是低位字节排放在内存的低地址端，高位字节排放在内存的高地址端。<br />b) Big-Endian就是高位字节排放在内存的低地址端，低位字节排放在内存的高地址端。<br />c) 网络字节序：4个字节的32 bit值以下面的次序传输：首先是0～7bit，其次8～15bit，然后16～23bit，最后是24~31bit。这种传输次序称作大端字节序。由于 TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序，因此它又称作网络字节序。比如，以太网头部中2字节的&#8220;</span>&nbsp;<span style="font-size: small; ">以太网帧类型&#8221;，表示后面数据的类型。对于ARP请求或应答的以太网帧类型&nbsp;来说，在网络传输时，发送的顺序是0x08，0x06。在内存中的映象如下图所示：<br />栈底 （高地址）<br /></span><span style="font-size: small; ">---------------<br />0x06 -- 低位&nbsp;<br />0x08 -- 高位<br />---------------<br />栈顶 （低地址）<br />该字段的值为0x0806。按照大端方式存放在内存中。</span></p><p><strong><span style="font-size: small; ">二、高/低地址与高低字节</span></strong></p><p><span style="font-size: small; ">首先我们要知道我们C程序映像中内存的空间布局情况：在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明，大致如下图：<br />----------------------- 最高内存地址 0xffffffff<br />| 栈底<br />.<br />.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 栈<br />.<br />栈顶<br />-----------------------<br />|<br />|<br />/|/</span></p><p><span style="font-size: small; ">NULL (空洞)</span></p><p><span style="font-size: small; ">/|/<br />|<br />|<br />-----------------------<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 堆<br />-----------------------<br />未初始化的数据<br />----------------(统称数据段)<br />初始化的数据<br />-----------------------<br />正文段(代码段)<br />----------------------- 最低内存地址 0x00000000</span></p><p><span style="font-size: small; ">以上图为例如果我们在栈上分配一个unsigned char buf[4]，那么这个数组变量在栈上是如何布局的呢[注1]？看下图：<br />栈底 （高地址）<br />----------<br />buf[3]<br />buf[2]<br />buf[1]<br />buf[0]<br />----------<br />栈顶 （低地址）</span></p><p><span style="font-size: small; ">现 在我们弄清了高低地址，接着来弄清高/低字节，如果我们有一个32位无符号整型0x12345678(呵呵，恰好是把上面的那4个字节buf看成一个整 型)，那么高位是什么，低位又是什么呢？其实很简单。在十进制中我们都说靠左边的是高位，靠右边的是低位，在其他进制也是如此。就拿 0x12345678来说，从高位到低位的字节依次是0x12、0x34、0x56和0x78。</span></p><p><span style="font-size: small; ">高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义，并用图示说明两种字节序：<br />以unsigned int value = 0x12345678为例，分别看看在两种字节序下其存储情况，我们可以用unsigned char buf[4]来表示value：<br />Big-Endian: 低地址存放高位，如下图：<br />栈底 （高地址）<br />---------------<br />buf[3] (0x78) -- 低位<br />buf[2] (0x56)<br />buf[1] (0x34)<br />buf[0] (0x12) -- 高位<br />---------------<br />栈顶 （低地址）</span></p><p><span style="font-size: small; ">Little-Endian: 低地址存放低位，如下图：<br />栈底 （高地址）<br />---------------<br />buf[3] (0x12) -- 高位<br />buf[2] (0x34)<br />buf[1] (0x56)<br />buf[0] (0x78) -- 低位<br />---------------<br />栈顶 （低地址）</span></p><p><span style="font-size: small; ">在现有的平台上Intel的X86采用的是Little-Endian，而像Sun的SPARC采用的就是Big-Endian。</span></p><p><strong><span style="font-size: small; ">三、例子</span></strong></p><p><span style="font-size: small; ">嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节，而Big-endian模式对操作数的存放方式是从高字节到低字节。</span></p><p><span style="font-size: small; ">例如，16bit宽的数0x1234在Little-endian模式CPU内存中的存放方式（假设从地址0x4000开始存放）为：</span></p><p><span style="font-size: small; ">内存地址 存放内容<br />0x4001&nbsp;&nbsp;&nbsp; 0x12<br />0x4000&nbsp;&nbsp;&nbsp; 0x34</span></p><p><span style="font-size: small; ">而在Big-endian模式CPU内存中的存放方式则为：</span></p><p><span style="font-size: small; ">内存地址 存放内容<br />0x4001&nbsp;&nbsp;&nbsp; 0x34<br />0x4000&nbsp;&nbsp;&nbsp; 0x12<br /><br />32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式（假设从地址0x4000开始存放）为：</span></p><p><span style="font-size: small; ">内存地址 存放内容<br />0x4003&nbsp;&nbsp;&nbsp;&nbsp; 0x12<br />0x4002&nbsp;&nbsp;&nbsp;&nbsp; 0x34<br />0x4001&nbsp;&nbsp;&nbsp;&nbsp; 0x56<br />0x4000&nbsp;&nbsp;&nbsp;&nbsp; 0x78<br /><br />而在Big-endian模式CPU内存中的存放方式则为：</span></p><p><span style="font-size: small; ">内存地址 存放内容<br />0x4003&nbsp;&nbsp;&nbsp;&nbsp; 0x78<br />0x4002&nbsp;&nbsp;&nbsp;&nbsp; 0x56<br />0x4001&nbsp;&nbsp;&nbsp;&nbsp; 0x34<br />0x4000&nbsp;&nbsp;&nbsp;&nbsp; 0x12</span></p></div></div></div></td></tr></tbody></table></td></tr></tbody></table><br /><span style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">网络传输一般采用大端序，也被称之为</span><strong style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">网络字节序</strong><span style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">，或</span><strong style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">网络序</strong><span style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">。</span><a href="http://zh.wikipedia.org/wiki/IP" title="IP" style="text-decoration: none; background-image: none; background-attachment: initial; background-origin: initial; background-clip: initial; background-color: #ffffff; font-family: sans-serif; font-size: 13px; line-height: 19px; color: red; ">IP</a><span style="font-family: sans-serif; font-size: 13px; line-height: 19px; background-color: #ffffff; color: red; ">协议中定义大端序为网络字节序。</span><span style="color: red; ">&nbsp;</span><br /><a href="http://blog.csdn.net/zhaojiangwei102/article/details/4532184">http://blog.csdn.net/zhaojiangwei102/article/details/4532184</a><img src ="http://www.cppblog.com/yehao/aggbug/167301.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-03-07 11:13 <a href="http://www.cppblog.com/yehao/articles/167301.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用CFtpConnection从ftp虚拟目录下载成功但上传失败的原因(笔记)</title><link>http://www.cppblog.com/yehao/articles/165686.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 15 Feb 2012 09:40:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/165686.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/165686.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/165686.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/165686.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/165686.html</trackback:ping><description><![CDATA[使用CFtpConnection过程中遇到ftp虚拟目录下载成功但上传失败,ftp服务器为windows 2003,网上查了很久仍然没有解决,GetLastError()返回12003错误码,InternetGetLastResponseInfo返回具体错误信息如下<br /><span style="color: red">InternetGetLastResponseInfo:200 Type set to I.200 PORT command successful.550 Virtual XmlFilesFolder\TaskConfig.xml: Access is denied. <br /></span>在连接ftp服务器时使用Aministartor帐号即可解决问题<br /><strong>如果使用CInternetFile的Write上传文件不会有这问题</strong><img src ="http://www.cppblog.com/yehao/aggbug/165686.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2012-02-15 17:40 <a href="http://www.cppblog.com/yehao/articles/165686.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>P2P网络技术概览与实现原理</title><link>http://www.cppblog.com/yehao/articles/153733.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 18 Aug 2011 08:13:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/153733.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/153733.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/153733.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/153733.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/153733.html</trackback:ping><description><![CDATA[<a href="http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/07/1263278.html">http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/07/1263278.html</a><br />穿越NAT的意义： 
<p>　　NAT是为了节省IP地址而设计的，但它隐藏了内网机器的地址，&#8220;意外&#8221;起到了安全的作用。对外不可见，不透明的内部网络也与互联网的&#8220;公平&#8221; 应用，&#8220;相互共享&#8221;的思想所不容，尤其是P2P网络中&#8220;相互服务&#8221;的宗旨，所以穿越NAT，让众多内部网络的机器也参与到P2P网络中的大集体中来，一直 是P2P开发者的所希望的。穿越NAT需要借助外部的支持，说白了就是&#8220;内外勾结&#8221;，骗过NAT。很多P2P网络成功地实现了这一目标，但还是有一些&#8220;遗 憾&#8221;---并非所有的情况下都可以。由于客户端是主动登录P2P网络才可穿越，所以P2P的方式也没有违背企业的内部管理原则，毕竟&#8220;自由世界&#8221;的加入都 是自觉自愿的。</p>
<p>　　NAT原理：</p>
<p>　　NAT(Network Address Translation)网络地址转换/网络地址翻译。</p>
<p>　　工作原理：NAT主要的通过对数据包头的地址替换来完成内网计算机访问外网服务的。当内部机器要访问外部网络时，NAT设备把内部的IP1与端 口号1(网络层地址与传输层地址)，转换成NAT的外部IP2与新的端口号2，再送给外部网络，数据返回时，再把目的为IP2:端口2的数据包替换为 IP1:端口1，送给内网机器。若通讯协议的内容中有IP地址的传递，如FTP协议，NAT在翻译时还要注意数据包内涉及协议地址交互的地方也要替换，否 则协议就会出现地址混乱。在NAT设备中维护了这个要替换地址的映射表，并根据内部计算机的通讯需求维护该表。外部网络来数据包能否进入NAT，主要是看 是否已经有可映射的表项，若没有就会丢弃。</p>
<p>　　<img height="341" alt="1" src="http://www.bairuitech.com/upimg/allimg/080114/1211500.jpg" width="500" /></p>
<p>　　NAT的外部公网地址可以是一个IP，也可以是一个网段，形成地址池。NAT还可以把某个外网地址直接影射给内网的某个服务器，让外网的用户可以直接访问到这台服务器。NAT的工作的隐藏内网的机器，但允许内网主动打开到外网的通讯&#8220;通道&#8221;，也就是建立映射表项。</p>
<p>　　NAT给P2P带来的问题是：NAT只允许单方面发起连接，通讯的双方不是平等的，P2P网络的基础有了问题，具体的表现为：</p>
<p>　　内网主机IP是私有的，外部主机看不到，也无法主动发起连接</p>
<p>　　即使知道了内网IP，但NAT会丢弃没有在影射表的数据包</p>
<p>　　内网主机可以作为客户端访问外网，但不能作为服务器提供服务</p>
<p>　　当两个主机都位于各自的NAT之后，要实现P2P的连接，就不仅是谁主动的问题，而是如何解决在两个NAT上同时有对方映射表项的问题。</p>
<p>STUN协议(IETF RFC 3489)：</p>
<p>　　STUN协议是一种通道协议，可以作为正式通讯前的通路建立，它采用的是用户终端干预的一种方法，可以解决应用协议内部传递IP地址给NAT带 来的麻烦。用户通过其他方法得到其地址对应在NAT出口上的对外地址，然后在报文负载中所描述的地址信息就直接填写NAT上对外地址，而不是内网的私有 IP，这样报文的内容在经过NAT时就按普通的NAT流程转换报文头部的IP地址即可，负载内的IP地址信息无需再修改。利用STUN的思路可以穿越 NAT。STUN协议是客户端/服务器协议，分两种请求方式：一是UDP发送的绑定请求(Binding Requests)，二是TCP发送的秘密请求(Shared Secret Requests)。绑定请求用于确定NAT分配的绑定地址。</p>
<p>　　STUN标准中，根据内部终端的地址(P:p)到NAT出口的公网地址(A:b)的影射方式，把NAT分为四种类型：</p>
<p>　　<img height="277" alt="2" src="http://www.bairuitech.com/upimg/allimg/080114/1211501.jpg" width="500" /></p>
<p>　　1. Full Cone：来自相同的内部地址的请求消息映射为相同的外部地址，与外部地址(目的地址)无关。映射关系为P:p&#8596;A:b，任何外部主机可通过(A:b)发送到数据到(P:p)上。</p>
<p>　　2. Restricted Cone：来自相同的内部地址的请求消息映射为相同的外部地址，返回的数据只接受该内部节点曾发数据的那个目的计算机地址X。映射关系为P:p&#8596;A:b&#8596;X，只有来自X的数据包才可通过(A:b)发送到数据到(P:p)上。</p>
<p>　　3. Port Restricted Cone：来自相同的内部地址的请求消息映射为相同的外部地址，返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为 P:p&#8596;A:b&#8596;X:x，只有来自X:x的数据包才可通过(A:b)发送到数据到(P:p)上。</p>
<p>　　4. Symmetric(对称) NAT：只有来自相同的内部地址(P:p)，并且发送到同一个地址(X:x) 的请求消息，才被映射为相同的外部地址(A:b)，返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为P:p&#8596;A:b&#8596;X:x，当 (P:p)访问(Y:y)时，映射为P:p&#8596;B:c&#8596;Y:y。</p>
<p>　　P2P利用STUN穿越NAT：</p>
<p>　　位于NAT后面终端A与B要穿越NAT直接通讯，可以借助在公网上的第三者Server来帮助。</p>
<p>　　穿越NAT的情况分为为两种方式：</p>
<p>&nbsp;&nbsp;&nbsp; 1、<strong>一方在NAT之后，一方在公网上</strong>。这种情况相对简单，<span style="background-color: yellow">只要让NAT之后的终端先发起通讯，NAT就没有作用了，它可以从Server上取得另一个Peer的地址，主动连接，回来的数据包就可以方便地穿越NAT</span>。</p>
<p>&nbsp;&nbsp;&nbsp; 2、双方都在NAT之后，连接的成功与否与两个NAT的类型有关。主要的思路的<span style="background-color: yellow">先通过终端与Server的连接，获得两个终端在NAT外部的地址(IP与 端口号)，再由终端向对方的外部地址发邀请包，获取自己与对方通讯的外部地址</span>，俗称为&#8220;打洞&#8221;。关键是获取了NAT外部映射的地址，就可以发包直接沟通， 建立连接。但当一方是对称型，另一方是Port Restricted或对称型时，无法有效获取外部地址，邀请包无法到达对方，也就无法穿越NAT。具体的分析可以根据两个NAT的类型分成若干情况分 析，这里给一般的穿越例子。</p>
<p><img height="295" alt="3" src="http://www.bairuitech.com/upimg/allimg/080114/1211502.jpg" width="500" /></p>
<p>实例：UDP穿越NAT：</p>
<p>　　A登录Server，NAT A分配端口11000，Server得到A的地址为100.10.10.10:11000</p>
<p>　　B登录Server，NAT B分配端口22000，Server得到B的地址为200.20.20.20:22000</p>
<p>　　此时B会把直接来自A的包丢弃，所以要在NAT B上打一个方向为A的洞，那么A就可以向200.20.20.20:22000发送数据了</p>
<p>　　<strong style="background-color: yellow">打洞的指令来自Server</strong>。B向A的地址100.10.10.10:11000发一个UDP报文，被NAT A丢弃，但在NAT B上建立映射记录，NAT B不在丢弃来自A的报文。</p>
<p>　　Server通知A可以通讯，A发起数据UDP包给B，NAT B放行，B收到A的包，双方开始通讯</p>
<p>　　注：若是对称NAT，当B向A打洞的端口要重新分配(NAT A不会再分配11000端口)，B无法获取这个端口，所以不适用本方法。</p>
<p>　　实例：TCP穿越NAT：</p>
<p>　　A登录Server，NAT A分配端口11000，Server得到A的地址为100.10.10.10:11000</p>
<p>　　B登录Server，NAT B分配端口22000，Server得到B的地址为200.20.20.20:22000</p>
<p>　　A向B发送TCP数据包SYN:192.168.10.11:1234=&gt;200.20.20.20:22000，在NAT A上打洞</p>
<p>　　B向A发送TCP数据包SYN:192.168.20.22:1234=&gt;100.10.10.10:11000，在NAT B上打洞</p>
<p>　　通道建立，A与B三次握手建立TCP连接</p><img src ="http://www.cppblog.com/yehao/aggbug/153733.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-08-18 16:13 <a href="http://www.cppblog.com/yehao/articles/153733.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字IO模型(一) Select模型</title><link>http://www.cppblog.com/yehao/articles/153732.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 18 Aug 2011 08:11:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/153732.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/153732.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/153732.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/153732.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/153732.html</trackback:ping><description><![CDATA[<a href="http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268023.html">http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268023.html</a><br />讲一下套接字模式和套接字I/O模型的区别。先说明一下，只针对Winsock，如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套，那就不关我的事。<br /><strong>套接字模式</strong>：<span style="background-color: yellow">阻塞套接字</span>和<span style="background-color: yellow">非阻塞套接字</span>。或者叫同步套接字和异步套接字。<br /><strong>套接字模型</strong>：描述如何对套接字的I/O行为进行管理。<br />Winsock提供的I/O模型一共有五种：<br />
<p>select,WSAAsyncSelect,WSAEventSelect,Overlapped,Completion。今天先讲解select。</p>
<p>1：select模型择模（选型）<br /></p>
<p>先看一下下面的这句代码：</p>
<p><strong>阻塞socket</strong>：<br /></p>
<p>int iResult = recv(s, buffer,1024);<br /></p>
<p>&nbsp;&nbsp; 这是用来接收数据的，在默认的阻塞模式下的套接字里，recv会阻塞在那里，直到套接字连接上有数据可读，把数据读到buffer里后recv函数才会返 回，不然就会一直阻塞在那里。</p>
<p>&nbsp;&nbsp; 在单线程的程序里出现这种情况会导致主线程（单线程程序里只有一个默认的主线程）被阻塞,这样整个程序被锁死在这里，如果永 远没数据发送过来，那么程序就会被永远锁死。这个问题可以用多线程解决，但是在有多个套接字连接的情况下，这不是一个好的选择，扩展性很差。</p>
<p><strong>非阻塞 </strong><strong>socket：</strong></p>
<p>再看代码：<br />int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&amp;ul);<br />iResult = recv(s, buffer,1024);<br /></p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008000">//</span><span style="color: #008000">-------------------------<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Initialize&nbsp;Winsock</span><span style="color: #008000"><br /></span><span style="color: #000000">WSADATA&nbsp;wsaData;<br /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;iResult&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;WSAStartup(MAKEWORD(</span><span style="color: #800080">2</span><span style="color: #000000">,</span><span style="color: #800080">2</span><span style="color: #000000">),&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">wsaData);<br /></span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(iResult&nbsp;</span><span style="color: #000000">!=</span><span style="color: #000000">&nbsp;NO_ERROR)<br />&nbsp;&nbsp;printf(</span><span style="color: #800000">"</span><span style="color: #800000">Error&nbsp;at&nbsp;WSAStartup()\n</span><span style="color: #800000">"</span><span style="color: #000000">);<br /><br /></span><span style="color: #008000">//</span><span style="color: #008000">-------------------------<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Create&nbsp;a&nbsp;SOCKET&nbsp;object.</span><span style="color: #008000"><br /></span><span style="color: #000000">SOCKET&nbsp;m_socket;<br />m_socket&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;socket(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP);<br /></span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(m_socket&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;INVALID_SOCKET)&nbsp;{<br />&nbsp;&nbsp;printf(</span><span style="color: #800000">"</span><span style="color: #800000">Error&nbsp;at&nbsp;socket():&nbsp;%ld\n</span><span style="color: #800000">"</span><span style="color: #000000">,&nbsp;WSAGetLastError());<br />&nbsp;&nbsp;WSACleanup();<br />&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">;<br />}<br /><br /></span><span style="color: #008000">//</span><span style="color: #008000">-------------------------<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Set&nbsp;the&nbsp;socket&nbsp;I/O&nbsp;mode:&nbsp;In&nbsp;this&nbsp;case&nbsp;FIONBIO<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;enables&nbsp;or&nbsp;disables&nbsp;the&nbsp;blocking&nbsp;mode&nbsp;for&nbsp;the&nbsp;<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;socket&nbsp;based&nbsp;on&nbsp;the&nbsp;numerical&nbsp;value&nbsp;of&nbsp;iMode.<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;If&nbsp;iMode&nbsp;=&nbsp;0,&nbsp;blocking&nbsp;is&nbsp;enabled;&nbsp;<br /></span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;If&nbsp;iMode&nbsp;!=&nbsp;0,&nbsp;non-blocking&nbsp;mode&nbsp;is&nbsp;enabled.</span><span style="color: #008000"><br /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;iMode&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br />ioctlsocket(m_socket,&nbsp;FIONBIO,&nbsp;(u_long&nbsp;FAR</span><span style="color: #000000">*</span><span style="color: #000000">)&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">iMode);<br /><br /></span></div>
<p><br /></p>
<p>这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪 一下就会发现，在没有数据的情况下，recv确实是马上返回了，但是也返回了一个错误：WSAEWOULDBLOCK，意思就是请求的操作没有成功完成。 <strong style="background-color: yellow">看到这里很多人可能会说，那么就重复调用recv并检查返回值，直到成功为止，但是这样做效率很成问题，开销太大</strong>。</p>
<p>&nbsp;</p>
<p><strong>多线程来解决使用阻塞套接字存在的问题：</strong></p>
<p>&nbsp; 多线程来解决阻塞套接字的方法是为阻塞套接字的IO操作创建单独的线程，阻塞的套接字IO操作放在单独的线程中，而不会因为套接字IO操作的阻塞造成整个主线程的阻塞，但是这样也会造成一定的问题：<br /></p>
<p>1) 如果是多个套接字的场合通过多线程来解决主线程阻塞就会显得不合适了，server端创建一个监听socket来负责监听连接，而为accept函数</p>
<p>&nbsp;&nbsp; 为每个client端连接创建一个套接字，这样就会创建很多的套接字。如果是创建不同的套接字则应该创建多个线程，而每个线程的线程函数是<br /></p>
<p>&nbsp;&nbsp; 不同的，这样就造成了所谓的扩展性很差。</p>
<p>2）如果不是每个连接创建一个套接字的话，duoxanch方法比较直观，程序非常简单而且可移植性好，但是不能利用平台相关的特性。例如，如 果连接数增多的时候（成千上万的连接），那么线程数成倍增长，操作 系统忙于频繁的线程间切换，而且大部分线程在其生命周期内都是处于非活动状态的，这大大浪费了系统的资源。所以，如果你已经知道你的代码只会运行在 Windows平台上，建议采用Winsock I/O模型。</p>
<p>&nbsp;</p>
<p><strong>微软提供了select函数来解决这个问题</strong>：</p>
<p>&nbsp;int select(<br />int nfds, <br />fd_set FAR *readfds, <br />fd_set FAR *writefds, <br />fd_set FAR *exceptfds, <br />const struct timeval FAR *timeout <br />);<br /></p>
<p>第一个参数不要管，会被系统忽略的。第二个参数是用来检查套接字可读性，也就说检查套接字上是否有数据可读，同样，第三个参数用来检查数据是否可以发出。最后一个是检查是否有带外数据可读取。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>最后一个参数是用来设置select等待多久的，是个结构：<br />struct timeval {<br />long tv_sec; // seconds <br />long tv_usec; // and microseconds <br />};<br />如果将这个结构设置为(0,0)，那么select函数会马上返回。</p>
<p>&nbsp;<strong>说了这么久，select的作用到底是什么？</strong><br /></p>
<p>他的作用就是：</p>
<p>1）防止在在阻塞模式的套接字里被锁死</p>
<p>2）避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。</p>
<p>&nbsp;</p>
<p><strong>他的工作流程如下：</strong></p>
<p>1：用FD_ZERO宏来初始化我们感兴趣的fd_set，也就是select函数的第二三四个参数。<br />2：用FD_SET宏来将套接字句柄分配给相应的fd_set。<br />3：调用select函数。<br />4：用FD_ISSET对套接字句柄进行检查，<strong style="background-color: yellow">如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里，那么说明马上可以进行相应的IO操 作。</strong><em><strong>比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里，那么说明当前数据已经来了， 马上可以读取成功而不会被阻塞</strong></em>。</p>
<p>&nbsp;</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008080">&nbsp;1</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #800000">"</span><span style="color: #800000">stdafx.h</span><span style="color: #800000">"</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;2</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">iostream</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;3</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">winsock2.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;4</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">windows.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;5</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;6</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;TRACE&nbsp;ATLTrace&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">必须要加上这个宏定义，否则在WIN32的控制台程序中是不能直接用的</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;7</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;8</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;InternetAddr&nbsp;"127.0.0.1"</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;9</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;iPort&nbsp;5055</span><span style="color: #000000"><br /></span><span style="color: #008080">10</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">11</span>&nbsp;<span style="color: #0000ff">#pragma</span><span style="color: #000000">&nbsp;comment(lib,&nbsp;"ws2_32.lib")</span><span style="color: #000000"><br /></span><span style="color: #008080">12</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">13</span>&nbsp;<span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;_tmain(</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;argc,&nbsp;_TCHAR</span><span style="color: #000000">*</span><span style="color: #000000">&nbsp;argv[])<br /></span><span style="color: #008080">14</span>&nbsp;<span style="color: #000000">{<br /></span><span style="color: #008080">15</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;WSADATA&nbsp;wsa;<br /></span><span style="color: #008080">16</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;WORD&nbsp;wVersionRequested;<br /></span><span style="color: #008080">17</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;err;<br /></span><span style="color: #008080">18</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">19</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;wVersionRequested&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;MAKEWORD(&nbsp;</span><span style="color: #800080">2</span><span style="color: #000000">,&nbsp;</span><span style="color: #800080">2</span><span style="color: #000000">&nbsp;);<br /></span><span style="color: #008080">20</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;err&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;WSAStartup(&nbsp;wVersionRequested,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">wsa);<br /></span><span style="color: #008080">21</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(&nbsp;err&nbsp;</span><span style="color: #000000">!=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">&nbsp;)&nbsp;{<br /></span><span style="color: #008080">22</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Tell&nbsp;the&nbsp;user&nbsp;that&nbsp;we&nbsp;could&nbsp;not&nbsp;find&nbsp;a&nbsp;usable&nbsp;<br /></span><span style="color: #008080">23</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">WinSock&nbsp;DLL.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000"><br /></span><span style="color: #008080">24</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;TRACE(</span><span style="color: #800000">"</span><span style="color: #800000">你忘记添加WinSock&nbsp;DLL了\n</span><span style="color: #800000">"</span><span style="color: #000000">);<br /></span><span style="color: #008080">25</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;WSACleanup();<br /></span><span style="color: #008080">26</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">1</span><span style="color: #000000">;<br /></span><span style="color: #008080">27</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">28</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">29</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Create&nbsp;a&nbsp;SOCKET&nbsp;for&nbsp;listening&nbsp;for&nbsp;&nbsp;incoming&nbsp;connection&nbsp;requests</span><span style="color: #008000"><br /></span><span style="color: #008080">30</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;fdServer&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;socket(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP);<br /></span><span style="color: #008080">31</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">32</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;sockaddr_in&nbsp;server;<br /></span><span style="color: #008080">33</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">34</span>&nbsp;<span style="color: #000000">　server.sin_family&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;AF_INET;<br /></span><span style="color: #008080">35</span>&nbsp;<span style="color: #000000">　server.sin_addr.s_addr&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;inet_addr(InternetAddr);<br /></span><span style="color: #008080">36</span>&nbsp;<span style="color: #000000">　server.sin_port&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;htons(iPort);<br /></span><span style="color: #008080">37</span>&nbsp;<span style="color: #000000">&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Bind&nbsp;the&nbsp;socket.</span><span style="color: #008000"><br /></span><span style="color: #008080">38</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;ret&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;bind(fdServer,&nbsp;(sockaddr</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">server,&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(server));<br /></span><span style="color: #008080">39</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;ret&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;listen(fdServer,&nbsp;</span><span style="color: #800080">4</span><span style="color: #000000">);<br /></span><span style="color: #008080">40</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">41</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;AcceptSocket;<br /></span><span style="color: #008080">42</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fdread;<br /></span><span style="color: #008080">43</span>&nbsp;<span style="color: #000000">　timeval&nbsp;&nbsp;&nbsp;&nbsp;tv;<br /></span><span style="color: #008080">44</span>&nbsp;<span style="color: #000000">　</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nSize;<br /></span><span style="color: #008080">45</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">其实也算是轮训，那么对阻塞socket用select和对使用非阻塞socket的优点在哪？<br /></span><span style="color: #008080">46</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">可能的优点就是避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。</span><span style="color: #008000"><br /></span><span style="color: #008080">47</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">while</span><span style="color: #000000">(</span><span style="color: #800080">1</span><span style="color: #000000">)<br /></span><span style="color: #008080">48</span>&nbsp;<span style="color: #000000">&nbsp;　{<br /></span><span style="color: #008080">49</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　　　<br /></span><span style="color: #008080">50</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread);</span><span style="color: #008000">//</span><span style="color: #008000">初始化fd_set</span><span style="color: #008000"><br /></span><span style="color: #008080">51</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(fdServer,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread);</span><span style="color: #008000">//</span><span style="color: #008000">分配套接字句柄到相应的fd_set</span><span style="color: #008000"><br /></span><span style="color: #008080">52</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　　　<br /></span><span style="color: #008080">53</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tv.tv_sec&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">2</span><span style="color: #000000">;</span><span style="color: #008000">//</span><span style="color: #008000">这里我们打算让select等待两秒后返回，避免被锁死，也避免马上返回</span><span style="color: #008000"><br /></span><span style="color: #008080">54</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tv.tv_usec&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">55</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　　　<br /></span><span style="color: #008080">56</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select(</span><span style="color: #800080">0</span><span style="color: #000000">,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread,&nbsp;NULL,&nbsp;NULL,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">tv);<br /></span><span style="color: #008080">57</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　　　<br /></span><span style="color: #008080">58</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nSize&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(server);<br /></span><span style="color: #008080">59</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">先判断fdServer是否还在fd_set内来判断是否可以读，这样就避免因为&nbsp;accept在等待<br /></span><span style="color: #008080">60</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">时造成的阻塞</span><span style="color: #008000"><br /></span><span style="color: #008080">61</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(FD_ISSET(fdServer,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread))<br /></span><span style="color: #008080">62</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">如果套接字句柄还在fd_set里，说明客户端已经有connect的请求发过来了，<br /></span><span style="color: #008080">63</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">马上可以accept成功</span><span style="color: #008000"><br /></span><span style="color: #008080">64</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　{<br /></span><span style="color: #008080">65</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AcceptSocket&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;accept(fdServer,(&nbsp;sockaddr</span><span style="color: #000000">*</span><span style="color: #000000">)&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">server,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">nSize);<br /></span><span style="color: #008080">66</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">67</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　　　<br /></span><span style="color: #008080">68</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">else</span><span style="color: #000000"><br /></span><span style="color: #008080">69</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">还没有客户端的connect请求，我们可以去做别的事，避免像没有用select方式<br /></span><span style="color: #008080">70</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">的阻塞套接字程序被锁死的情况，如果没用select,当程序运行到accept的时候客户<br /></span><span style="color: #008080">71</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">端恰好没有connect请求，那么程序就会被锁死，做不了任何事情</span><span style="color: #008000"><br /></span><span style="color: #008080">72</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">73</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">do&nbsp;something</span><span style="color: #008000"><br /></span><span style="color: #008080">74</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBox(NULL,&nbsp;</span><span style="color: #800000">"</span><span style="color: #800000">waiting<img alt="" src="http://www.cnblogs.com/Images/dot.gif" /></span><span style="color: #800000">"</span><span style="color: #000000">,&nbsp;</span><span style="color: #800000">"</span><span style="color: #800000">recv</span><span style="color: #800000">"</span><span style="color: #000000">,&nbsp;MB_ICONINFORMATION);<br /></span><span style="color: #008080">75</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">别的事做完后，继续去检查是否有客户端连接请求</span><span style="color: #008000"><br /></span><span style="color: #008080">76</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">77</span>&nbsp;<span style="color: #000000">&nbsp;　　}<br /></span><span style="color: #008080">78</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">79</span>&nbsp;<span style="color: #000000">&nbsp;　　</span><span style="color: #0000ff">char</span><span style="color: #000000">&nbsp;buffer[</span><span style="color: #800080">128</span><span style="color: #000000">];<br /></span><span style="color: #008080">80</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　ZeroMemory(buffer,&nbsp;</span><span style="color: #800080">128</span><span style="color: #000000">);<br /></span><span style="color: #008080">81</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">82</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;recv(AcceptSocket,buffer,</span><span style="color: #800080">128</span><span style="color: #000000">,</span><span style="color: #800080">0</span><span style="color: #000000">);</span><span style="color: #008000">//</span><span style="color: #008000">这里同样可以用select，用法和上面一样</span><span style="color: #008000"><br /></span><span style="color: #008080">83</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">84</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBox(NULL,&nbsp;buffer,&nbsp;</span><span style="color: #800000">"</span><span style="color: #800000">recv</span><span style="color: #800000">"</span><span style="color: #000000">,&nbsp;MB_ICONINFORMATION);<br /></span><span style="color: #008080">85</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">86</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(AcceptSocket);<br /></span><span style="color: #008080">87</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WSACleanup();<br /></span><span style="color: #008080">88</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">89</span>&nbsp;<span style="color: #000000">}<br /></span><span style="color: #008080">90</span>&nbsp;</div>
<p>&nbsp;</p>
<p>select函数的返回值 ：<br /></p>
<p>函数失败的返回值：调用失败返回SOCKET_ERROR,超时返回0。</p>
<p>&nbsp;</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;ret;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">((ret</span><span style="color: #000000">=</span><span style="color: #000000">select(</span><span style="color: #800080">0</span><span style="color: #000000">,</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread,NULL,NULL,NULL))</span><span style="color: #000000">==</span><span style="color: #000000">SOCKET_ERROR)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Error&nbsp;Condition</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(ret&nbsp;</span><span style="color: #000000">&gt;</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">)</span><span style="color: #008000">//</span><span style="color: #008000">ret&gt;0这个ret值表示满足条件的socket的数量,不止一个socket满足IO操作的条件</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(FD_ISSET(fdServer,</span><span style="color: #000000">&amp;</span><span style="color: #000000">fdread))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">A&nbsp;read&nbsp;event&nbsp;has&nbsp;occured&nbsp;on&nbsp;socket&nbsp;fdServer</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><img src ="http://www.cppblog.com/yehao/aggbug/153732.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-08-18 16:11 <a href="http://www.cppblog.com/yehao/articles/153732.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字IO模型（二） WSAAsynSelect模型</title><link>http://www.cppblog.com/yehao/articles/153731.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 18 Aug 2011 08:09:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/153731.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/153731.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/153731.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/153731.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/153731.html</trackback:ping><description><![CDATA[<a href="http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268185.html">http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268185.html</a><br />WSAAsynSelect模型也是一个常用的异步I/O模型。应用程序可以在一个套接字上接收以WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函数自动将套接字设置（转变）为非阻塞模式，并向WINDOWS注册一个或多个网络事件，并提供一个通知时使用的窗口句柄。当注册的事件发生时，对应的窗口将收到一个基于消息的通知。 
<p>&nbsp;</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008080">&nbsp;&nbsp;1</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">winsock.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;2</span>&nbsp;<span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">tchar.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;3</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;4</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;PORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;5150</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;5</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;MSGSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1024</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;6</span>&nbsp;<span style="color: #0000ff">#define</span><span style="color: #000000">&nbsp;WM_SOCKET&nbsp;WM_USER+0</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;7</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;8</span>&nbsp;<span style="color: #0000ff">#pragma</span><span style="color: #000000">&nbsp;comment(lib,&nbsp;"ws2_32.lib")</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;&nbsp;9</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;10</span>&nbsp;<span style="color: #000000">LRESULT&nbsp;CALLBACK&nbsp;WndProc(HWND,&nbsp;UINT,&nbsp;WPARAM,&nbsp;LPARAM);<br /></span><span style="color: #008080">&nbsp;11</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;12</span>&nbsp;<span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;WINAPI&nbsp;WinMain(HINSTANCE&nbsp;hInstance,&nbsp;HINSTANCE&nbsp;hPrevInstance,&nbsp;PSTR&nbsp;szCmdLine,&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;iCmdShow)<br /></span><span style="color: #008080">&nbsp;13</span>&nbsp;<span style="color: #000000">{<br /></span><span style="color: #008080">&nbsp;14</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">static</span><span style="color: #000000">&nbsp;TCHAR&nbsp;szAppName[]&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;_T(</span><span style="color: #800000">"</span><span style="color: #800000">AsyncSelect&nbsp;Model</span><span style="color: #800000">"</span><span style="color: #000000">);<br /></span><span style="color: #008080">&nbsp;15</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HWND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hwnd&nbsp;;<br /></span><span style="color: #008080">&nbsp;16</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MSG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg&nbsp;;<br /></span><span style="color: #008080">&nbsp;17</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WNDCLASS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass&nbsp;;<br /></span><span style="color: #008080">&nbsp;18</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;19</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.style&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;CS_HREDRAW&nbsp;</span><span style="color: #000000">|</span><span style="color: #000000">&nbsp;CS_VREDRAW&nbsp;;<br /></span><span style="color: #008080">&nbsp;20</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.lpfnWndProc&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;WndProc&nbsp;;<br /></span><span style="color: #008080">&nbsp;21</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.cbClsExtra&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">&nbsp;;<br /></span><span style="color: #008080">&nbsp;22</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.cbWndExtra&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">&nbsp;;<br /></span><span style="color: #008080">&nbsp;23</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.hInstance&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;hInstance&nbsp;;<br /></span><span style="color: #008080">&nbsp;24</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.hIcon&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;LoadIcon&nbsp;(NULL,&nbsp;IDI_APPLICATION)&nbsp;;<br /></span><span style="color: #008080">&nbsp;25</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.hCursor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;LoadCursor&nbsp;(NULL,&nbsp;IDC_ARROW)&nbsp;;<br /></span><span style="color: #008080">&nbsp;26</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.hbrBackground&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;(HBRUSH)&nbsp;GetStockObject&nbsp;(WHITE_BRUSH)&nbsp;;<br /></span><span style="color: #008080">&nbsp;27</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.lpszMenuName&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;NULL&nbsp;;<br /></span><span style="color: #008080">&nbsp;28</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;wndclass.lpszClassName&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;szAppName&nbsp;;<br /></span><span style="color: #008080">&nbsp;29</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;30</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(</span><span style="color: #000000">!</span><span style="color: #000000">RegisterClass(</span><span style="color: #000000">&amp;</span><span style="color: #000000">wndclass))<br /></span><span style="color: #008080">&nbsp;31</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;32</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MessageBox&nbsp;(NULL,&nbsp;TEXT&nbsp;(</span><span style="color: #800000">"</span><span style="color: #800000">This&nbsp;program&nbsp;requires&nbsp;Windows&nbsp;NT!</span><span style="color: #800000">"</span><span style="color: #000000">),&nbsp;szAppName,&nbsp;MB_ICONERROR)&nbsp;;<br /></span><span style="color: #008080">&nbsp;33</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">&nbsp;;<br /></span><span style="color: #008080">&nbsp;34</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">&nbsp;35</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;36</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>hwnd&nbsp;</strong></span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;CreateWindow&nbsp;(szAppName,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;window&nbsp;class&nbsp;name</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;37</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TEXT&nbsp;(</span><span style="color: #800000">"</span><span style="color: #800000">AsyncSelect&nbsp;Model</span><span style="color: #800000">"</span><span style="color: #000000">),&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;window&nbsp;caption</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;38</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WS_OVERLAPPEDWINDOW,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;window&nbsp;style</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;39</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;initial&nbsp;x&nbsp;position</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;40</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;initial&nbsp;y&nbsp;position</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;41</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;initial&nbsp;x&nbsp;size</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;42</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;initial&nbsp;y&nbsp;size</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;43</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;parent&nbsp;window&nbsp;handle</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;44</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;window&nbsp;menu&nbsp;handle</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;45</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hInstance,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;program&nbsp;instance&nbsp;handle</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;46</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NULL)&nbsp;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;creation&nbsp;parameters</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;47</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;48</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ShowWindow(hwnd,&nbsp;iCmdShow);<br /></span><span style="color: #008080">&nbsp;49</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UpdateWindow(hwnd);<br /></span><span style="color: #008080">&nbsp;50</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;51</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">while</span><span style="color: #000000">&nbsp;(GetMessage(</span><span style="color: #000000">&amp;</span><span style="color: #000000">msg,&nbsp;NULL,&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">,&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">))<br /></span><span style="color: #008080">&nbsp;52</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;53</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TranslateMessage(</span><span style="color: #000000">&amp;</span><span style="color: #000000">msg)&nbsp;;<br /></span><span style="color: #008080">&nbsp;54</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DispatchMessage(</span><span style="color: #000000">&amp;</span><span style="color: #000000">msg)&nbsp;;<br /></span><span style="color: #008080">&nbsp;55</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">&nbsp;56</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;<br /></span><span style="color: #008080">&nbsp;57</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;msg.wParam;<br /></span><span style="color: #008080">&nbsp;58</span>&nbsp;<span style="color: #000000">}<br /></span><span style="color: #008080">&nbsp;59</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;60</span>&nbsp;<span style="color: #000000">LRESULT&nbsp;CALLBACK&nbsp;WndProc&nbsp;(HWND&nbsp;hwnd,&nbsp;UINT&nbsp;message,&nbsp;WPARAM&nbsp;wParam,&nbsp;LPARAM&nbsp;lParam)<br /></span><span style="color: #008080">&nbsp;61</span>&nbsp;<span style="color: #000000">{<br /></span><span style="color: #008080">&nbsp;62</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WSADATA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>&nbsp;wsd</strong>;<br /></span><span style="color: #008080">&nbsp;63</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">static</span><span style="color: #000000">&nbsp;SOCKET&nbsp;<strong>sListen</strong>;<br /></span><span style="color: #008080">&nbsp;64</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>sClient</strong>;<br /></span><span style="color: #008080">&nbsp;65</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SOCKADDR_IN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>local,&nbsp;client</strong>;<br /></span><span style="color: #008080">&nbsp;66</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret,&nbsp;iAddrSize&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(client);<br /></span><span style="color: #008080">&nbsp;67</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">char</span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>szMessage</strong>[MSGSIZE];<br /></span><span style="color: #008080">&nbsp;68</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;69</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">switch</span><span style="color: #000000">&nbsp;(message)<br /></span><span style="color: #008080">&nbsp;70</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">&nbsp;71</span>&nbsp;<span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>WM_CREATE</strong>:<br /></span><span style="color: #008080">&nbsp;72</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Initialize&nbsp;Windows&nbsp;Socket&nbsp;library</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;73</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>WSAStartup</strong>(</span><span style="color: #800080">0x0202</span><span style="color: #000000">,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">wsd);<br /></span><span style="color: #008080">&nbsp;74</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;<br /></span><span style="color: #008080">&nbsp;75</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Create&nbsp;listening&nbsp;socket</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;76</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sListen&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;<strong>socket</strong>(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP);<br /></span><span style="color: #008080">&nbsp;77</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">&nbsp;78</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Bind</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;79</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local.sin_addr.S_un.S_addr&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;htonl(INADDR_ANY);<br /></span><span style="color: #008080">&nbsp;80</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local.sin_family&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;AF_INET;<br /></span><span style="color: #008080">&nbsp;81</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;local.sin_port&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;htons(PORT);<br /></span><span style="color: #008080">&nbsp;82</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>bind</strong>(sListen,&nbsp;(</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;sockaddr&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">local,&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(local));<br /></span><span style="color: #008080">&nbsp;83</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;<br /></span><span style="color: #008080">&nbsp;84</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Listen</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;85</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>listen</strong>(sListen,&nbsp;</span><span style="color: #800080">3</span><span style="color: #000000">);<br /></span><span style="color: #008080">&nbsp;86</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;87</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Associate&nbsp;listening&nbsp;socket&nbsp;with&nbsp;FD_ACCEPT&nbsp;event</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;88</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>WSAAsyncSelect</strong>(<strong>sListen</strong>,&nbsp;hwnd,&nbsp;<strong>WM_SOCKET</strong>,&nbsp;<strong>FD_ACCEPT</strong>);<br /></span><span style="color: #008080">&nbsp;89</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">&nbsp;90</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;91</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>WM_DESTROY</strong>:<br /></span><span style="color: #008080">&nbsp;92</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(sListen);<br /></span><span style="color: #008080">&nbsp;93</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WSACleanup();<br /></span><span style="color: #008080">&nbsp;94</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PostQuitMessage(</span><span style="color: #800080">0</span><span style="color: #000000">);<br /></span><span style="color: #008080">&nbsp;95</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">&nbsp;96</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;<br /></span><span style="color: #008080">&nbsp;97</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>WM_SOCKET</strong>:<br /></span><span style="color: #008080">&nbsp;98</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(WSAGETSELECTERROR(lParam))</span><span style="color: #008000">//lParam的高字节包含了可能出现的任何的错误代码</span><span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;99</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">100</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wParam);<br /></span><span style="color: #008080">101</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">102</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">103</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">104</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">switch</span><span style="color: #000000">&nbsp;(WSAGETSELECTEVENT(lParam))</span><span style="color: #008000"> //lParam的低字节指定已经发生的网络事件</span><span style="color: #000000"><br /></span><span style="color: #008080">105</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">106</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>FD_ACCEPT</strong>:<br /></span><span style="color: #008080">107</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Accept&nbsp;a&nbsp;connection&nbsp;from&nbsp;client</span><span style="color: #008000"><br /></span><span style="color: #008080">108</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sClient&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;accept(wParam,&nbsp;(</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;sockaddr&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">client,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">iAddrSize);<br /></span><span style="color: #008080">109</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">110</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">&nbsp;Associate&nbsp;client&nbsp;socket&nbsp;with&nbsp;FD_READ&nbsp;and&nbsp;FD_CLOSE&nbsp;event</span><span style="color: #008000"><br /></span><span style="color: #008080">111</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WSAAsyncSelect(sClient,&nbsp;hwnd,&nbsp;WM_SOCKET,&nbsp;FD_READ&nbsp;</span><span style="color: #000000">|</span><span style="color: #000000">&nbsp;FD_CLOSE);<br /></span><span style="color: #008080">112</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">113</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">114</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>FD_READ</strong>:<br /></span><span style="color: #008080">115</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;<strong>recv</strong>(wParam,&nbsp;szMessage,&nbsp;MSGSIZE,&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">);<br /></span><span style="color: #008080">116</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">117</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(ret&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">||</span><span style="color: #000000">&nbsp;ret&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;SOCKET_ERROR&nbsp;</span><span style="color: #000000">&amp;&amp;</span><span style="color: #000000">&nbsp;WSAGetLastError()&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;WSAECONNRESET)<br /></span><span style="color: #008080">118</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">119</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wParam);<br /></span><span style="color: #008080">120</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">121</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">else</span><span style="color: #000000"><br /></span><span style="color: #008080">122</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">123</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;szMessage[ret]&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800000">'</span><span style="color: #800000">\0</span><span style="color: #800000">'</span><span style="color: #000000">;<br /></span><span style="color: #008080">124</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>send</strong>(wParam,&nbsp;szMessage,&nbsp;strlen(szMessage),&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">);<br /></span><span style="color: #008080">125</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">126</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">127</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">128</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">case</span><span style="color: #000000">&nbsp;<strong>FD_CLOSE</strong>:<br /></span><span style="color: #008080">129</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket(wParam);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span style="color: #008080">130</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">131</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">132</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">133</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">134</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;<br /></span><span style="color: #008080">135</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;DefWindowProc(hwnd,&nbsp;message,&nbsp;wParam,&nbsp;lParam);<br /></span><span style="color: #008080">136</span>&nbsp;<span style="color: #000000">}</span></div>
<p>WSAAsyncSelect是最简单的一种Winsock I/O模型（之所以说它简单是因为一个主线程就搞定了）。使用Raw Windows API写过窗口类应用程序的人应该都能看得懂。这里，我们需要做的仅仅是：<br />1.在WM_CREATE消息处理函数中，初始化Windows Socket library，创建监听套接字，绑定，监听，<strong><span style="background-color: yellow">并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件</span>；</strong><br />2.自定义一个消息<strong>WM_SOCKET</strong>，一旦在我们所关心的套接字（<strong>监听套接字</strong>和<strong>客户端套接字</strong>）上发生了某个事件，<strong>系统</strong>发送消息(<strong>WM_SOCKET)</strong>给hWnd指向的窗体，而<strong>WndProc函数处理所有发往窗体的消息</strong>并且message参数被设置为<strong>WM_SOCKET</strong>；<br />3.在WM_SOCKET的消息处理中，分别对FD_ACCEPT、FD_READ和FD_CLOSE事件进行处理；<br /></p>
<p>4.在窗口销毁消息(WM_DESTROY)的处理函数中，我们关闭监听套接字，清除Windows Socket library</p>
<p>&nbsp;</p>
<p>下面这张用于WSAAsyncSelect函数的网络事件类型表可以让你对各个网络事件有更清楚的认识：<br />表1</p>
<p>FD_READ 应用程序想要接收有关是否可读的通知，以便读入数据 <br />FD_WRITE 应用程序想要接收有关是否可写的通知，以便写入数据 <br />FD_OOB 应用程序想接收是否有带外（OOB）数据抵达的通知 <br />FD_ACCEPT 应用程序想接收与进入连接有关的通知 <br />FD_CONNECT 应用程序想接收与一次连接或者多点join操作完成的通知 <br />FD_CLOSE 应用程序想接收与套接字关闭有关的通知 <br />FD_QOS 应用程序想接收套接字&#8220;服务质量&#8221;（QoS）发生更改的通知 <br />FD_GROUP_QOS&nbsp;&nbsp;&nbsp;&nbsp; 应用程序想接收套接字组&#8220;服务质量&#8221;发生更改的通知（现在没什么用处，为未来套接字组的使用保留） <br />FD_ROUTING_INTERFACE_CHANGE 应用程序想接收在指定的方向上，与路由接口发生变化的通知 <br />FD_ADDRESS_LIST_CHANGE&nbsp;&nbsp;&nbsp;&nbsp; 应用程序想接收针对套接字的协议家族，本地地址列表发生变化的通知</p><img src ="http://www.cppblog.com/yehao/aggbug/153731.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-08-18 16:09 <a href="http://www.cppblog.com/yehao/articles/153731.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字IO模型（三） WSAEventSelect模型</title><link>http://www.cppblog.com/yehao/articles/153730.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 18 Aug 2011 08:08:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/153730.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/153730.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/153730.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/153730.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/153730.html</trackback:ping><description><![CDATA[<p><a href="http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268214.html">http://www.cnblogs.com/NeuqUstcIim/archive/2008/08/14/1268214.html</a><br />WSAEventSelect模型类似WSAAsynSelect模型，但最主要的区别是网络事件发生时会被发送到一个事件对象句柄，而不是发送到一个窗口。这样可能更加的好，对于服务器端的程序来说。</p>
<p>使用步骤如下：</p>
<p>a、 创建事件对象来接收网络事件：</p>
<p>WSAEVENT WSACreateEvent( void ); </p>
<p>该函数的返回值为一个事件对象句柄，它具有两种工作状态：已传信(signaled)和未传信(nonsignaled)以及两种工作模式：人工重设(manual reset)和自动重设(auto reset)。默认未未传信的工作状态和人工重设模式。</p>
<p>&nbsp;</p>
<p>b、将事件对象与套接字关联，同时注册事件，使事件对象的工作状态从未传信转变未已传信。 <br /></p>
<p>int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents ); <br />s为套接字<br />hEventObject为刚才创建的事件对象句柄<br /></p>
<p>lNetworkEvents为掩码，定义如上面所述</p>
<p>&nbsp;</p>
<p>c、I/O处理后，设置事件对象为未传信 <br />BOOL WSAResetEvent( WSAEVENT hEvent );<br />Hevent为事件对象</p>
<p>成功返回TRUE，失败返回FALSE。</p>
<p>&nbsp;</p>
<p>d、等待网络事件来触发事件句柄的工作状态：</p>
<p>DWORD WSAWaitForMultipleEvents( DWORD cEvents,const WSAEVENT FAR * lphEvents, BOOL fWaitAll,DWORD dwTimeout, BOOL fAlertable );<br />lpEvent为事件句柄数组的指针<br />cEvent为为事件句柄的数目，其最大值为WSA_MAXIMUM_WAIT_EVENTS <br />fWaitAll指定等待类型：TRUE：当lphEvent数组重所有事件对象同时有信号时返回；<br />FALSE：任一事件有信号就返回。<br />dwTimeout为等待超时（毫秒）<br /></p>
<p>fAlertable为指定函数返回时是否执行完成例程</p>
<p>&nbsp;</p>
<p>nIndex=WSAWaitForMultipleEvents(&#8230;);<br /></p>
<p>MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>事 件选择模型也比较简单，实现起来也不是太复杂，它的基本思想是将每个套接字都和一个<strong>WSAEVENT</strong>对象对应起来，并且在关联的时候指定需要关注的哪些网 络事件。一旦在某个套接字上发生了我们关注的事件（FD_READ和FD_CLOSE），与之相关联的WSAEVENT对象被Signaled。程序定义 了两个全局数组，一个套接字数组，一个WSAEVENT对象数组，其大小都是MAXIMUM_WAIT_OBJECTS（64），两个数组中的元素一一对 应。<br />同样的，这里的程序没有考虑两个问题，<strong>一是不能无条件的调用accept，因为我们支持的并发连接数有限</strong>。解决方法是将套接字按 MAXIMUM_WAIT_OBJECTS分组，<span style="background-color: yellow">每MAXIMUM_WAIT_OBJECTS个套接字一组，每一组分配一个工作者线程</span>；或者采用 <span style="background-color: yellow">WSAAccept代替accept，并回调自己定义的Condition Function</span>。第二个问题是没有对连接数为0的情形做特殊处理，程序在连接数为0的时候CPU占用率为100%。 <br /></p>
<p>&nbsp;</p>
<div class="cnblogs_code"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #008080">&nbsp;1</span>&nbsp;<span style="color: #000000">SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Socket[WSA_MAXIMUM_WAIT_EVENTS];<br /></span><span style="color: #008080">&nbsp;2</span>&nbsp;<span style="color: #000000">WSAEVENT&nbsp;&nbsp;&nbsp;Event[WSA_MAXINUM_WAIT_EVENTS];<br /></span><span style="color: #008080">&nbsp;3</span>&nbsp;<span style="color: #000000">SOCKET&nbsp;&nbsp;&nbsp;&nbsp;Accept,</span> <span style="color: #000000">Listen;<br /></span><span style="color: #008080">&nbsp;5</span>&nbsp;<span style="color: #000000">DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EventTotal&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;<br /></span><span style="color: #008080">&nbsp;6</span>&nbsp;<span style="color: #000000">DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Index;<br /></span><span style="color: #008080">&nbsp;7</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">&nbsp;8</span>&nbsp;<span style="color: #008000">//</span><span style="color: #008000">Set&nbsp;up&nbsp;a&nbsp;TCP&nbsp;socket&nbsp;for&nbsp;listening&nbsp;on&nbsp;port&nbsp;5150</span><span style="color: #008000"><br /></span><span style="color: #008080">&nbsp;9</span>&nbsp;<span style="color: #000000"><strong>Listen</strong>&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;socket(PF_INET,SOCK_STREAM,</span><span style="color: #800080">0</span><span style="color: #000000">);<br /></span><span style="color: #008080">10</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">11</span>&nbsp;<span style="color: #000000">InternetAddr.sin_family&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;AF_INET;<br /></span><span style="color: #008080">12</span>&nbsp;<span style="color: #000000">InternetAddr.sin_addr.s_addr&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;htonl(INADDR_ANY);<br /></span><span style="color: #008080">13</span>&nbsp;<span style="color: #000000">InternetAddr.sin_port&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;htons(</span><span style="color: #800080">5150</span><span style="color: #000000">);<br /></span><span style="color: #008080">14</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">15</span>&nbsp;<span style="color: #000000">bind(Listen,(PSOCKADDR)&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">InternetAddr,</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(InternetAddr));<br /></span><span style="color: #008080">16</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">17</span>&nbsp;<span style="color: #000000">NewEvent&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;<span style="background-color: yellow">WSACreateEvent</span>();<br /></span><span style="color: #008080">18</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">19</span>&nbsp;<span style="color: #000000"><span style="background-color: yellow">WSAEventSelect</span>(<strong>Listen</strong>,<strong>NewEvnet</strong>,<strong>FD_ACCEPT</strong></span><span style="color: #000000">|</span><span style="color: #000000">FD_CLOSE);<br /></span><span style="color: #008080">20</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">21</span>&nbsp;<span style="color: #000000">listen(Listen,</span><span style="color: #800080">5</span><span style="color: #000000">);<br /></span><span style="color: #008080">22</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">23</span>&nbsp;<span style="color: #000000">Socket[EventTotal]&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;Listen;<br /></span><span style="color: #008080">24</span>&nbsp;<span style="color: #000000">Event[EventTotal]&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;NewEvent;<br /></span><span style="color: #008080">25</span>&nbsp;<span style="color: #000000">EventTotal</span><span style="color: #000000">++</span><span style="color: #000000">;<br /></span><span style="color: #008080">26</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">27</span>&nbsp;<span style="color: #0000ff">while</span><span style="color: #000000">&nbsp;(TRUE)<br /></span><span style="color: #008080">28</span>&nbsp;<span style="color: #000000">{<br /></span><span style="color: #008080">29</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Wait&nbsp;for&nbsp;network&nbsp;events&nbsp;on&nbsp;all&nbsp;sockets</span><span style="color: #008000"><br /></span><span style="color: #008080">30</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;Index&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;<span style="background-color: yellow">WSAWaitForMultipleEvents</span>(EventTotal,EventArray,FALSE,<strong>WSA_INFINITE</strong>,FALSE);<br /></span><span style="color: #008080">31</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">32</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<span style="background-color: yellow">WSAEnumNewWorkEvents</span>(SocketArray[Index</span><span style="color: #000000">-</span><span style="color: #000000">WSA_WAIT_EVENT_0],<br /></span><span style="color: #008080">33</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EventArray[Index</span><span style="color: #000000">-</span><span style="color: #000000">WSA_WAIT_EVENT_0],<br /></span><span style="color: #008080">34</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">NetworkEvents);<br /></span><span style="color: #008080">35</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Check&nbsp;for&nbsp;FD_ACCEPT&nbsp;messages</span><span style="color: #008000"><br /></span><span style="color: #008080">36</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(</span><span style="background-color: #3366ff"><span style="color: #000000"><strong>NetworkEvents.lNetworkEvents&nbsp;</strong><strong><span style="color: #000000">&amp;</span></strong><span style="color: #000000"><strong>&nbsp;FD_ACCEPT</strong></span></span></span><span style="color: #000000"><strong></strong>)<br /></span><span style="color: #008080">37</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">38</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]&nbsp;</span><span style="color: #000000">!=</span><span style="color: #800080">0</span><span style="color: #000000">)<br /></span><span style="color: #008080">39</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">40</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Error</span><span style="color: #008000"><br /></span><span style="color: #008080">41</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">42</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">43</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Accept&nbsp;a&nbsp;new&nbsp;connection&nbsp;and&nbsp;add&nbsp;it&nbsp;to&nbsp;the&nbsp;socket&nbsp;and&nbsp;event&nbsp;lists</span><span style="color: #008000"><br /></span><span style="color: #008080">44</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #00ff00"><span style="color: #000000"><strong>Accept</strong>&nbsp;<span style="color: #000000">=</span><span style="color: #000000">&nbsp;accept(SocketArray[<strong>Index</strong></span><strong><span style="color: #000000">-</span></strong><span style="color: #000000"><strong>WSA_WAIT_EVENT_0</strong>],NULL,NULL)</span></span></span><span style="color: #000000">;<br /></span><span style="color: #008080">45</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">46</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">We&nbsp;cannot&nbsp;process&nbsp;more&nbsp;than&nbsp;WSA_MAXIMUM_WAIT_EVENTS&nbsp;sockets&nbsp;,<br /></span><span style="color: #008080">47</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">so&nbsp;close&nbsp;the&nbsp;accepted&nbsp;socket</span><span style="color: #008000"><br /></span><span style="color: #008080">48</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #ff00ff"><span style="color: #000000"><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(EventTotal&nbsp;</span><span style="color: #000000">&gt;</span><span style="color: #000000">&nbsp;WSA_MAXIMUM_WAIT_EVENTS)</span></span></span><span style="color: #000000"><br /></span><span style="color: #008080">49</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">50</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #800000">"</span><span style="color: #800000"><img alt="" src="http://www.cnblogs.com/Images/dot.gif" /><img alt="" src="http://www.cnblogs.com/Images/dot.gif" />..</span><span style="color: #800000">"</span><span style="color: #000000">);<br /></span><span style="color: #008080">51</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket&nbsp;(Accept);<br /></span><span style="color: #008080">52</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">53</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">54</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NewEvent&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;WSACreateEvent();<br /></span><span style="color: #008080">55</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">56</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #ffff00"><span style="color: #000000">WSAEventSelect(<strong>Accept</strong>,NewEvent,<strong>FD_READ<span style="color: #000000">|</span><span style="color: #000000">FD_WRITE</span><span style="color: #000000">|</span></strong><span style="color: #000000"><strong>FD_CLOSE</strong>);</span></span></span><span style="color: #000000"><br /></span><span style="color: #008080">57</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">58</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Event[EventTotal]&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;NewEvent;<br /></span><span style="color: #008080">59</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #ff99cc"><span style="color: #000000">&nbsp;</span></span><span style="background-color: #ff99cc">Socket[EventTotal]=&nbsp;<strong>Accept</strong>;</span><span style="color: #000000"><br /></span><span style="color: #008080">60</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EventTotal</span><span style="color: #000000">++</span><span style="color: #000000">;<br /></span><span style="color: #008080">61</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prinrt(</span><span style="color: #800000">"</span><span style="color: #800000">Socket&nbsp;%d&nbsp;connect\n</span><span style="color: #800000">"</span><span style="color: #000000">,Accept);<br /></span><span style="color: #008080">62</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">63</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Process&nbsp;FD_READ&nbsp;notification</span><span style="color: #008000"><br /></span><span style="color: #008080">64</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;</span><span style="background-color: #0000ff"></span><span style="background-color: #3366ff"><span style="color: #000000">(<strong>NetworkEvents.lNetwo<span style="color: #000000"><span style="color: #000000">AD</span></span><span style="color: #000000">)</span><span style="color: #000000">rkEvents&nbsp;<span style="color: #000000">&amp;<span style="color: #000000">&nbsp;FD_R</span></span></span></strong></span></span><strong><span style="background-color: #0000ff"><span style="color: #000000"><span style="color: #000000">E</span></span></span></strong><span style="color: #000000"><br /></span><span style="color: #008080">65</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">66</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(NetworkEvents.iErrorCode[FD_READ_BIT&nbsp;</span><span style="color: #000000">!=</span><span style="color: #800080">0</span><span style="color: #000000">])<br /></span><span style="color: #008080">67</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">68</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Error</span><span style="color: #008000"><br /></span><span style="color: #008080">69</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">70</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">71</span>&nbsp;<span style="color: #000000"><br /></span><span style="color: #008080">72</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Read&nbsp;data&nbsp;from&nbsp;the&nbsp;socket</span><span style="color: #008000"><br /></span><span style="color: #008080">73</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #00ff00"><span style="color: #000000">recv(Socket[<strong>Index<span style="color: #000000">-</span></strong><span style="color: #000000"><strong>WSA_WAIT_EVENT_0</strong>],buffer,</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(buffer),</span><span style="color: #800080">0</span><span style="color: #000000">);</span></span></span><span style="color: #000000"><br /></span><span style="color: #008080">74</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">75</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">process&nbsp;FD_WRITE&nbsp;notitication</span><span style="color: #008000"><br /></span><span style="color: #008080">76</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(</span><strong><span style="background-color: #3366ff"><span style="color: #000000">NetworkEvents.lNetworkEvents&nbsp;<span style="color: #000000">&amp;<span style="color: #000000">&nbsp;FD_WRITE</span></span></span></span></strong><span style="color: #000000">)<br /></span><span style="color: #008080">77</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">78</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(NetworkEvents.iErrorCode[FD_WRITE_BIT]&nbsp;</span><span style="color: #000000">!=</span><span style="color: #800080">0</span><span style="color: #000000">)<br /></span><span style="color: #008080">79</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">80</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Error</span><span style="color: #008000"><br /></span><span style="color: #008080">81</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">82</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">83</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="background-color: #00ff00"><span style="color: #000000">send(Socket[<strong>Index<span style="color: #000000">-</span></strong><span style="color: #000000"><strong>WSA_WAIT_EVENT_0]</strong>,buffer,</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(buffer),</span><span style="color: #800080">0</span><span style="color: #000000">);</span></span></span><span style="color: #000000"><br /></span><span style="color: #008080">84</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">85</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(</span><span style="background-color: #3366ff"><span style="color: #000000"><strong>NetworkEvents.lNetworkEvents&nbsp;</strong><strong><span style="color: #000000">&amp;</span></strong><span style="color: #000000"><strong>&nbsp;FD_CLOSE)</strong></span></span></span><span style="color: #000000"><br /></span><span style="color: #008080">86</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">87</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(NetworkEvents.iErrorCode[FD_CLOSE_BIT]&nbsp;</span><span style="color: #000000">!=</span><span style="color: #800080">0</span><span style="color: #000000">)<br /></span><span style="color: #008080">88</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br /></span><span style="color: #008080">89</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Error</span><span style="color: #008000"><br /></span><span style="color: #008080">90</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">break</span><span style="color: #000000">;<br /></span><span style="color: #008080">91</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">92</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;closesocket&nbsp;(Socket[Index</span><span style="color: #000000">-</span><span style="color: #000000">WSA_WAIT_EVENT_0]);<br /></span><span style="color: #008080">93</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">Remove&nbsp;socket&nbsp;and&nbsp;associated&nbsp;event&nbsp;from&nbsp;the&nbsp;Socket&nbsp;and&nbsp;Event&nbsp;arrays&nbsp;and<br /></span><span style="color: #008080">94</span>&nbsp;<span style="color: #008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">decrement&nbsp;eventTotal</span><span style="color: #008000"><br /></span><span style="color: #008080">95</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CompressArrays(Event,Socket,</span><span style="color: #000000">&amp;</span><span style="color: #000000">&nbsp;EventTotal);<br /></span><span style="color: #008080">96</span>&nbsp;<span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008080">97</span>&nbsp;<span style="color: #000000">} <br /></span></div><img src ="http://www.cppblog.com/yehao/aggbug/153730.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-08-18 16:08 <a href="http://www.cppblog.com/yehao/articles/153730.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Winsock异步模式I/O模型WSAEventSelect的使用及FD_WRITE事件的触发机制</title><link>http://www.cppblog.com/yehao/articles/153729.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Thu, 18 Aug 2011 08:04:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/153729.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/153729.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/153729.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/153729.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/153729.html</trackback:ping><description><![CDATA[<p><strong><a href="http://oliver258.blog.51cto.com/750330/423813">http://oliver258.blog.51cto.com/750330/423813</a><br />1.Winsock同步阻塞方式的问题</strong></p>
<p>在异步非阻塞模式下，像accept（WSAAccept），recv（recv，WSARecv，WSARecvFrom）等这样的winsock函数调用后马上返回，而不是等待可用的连接和数据。在阻塞模式下，server往往这样等待client的连接：</p>
<p><font face="Courier New"><font color="#000080">while</font>(TRUE)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;</font><font face="Comic Sans MS" color="#008000">//wait for a connection<br /></font><font face="Courier New">&nbsp;&nbsp;&nbsp;&nbsp; ClientSocket = accept(ListenSocket,NULL,NULL);<br />&nbsp;&nbsp;&nbsp;&nbsp;<font color="#000080">if</font>(ClientSocket == INVALID_SOCKET)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ERRORHANDLE<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DoSomething<br />}</font></p>
<p><font face="Courier New">上述代码简单易用，但是缺点在于如果没有client连接的话，accept一直不会返回，而且即使accept成功创建会话套接字，在阻塞方式下，C/S间传输数据依然要将recv，send这类函数放到一个循环中，反复等待数据的到来，这种轮询的方式效率很低。为此，Winsock提供了异步模式的5种I/O模型，这些模型会在有网络事件（如socket收到连接请求，读取收到的数据请求等等）时通过监视集合(select)，事件对象(WSAEventSelect,重叠I/O)，窗口消息(WSAAsyncSelect)，回调函数(重叠I/O),完成端口的方式通知程序，告诉我们可以&#8220;干活了&#8221;，这样的话大大的提高了执行效率，程序只需枕戈待旦，兵来将挡水来土掩，通知我们来什么网络事件，就做相应的处理即可。</font></p>
<p><font face="Courier New"><strong>2.WSAEventSelect模型的使用</strong></font></p>
<p><font face="Courier New">WSAEventSelect模型其实很简单，就是将一个事件对象同一个socket绑定并设置要监视的网络事件，当这个socket有我们感兴趣的网络事件到达时，ws2_32.dll就将这个事件对象置为受信状态(signaled)，在程序中等待这个事件对象受信后，根据网络事件类型做不同的处理。如果对线程同步机制有些了解的话，这个模型很容易理解，其实就是CreateEvent系列的winsock版。</font></p>
<p><font face="Courier New">无代码无真相，具体API的参数含义可以参考MSDN，MSDN上对这个模型解释的非常详尽。<br /><font face="Comic Sans MS" color="#008000">// 使用WSAEventSelect的代码片段，百度贴吧字数限制，略去错误处理及界面操作<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 为了能和多个客户端通信，使用两个数组分别记录所有通信的会话套接字<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 以及和这些套接字绑定的事件对象<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// WSA_MAXIMUM_WAIT_EVENTS是系统内部定义的宏，值为64<br /></font><br />&nbsp;&nbsp;&nbsp;&nbsp; SOCKET g_sockArray[WSA_MAXIMUM_WAIT_EVENTS];<br />&nbsp;&nbsp;&nbsp;&nbsp; WSAEVENT g_eventArray[WSA_MAXIMUM_WAIT_EVENTS];<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 事件对象计数器<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">int</font> nEventTotal = 0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 创建监听套接字sListenSocket，并对其绑定端口和本机ip 代码省去<br /></font>&nbsp;&nbsp;&nbsp;&nbsp; ........<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 设置sListenSocket为监听状态<br /></font>&nbsp;&nbsp;&nbsp;&nbsp; listen(sListenSocket, 5);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 创建事件对象，同CreateEvent一样，event创建后被置为非受信状态<br /></font>&nbsp;&nbsp;&nbsp;&nbsp; WSAEVENT acceptEvent = WSACreateEvent();<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 将sListenSocket和acceptEvent关联起来<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 并注册程序感兴趣的网络事件FD_ACCEPT 和 FD_CLOSE<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 这里由于是在等待客户端connect，所以FD_ACCEPT和FD_CLOSE是我们关心的<br /></font>&nbsp;&nbsp;&nbsp;&nbsp; WSAEventSelect(sListenSocket, acceptEvent, FD_ACCEPT|FD_CLOSE);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 添加到数组中<br /></font>&nbsp;&nbsp;&nbsp;&nbsp; g_eventArray[nEventTotal] = acceptEvent;<br />&nbsp;&nbsp;&nbsp;&nbsp; g_sockArray[nEventTotal] = sListenSocket;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp; nEventTotal++;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 处理网络事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">while</font>(TRUE)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 由于第三个参数是 FALSE，所以 g_eventArray 数组中有一个元素受信 WSAWaitForMultipleEvents 就返回<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 注意 返回值 nIndex 减去 WSA_WAIT_EVENT_0 的值才是受信事件在数组中的索引。<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 如果有多个事件同时受信，函数返回索引值最小的那个。<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 由于第四个参数指定 WSA_INFINITE ，所以没有对象受信时会无限等待。<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">int</font> nIndex = WSAWaitForMultipleEvents(nEventTotal, g_eventArray, FALSE, WSA_INFINITE, FALSE);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 取得受信事件在数组中的位置。<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nIndex = nIndex - WSA_WAIT_EVENT_0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 判断受信事件 g_eventArray[nIndex] 所关联的套接字 g_sockArray[nIndex] 的网络事件类型<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// MSDN中说如果事件对象不是NULL, WSAEnumNetworkEvents 会帮咱重置该事件对象为非受信，方便等待新的网络事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 也就是说这里的 g_eventArray[nIndex] 变为非受信了，所以程序中不用再调用 WSAResetEvent了<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// WSANETWORKEVENTS 这个结构中 记录了关于g_sockArray[nIndex] 的网络事件和错误码<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSANETWORKEVENTS event;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEnumNetworkEvents(g_sockArray[nIndex], g_eventArray[nIndex], &amp;event);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 这里处理 FD_ACCEPT 这个网络事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// event.lNetWorkEvents中记录的是网络事件类型<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_ACCEPT)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// event.iErrorCode是错误代码数组，event.iErrorCode[FD_ACCEPT_BIT] 为0表示正常<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.iErrorCode[FD_ACCEPT_BIT] == 0)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 连接数超过系统约定的范围<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(nEventTotal &gt; WSA_MAXIMUM_WAIT_EVENTS)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">continue</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 没有问题就可以accept了<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOCKET sAcceptSocket = accept(g_sockArray[nIndex], NULL, NULL);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 新建的会话套接字用于C/S间的数据传输，所以这里关心FD_READ,FD_CLOSE,FD_WRITE三个事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEVENT event = WSACreateEvent();<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEventSelect(sAcceptSocket, event, FD_READ|FD_CLOSE|FD_WRITE);<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 将新建的会话套接字及与该套接字关联的事件对象添加到数组中<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_eventArray[nEventTotal] = event;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_sockArray[nEventTotal] = sAcceptSocket;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nEventTotal++;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">//event.iErrorCode[FD_ACCEPT_BIT] != 0 出错了<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 这里处理FD_READ通知消息，当会话套接字上有数据到来时，ws2_32.dll会记录该事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_READ)&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.iErrorCode[FD_READ_BIT] == 0)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">int</font> nRecv = recv(g_sockArray[nIndex], buffer, nbuffersize, 0);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(nRecv == SOCKET_ERROR)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 为了程序更鲁棒，这里要特别处理一下WSAEWOULDBLOCK这个错误<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// MSDN中说在异步模式下有时recv(WSARecv)读取时winsock的缓冲区中没有数据，导致recv立即返回<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 错误码就是 WSAEWOULDBLOCK,但这时程序并没有出问题，在有新的数据到来时recv还是可以读到数据的<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 所以不能仅仅根据recv返回值是SOCKET_ERROR就认为出错从而执行退出操作。<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">//如果错误码不是WSAEWOULDBLOCK 则表示真的出错了<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(WSAGetLastError() != WSAEWOULDBLOCK)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 没出任何错误<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DoSomeThing...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// event.iErrorCode[FD_READ_BIT] != 0<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 这里处理FD_CLOSE通知消息<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 当连接被关闭时，ws2_32.dll会记录FD_CLOSE事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_CLOSE)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.iErrorCode[FD_CLOSE_BIT] == 0)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(g_sockArray[nIndex]);<br /><font face="Comic Sans MS" color="#008000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 将g_sockArray[nIndex]从g_sockArray数组中删除<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">for</font>(<font face="Courier New" color="#000080">int</font> j=nIndex; j&lt;nEventTotal-1; j++)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_sockArray[j] = g_sockArray[j+1];&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nEventTotal--;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// event.iErrorCode[FD_CLOSE_BIT] != 0<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 处理FD_WRITE通知消息<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// FD_WRITE事件其实就是ws2_32.dll告诉我们winsock的缓冲区已经ok，可以发送数据了<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 同recv一样，send(WSASend)的返回值也要对SOCKET_ERROR特殊判断一下 WSAEWOULDBLOCK<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else <font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_WRITE)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000"><u><strong>//关于FD_WRITE的讨论在下面。</strong></u><br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 如果出错退出循环 则将套接字数组中的套接字与事件对象统统解除关联<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 给WSAEventSelect的最后一个参数传0可以解除g_sockArray[nIndex]和g_eventArray[nIndex]的关联<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 解除关联后,ws2_32.dll将停止记录g_sockArray[nIndex]这个套接字的网络事件<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 退出时还要关闭所有创建的套接字和事件对象<br /></font><br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">for</font>(<font face="Courier New" color="#000080">int</font> i = 0; i &lt; nEventTotal; i++)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAEventSelect(g_sockArray[i], g_eventArray[i], 0);&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(g_sockArray[i]);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSACloseEvent(g_eventArray[i]);<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br /><br />&nbsp;&nbsp;&nbsp;&nbsp; nEventTotal = 0;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp; DoSomethingElse....</font></p>
<p>&nbsp;</p>
<p><strong>3.FD_WRITE 事件的触发</strong></p>
<p>常见的网络事件中，FD_ACCEPT和FD_READ都比较好理解。一开始我唯一困惑的就是FD_WRITE,搞不清楚到底什么时候才会触发这个网络事件，后来仔细查了MSDN又看了一些文章并测试了下，终于搞懂了FD_WRITE的触发机制。</p>
<p>下面是MSDN中对FD_WRITE触发机制的解释：</p>
<p>The FD_WRITE network event is handled slightly differently. An FD_WRITE network event is recorded when a socket is first connected with connect/WSAConnect or accepted with accept/WSAAccept, and then after a send fails with WSAEWOULDBLOCK and buffer space becomes available. Therefore, an application can assume that sends are possible starting from the first FD_WRITE network event setting and lasting until a send returns WSAEWOULDBLOCK. After such a failure the application will find out that sends are again possible when an FD_WRITE network event is recorded and the associated event object is set</p>
<p>FD_WRITE事件只有在以下三种情况下才会触发</p>
<p>&#9312;client 通过connect（WSAConnect）首次和server建立连接时，在client端会触发FD_WRITE事件</p>
<p>&#9313;server通过accept（WSAAccept）接受client连接请求时，在server端会触发FD_WRITE事件</p>
<p>&#9314;send（WSASend）/sendto（WSASendTo）发送失败返回WSAEWOULDBLOCK，并且当缓冲区有可用空间时，则会触发FD_WRITE事件</p>
<p>&#9312;&#9313;其实是同一种情况，在第一次建立连接时，C/S端都会触发一个FD_WRITE事件。</p>
<p>主要是&#9314;这种情况：send出去的数据其实都先存在winsock的发送缓冲区中，然后才发送出去，如果缓冲区满了，那么再调用send（WSASend,sendto,WSASendTo）的话，就会返回一个 WSAEWOULDBLOCK的错误码，接下来随着发送缓冲区中的数据被发送出去，缓冲区中出现可用空间时，一个 FD_WRITE 事件才会被触发，这里比较容易混淆的是 FD_WRITE 触发的前提是 <strong>缓冲区要先被充满然后随着数据的发送又出现可用空间</strong>，而不是缓冲区中有可用空间，也就是说像如下的调用方式可能出现问题</p>
<p><font face="Courier New">else <font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_WRITE)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.iErrorCode[FD_WRITE_BIT] == 0)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(g_sockArray[nIndex], buffer, buffersize);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ....<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />}</font></p>
<p>问题在于建立连接后 FD_WRITE 第一次被触发， 如果send发送的数据不足以充满缓冲区，虽然缓冲区中仍有空闲空间，但是 FD_WRITE 不会再被触发，程序永远也等不到可以发送的网络事件。</p>
<p>基于以上原因，在收到FD_WRITE事件时，程序就用循环或线程不停的send数据，直至send返回WSAEWOULDBLOCK，表明缓冲区已满，再退出循环或线程。当缓冲区中又有新的空闲空间时，FD_WRITE 事件又被触发，程序被通知后又可发送数据了。</p>
<p>上面代码片段中省略的对 FD_WRITE 事件处理</p>
<p>&nbsp;</p>
<div style="padding-left: 4px; padding-bottom: 1px; overflow: auto; width: 100%; word-break: break-all; padding-top: 1px; background-color: #c0c0c0"><font face="Courier New" color="#000000">else <font face="Courier New" color="#000080">if</font>(event.lNetworkEvents &amp; FD_WRITE)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(event.iErrorCode[FD_WRITE_BIT] == 0)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">while</font>(TRUE)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 得到要发送的buffer，可以是用户的输入，从文件中读取等<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetBuffer....<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(send(g_sockArray[nIndex], buffer, buffersize, 0) == SOCKET_ERROR)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Comic Sans MS" color="#008000">// 发送缓冲区已满<br /></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">if</font>(WSAGetLastError() == WSAEWOULDBLOCK)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle...<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp;&nbsp; else<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ErrorHandle..<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<font face="Courier New" color="#000080">break</font>;<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />}</font></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>P.S.</p>
<p>1.WSAWaitForMultipleEvents内部调用的还是WaitForMulipleObjectsEx，MSDN中说使用WSAEventSelect模型等待时是不占cpu时间的，这也是效率比阻塞winsock高的原因。</p>
<p>2.WSAAsycSelect的用法和WSAEventSelect类似，不同的是网络事件的通知是以windows消息的方式发送到指定的窗口。</p>
<p>&nbsp;</p><img src ="http://www.cppblog.com/yehao/aggbug/153729.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-08-18 16:04 <a href="http://www.cppblog.com/yehao/articles/153729.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>网络远程监控的VC++6编程示例</title><link>http://www.cppblog.com/yehao/articles/148421.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Fri, 10 Jun 2011 05:58:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/148421.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/148421.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/148421.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/148421.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/148421.html</trackback:ping><description><![CDATA[<pre><h2>转自<a href="http://210.40.7.188/Source/yckz.asp">http://210.40.7.188/Source/yckz.asp</a></h2><h2>
</h2><h2>            计算机研究生开放研究
    <font color="red">《网络远程监控的VC++6编程示例》</font>
            美国GeneChiu基金资助

网络远程监控的VC++6编程示例
</h2>
<h3>

1 远程关机  编程：研究生 吴勇
  这是一个可作服务器也可作客户的使用WinSock进行网络远程关机的VC++6程序：
</h3>

#include "afxwin.h"       //使用MFC的字符串类CSting
#include "winsock.h"
#pragma comment(lib, "wsock32")
#include<math.h>  
#include "assert.h"
#include "iostream.h"

CString	    m_strServer;  
UINT	    m_nPort;       
CString	    m_strUser;     
CString	    m_strPass;      

SOCKET      m_hSocket;      
SOCKADDR_IN m_addr;        

void StartUp();             
void CleanUp();            
void OnOK();                
void SendToServer();        
void RecvFromServer();

void StartUpS();       
void CreateSocket(); 
void BindSocket();    
void Listen_Recv();   
void CleanUpS();
int p=0;
char buff[100];
char buff1[100]; 
int strlen(char str[]);
char b[100];
char ip[100];

int an=0;


void main(){
	
	cout&lt;&lt;"请输入1.开启服务器,输入2.运行客户端:";
	cin&gt;&gt;an;
	
	if (an==2)
		
	{   
		 cout&lt;&lt;"请输入服务器IP:";
	     cin&gt;&gt;ip;
		  m_strServer = ip; 
		  m_nPort = 2350;
		  
		  
		  StartUp();  
		  char cc;
		  do{
			  OnOK();
			  cout&lt;&lt;"已经与服务器连接成功，向服务器发送数据\n";
			  cout&lt;&lt;"请输入 G 关机:";
			  cin&gt;&gt;b;
			  m_strUser =b;
			  SendToServer();
			  cout&lt;&lt;"发送成功";
			  RecvFromServer();
			  cout&lt;&lt;"\n本次通信完成;再来吗(y/n)\n";cin&gt;&gt;cc;
		  }while (cc != 'n');	
		  CleanUp(); 
	}
	if(an==1)
	{     
		StartUpS();
		CreateSocket();
		BindSocket();
		Listen_Recv();
		CleanUpS();
	
	}
}


void OnOK() 
{
	
	if(m_hSocket != NULL){
		closesocket(m_hSocket);
		m_hSocket = NULL;
	}
	if(m_hSocket == NULL){
		m_hSocket = socket(AF_INET, SOCK_STREAM,0);
		assert(m_hSocket != NULL);
	}
	
	
	m_addr.sin_family = AF_INET;
	m_addr.sin_addr.S_un.S_addr = inet_addr(m_strServer.GetBuffer(0));
	m_addr.sin_port = htons(m_nPort);
	
	int ret = 0;
	int error = 0;    
	
	ret = connect(m_hSocket, (LPSOCKADDR)&amp;m_addr, sizeof(m_addr));
	if(ret == SOCKET_ERROR){
		TRACE("Connect Error: %d \n", (error = WSAGetLastError()));
		
		if(error == 10061)
			AfxMessageBox(_T("请确认服务器确实已经打开并工作在同样的端口！"));
		
		return ;
	}
}

void SendToServer(){ 
	
	CString str;
	str += m_strUser;
	
	
	
	int ret = send(m_hSocket, str, str.GetLength(), 0);
	if(ret != str.GetLength()){
		TRACE("Send data error: %d\n", WSAGetLastError());
		
		return ;
	}
}

void RecvFromServer(){	
	
	
	int ret = recv(m_hSocket, buff1,100, 0);
	if(ret == 0){
		TRACE("Recv data error: %d\n", WSAGetLastError());
		
		return ;
	}
//	buff1[ret] = '\0';
//	cout&lt;<buff1<<endl; StartUp() CleanUp() TRACE(?UnInitilize Error:%d\n?, WSAGetLastError()); strlen(char str[]){ i="0;"  while(str[i])i++; i; StartUpS() WSADATA wsaData; WORD version="MAKEWORD(2,"  &wsaData); CreateSocket(){ closesocket(m_hSocket); if(m_hSocket NULL){ SOCK_STREAM,0); assert(m_hSocket BindSocket(){ 给服务器结构地址m_addr赋值： 表示该socket处于Internet域 指定服务端口 sin_addr把一个IP地址保存为4字节的无符号整数， 本机测试时可用，即127.0.0.1 若使用实际IP地址，可用函数inet_addr将其转换为4字节的无符号整数 error="0;"  (LPSOCKADDR)&m_addr, sizeof(m_addr)); cout<<?Bind Error!\n?; , Listen_Recv(){ while(1){ int 2); cout<<?Listen SOCKET s="accept(m_hSocket,"  NULL, NULL); SOCKET_ERROR){ cout<<?Accept Error: %d \n?; cout<<?客户连接成功！\n?; buff, 100, 0); if(ret 0 || ret="WSAStartup(version,"  SOCKET_ERROR ){ cout<<?Recv data error: %d\n?; return ; if(buff[0]="='G')" ExitWindowsEx(0,0); cout<<?处理完成；服务器重新等待客户连接！\n?; void CleanUpS() { if (WSACleanup() !='0)cout<<"初始化错误!\n";' cout<<?UnInitilize Error:%d\n?; } <h3 if( ( s_addr="INADDR_ANY;"  m_addr.sin_addr.S_un. sin_port="htons(m_nPort);"  m_addr. sin_family="AF_INET;" m_hsocket="NULL;" m_nport="2350;">
2 远程监控程序  编程：研究生 唐郑熠

这个个远程监控程序，以命令行&#8220;ldc -s&#8221;运行时，是作为服务器运行；
以命令行&#8220;ldc &#8211;c IP&#8221;(IP为服务器IP地址)运行时，是作为客户端运行。
服务器向客户端发送指令&#8220;A&#8221;或&#8220;B&#8221;，若发送指令A，则客户端控制计算机自动关机；
若发送指令B，则将计算机所有的击键记录下来发送给服务器。
其中，ASCII值为32～128的可见ASCII字符直接输出，
其它的不可见ASCII字符以&#8220;(%d)&#8221;的形式输出，即括号加上该字符的ASCII值。
<h3></h3>


#include "afxwin.h"  
#include "iostream.h"
#include "assert.h"
#include "winsock.h"
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#pragma comment(lib, "wsock32")  //编译连接时使用静态连接库wsock32.lib

UINT	m_nPort;     //服务器端口号--16位整数
SOCKET m_hSocket;    //服务器进程套接字类
SOCKADDR_IN m_addr;  //套接字地址结构
CString	    m_strServer;    //服务器IP地址
//SOCKET      m_hSocket;      //服务器进程套接字类
//SOCKADDR_IN m_addr;         //套接字地址结构
char cmd;

void StartUp();       //初始化Winsock的函数:启动Winsock
void CreateSocket();  //使用socket函数建立服务器进程的流（TCP）套接字
void BindSocket();    //使用bind函数绑定IP地址到套接字，使网络上可识别该套接字
void Listen_Recv();   //将套接字置入监听模式并准备接受连接进行通信
void CleanUp();       //终止对Winsock DLL的使用，释放资源
void OnOK();                //建立TCP通信套接字并与服务器连接
//void SendToServer();        //向服务器发送数据
//void RecvFromServer();      //向服务器发送数据
void server();
void client();
void shutDown();

//从str的头一个字符往后检查，直到字符'\0'(其值为0)为止，即返回字符串str字符'\0'前的长
int strlen(char str[]);

int main(int argc, char *argv[]) {
	if( argc == 3 ) {
        if( !strcmp( argv[1], "-c") ) { 
			m_nPort = 2350;
			m_strServer = argv[2];
			client();
		}
    }
    else if ( argc &gt; 3 ){// invalid args.
        puts("For server: \n\tusage: echocs [-d]\nFor client:\n\tusage: echocs -c host\n");
        return 1;
    }
	else {
		m_nPort = 2350;
		server();
	}
	return 0;
}

void server() {
	printf("2007级唐郑熠&#8212;远程控制服务器\n\n");
/**
1  应用程序初始化：注册wsock.dll
	**/   
	StartUp();
	/**
    2  使用socket函数建立服务器进程的流（TCP）套接字：
	**/   
	CreateSocket();
	/**
    3  使用bind函数绑定IP地址到套接字，使网络上可识别该套接字，bind原型为
	int bind(SOCKET s, const struct sockaddr* name, int namelen)
	其中：s为等待连接的未绑定套接字，name为套接字地址结构，更为有用的是
	SOCKADDR_IN，用来标识TCP/IP协议下的地址，这时通过强制类型转换可将
	SOCKADDR_IN结构转换为sockaddr结构
	**/
    
	BindSocket();
	
	//UpdateData();//更新用户在编辑框里对端口的改变,MFC对话框用  
	/**
    4  将套接字置入监听模式并准备接受连接进行通信：bind只是将套接字与地址关联在一起；
	而使套接字进入等候连接则由函数listen完成，listen的原型为：
	**/     
	Listen_Recv();
	/**
	5	关闭套接字，终止对Winsock DLL的使用，释放资源
	**/
	CleanUp();
}

//初始化Winsock的函数:启动Winsock
void StartUp()
{
	//初始化全局变量
    m_nPort = 2350;
	m_hSocket = NULL;
    //初始化winsock
	WSADATA wsaData;
	WORD version = MAKEWORD(2, 0); //
	//调用WSAStartup函数对Winsock DLL进行初始化，协商Winsock版本，分配资源
	int ret = WSAStartup(version, &amp;wsaData);
	if(ret != 0)cout&lt;&lt;"初始化错误!\n";
}

void CreateSocket() {
	//先关闭已经建立的套接字
	if(m_hSocket != NULL){  
		closesocket(m_hSocket);
		m_hSocket = NULL;
	}
	//用socket（构造）函数建立流类型（TCP）套接字
	if(m_hSocket == NULL){
		m_hSocket = socket(AF_INET, SOCK_STREAM,0);
		assert(m_hSocket != NULL); //C语言函数，进行判断，若不真，则做出错处理
	}
	
}

void BindSocket(){	
	//给服务器结构地址m_addr赋值：
	m_addr.sin_family = AF_INET;  //表示该socket处于Internet域
	m_addr.sin_port = htons(m_nPort);  //指定服务端口
	//sin_addr把一个IP地址保存为4字节的无符号整数，
	m_addr.sin_addr.S_un.S_addr = INADDR_ANY; //本机测试时可用，即127.0.0.1
	//若使用实际IP地址，可用函数inet_addr将其转换为4字节的无符号整数
	//  m_addr.sin_addr.S_un.S_addr=inet_addr("210.40.7.131");
	
	int error = 0;
	int ret = bind(m_hSocket, (LPSOCKADDR)&amp;m_addr, sizeof(m_addr)); 
	if(ret == SOCKET_ERROR){
		cout&lt;&lt;"Bind Error!\n";//, (error = WSAGetLastError()));
		return ;
	} 
	
}

void Listen_Recv(){
	//cout&lt;&lt;"请按一键开始运行服务器";
	//char cc;cin&gt;&gt;cc;  
	
	//while(1){//服务器永远循环服务
	//侦听
	int ret = listen(m_hSocket, 2);
	if(ret == SOCKET_ERROR){
		cout&lt;&lt;"Listen Error: %d \n";//, (error = WSAGetLastError()));
		return ;
	}
	//
	SOCKET s = accept(m_hSocket, NULL, NULL);
	if(s == SOCKET_ERROR){
		cout&lt;&lt;"Accept Error: %d \n";//, (error = WSAGetLastError()));
		return ;
	}
	//cout&lt;&lt;"客户连接成功！\n";
	//接受客户数据
	char buff[1];
	ret = recv(s, buff, 1, 0);
	if(ret == 0 || ret == SOCKET_ERROR ){
		cout&lt;&lt;"Recv data error: %d\n";//, WSAGetLastError());
		return ;
	}
	printf("请选择 A.远程关机 B.远程监控键盘:");
	scanf("%c", &amp;buff[0]);
	cmd = buff[0];
	ret = send(s, buff, 1, 0);
	if (cmd == 'B' || cmd == 'b') {
		printf("监听到的内容：\n");
		while (1) {
			//侦听
			int ret = listen(m_hSocket, 2);
			if(ret == SOCKET_ERROR){
				cout&lt;&lt;"Listen Error: %d \n";//, (error = WSAGetLastError()));
				return ;
			}
			//
			SOCKET s = accept(m_hSocket, NULL, NULL);
			if(s == SOCKET_ERROR){
				cout&lt;&lt;"Accept Error: %d \n";//, (error = WSAGetLastError()));
				return ;
			}
			//cout&lt;&lt;"客户连接成功！\n";
			
			//接受客户数据
			char buff[1];
			ret = recv(s, buff, 1, 0);
			if(ret == 0 || ret == SOCKET_ERROR ){
				cout&lt;&lt;"Recv data error: %d\n";//, WSAGetLastError());
				return ;
			}
			int cnt = 0;
			if (buff[0] &gt;= 32 &amp;&amp; buff[0] &lt;= 128) {
				printf("%c", buff[0]);
				cnt++;
			}
			else {
				if (buff[0] == 13) {
					printf("\n");
				}
				else {
					printf("(%d)", buff[0]);
				}
				cnt++;
			}
		}
	}
	/**
	4   处理客户消息
	**/
	///**  回声服务：向客户返回消息(20个字节) 
	//ret = send(s, buff, 20, 0);
	//**/
	//
	//cout&lt;&lt;"处理完成；服务器重新等待客户连接！\n";
	//}
	
}

//应用程序关闭套接字后，终止对Winsock DLL的使用，释放资源
void CleanUp()
{
	if (WSACleanup() != 0){
		cout&lt;&lt;"UnInitilize Error:%d\n";//, WSAGetLastError());
	}
}

void client(){   
	
	// m_strServer = "127.0.0.1";   //_T("127.0.0.1");
	// m_nPort = 2350;
	// m_strUser = "贵大";//ware";
	// m_strPass = "Guizhou Univ";
	printf("2007级唐郑熠&#8212;远程控制客户端\n\n");
	StartUp();   //初始化Winsock的函数:启动Winsock
   	char cc;
	char buff[1];
	OnOK();//建立通信套接字
	//cout&lt;&lt;"已经与服务器连接成功，向服务器发送数据\n";
	int ret = send(m_hSocket, buff, 1, 0);
	if(ret != 1){
		TRACE("Send data error: %d\n", WSAGetLastError());
		//        cout&lt;&lt;"Send data error: %d\n";//, WSAGetLastError());
		return ;
	}
	ret = recv(m_hSocket, buff, 1, 0);
	if(ret == 0){
		TRACE("Recv data error: %d\n", WSAGetLastError());
		//        cout&lt;&lt;"Recv data error: %d\n";//, WSAGetLastError());
		return ;
	}
	cmd = buff[0];
	if (cmd == 'A' || cmd == 'a') {
		shutDown();
	}
	else {
		char ch;
		while(1) {
			if (kbhit()) {
				OnOK();//建立通信套接字
				ch = getch();
				buff[0] = ch;
				int ret = send(m_hSocket, buff, 1, 0);
				if(ret != 1) {
					TRACE("Send data error: %d\n", WSAGetLastError());
					//        cout&lt;&lt;"Send data error: %d\n";//, WSAGetLastError());
					return ;
				}
			}
		}
	}
	/*SendToServer();//向服务器发送数据
	cout&lt;&lt;"发送成功，服务器返回数据：\n\n";
	RecvFromServer();//从服务器接收数据*/
	//cout&lt;&lt;"\n本次通信完成;再来吗(y/n)\n";cin&gt;&gt;cc;	
	CleanUp();   //终止对Winsock DLL的使用，释放资源
}

//调用socket建立TCP通信套接字并与服务器连接
void OnOK() 
{
    //调用socket建立TCP通信套接字
	if(m_hSocket != NULL){
		closesocket(m_hSocket);
		m_hSocket = NULL;
	}
	if(m_hSocket == NULL){
		m_hSocket = socket(AF_INET, SOCK_STREAM,0);
		assert(m_hSocket != NULL);
	}
	
	//UpdateData();
    
	//给套接字地址结构赋值
	m_addr.sin_family = AF_INET;
	m_addr.sin_addr.S_un.S_addr = inet_addr(m_strServer.GetBuffer(0));
	m_addr.sin_port = htons(m_nPort);
	
	int ret = 0;// 
	int error = 0;    
	//用connect建立连接,连接成功，返回0，否则返回SOCKET_ERROR
	ret = connect(m_hSocket, (LPSOCKADDR)&amp;m_addr, sizeof(m_addr));
	if(ret == SOCKET_ERROR){//出错处理
		TRACE("Connect Error: %d \n", (error = WSAGetLastError()));
        //        cout&lt;&lt;"Connect Error!\n";//, (error = WSAGetLastError()));
		if(error == 10061)
			//	AfxMessageBox(_T("请确认服务器确实已经打开并工作在同样的端口！"));
			cout&lt;&lt;"请确认服务器确实已经打开并工作在同样的端口！\n";
		return ;
	}
}

/*void SendToServer(){
	//
	CString str;
	str += m_strUser;
	str += m_strPass;
	//发送数据，如成功，返回发送的字节数
	int ret = send(m_hSocket, str, str.GetLength(), 0);
	if(ret != str.GetLength()){
		TRACE("Send data error: %d\n", WSAGetLastError());
        //        cout&lt;&lt;"Send data error: %d\n";//, WSAGetLastError());
		return ;
	}
}*/

/*
void RecvFromServer(){	
	char buff[256];
	/** 接受数据recv函数的返回值：
	如无错误，返回接到的字节数(bytes received)
	如连接已关闭，返回0 
	要不然，返回出错处理值：SOCKET_ERROR 
	**/
/*	int ret = recv(m_hSocket, buff, 256, 0);
	if(ret == 0){
		TRACE("Recv data error: %d\n", WSAGetLastError());
        //        cout&lt;&lt;"Recv data error: %d\n";//, WSAGetLastError());
		return ;
	}
	buff[ret] = '\0';//ASCII值为0的字符：'\0'
	cout&lt;&lt;buff&lt;&lt;endl;//显示服务器送回数据
	//AfxMessageBox(buff);
}*/

//应用程序关闭套接字后，终止对Winsock DLL的使用，释放资源
/*void CleanUp()
{
	if (WSACleanup() != 0){
		TRACE("UnInitilize Error:%d\n", WSAGetLastError());
        //        cout&lt;&lt;"UnInitilize Error:%d\n";//, WSAGetLastError());
	}
}*/

//从str的头一个字符往后检查，直到字符'\0'(其值为0)为止，即返回字符串str字符'\0'前的长
int strlen(char str[]){
	int i=0;
	while(str[i])i++;
	return i;
}

void shutDown() {
	TOKEN_PRIVILEGES tkp, tkpnewbutignored;   
	HANDLE hdltokenhandle;   
	LUID tmpluid;   
	BOOL rc;   
	DWORD lbufferneeded;   
	HANDLE hdlprocesshandle = GetCurrentProcess();   
	rc = OpenProcessToken(hdlprocesshandle, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, &amp;hdltokenhandle);   
	rc = LookupPrivilegeValue("", "SeShutdownPrivilege",   &amp;tmpluid);   
	tkp.PrivilegeCount = 1;  
	tkp.Privileges[0].Luid = tmpluid;   
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;   
	rc = AdjustTokenPrivileges(hdltokenhandle, FALSE, &amp;tkp, sizeof(tkpnewbutignored), &amp;tkpnewbutignored, &amp;lbufferneeded);   
	ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE, 0);   
	
}</pre><img src ="http://www.cppblog.com/yehao/aggbug/148421.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-06-10 13:58 <a href="http://www.cppblog.com/yehao/articles/148421.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> CreateIoCompletionPort和完成端口</title><link>http://www.cppblog.com/yehao/articles/148154.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 06 Jun 2011 13:09:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/148154.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/148154.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/148154.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/148154.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/148154.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/hionceshine/archive/2008/11/25/3362669.aspx">http://blog.csdn.net/hionceshine/archive/2008/11/25/3362669.aspx</a></p>
<p><br />摘自《Networking Programming for Microsoft Windows》第八章</p>
<p>&#8220;完成端口&#8221;模型是迄今为止最为复杂的一种I/O模型。然而，假若一个应用程序同时需要管理为数众多的套接字，那么采用这种模型，往往可以达到最佳的系统性能！</p>
<p>从本质上说，完成端口模型要求我们创建一个Win32完成端口对象，通过指定数量的线程，对重叠I/O请求进行管理，以便为已经完成的重叠I/O请求提供服务。</p>
<p>使用这种模型之前，首先要创建一个I/O完成端口对象，用它面向任意数量的套接字句柄，管理多个I/O请求。要做到这一点，需要调用CreateCompletionPort函数。<br />该函数定义如下：</p>
<p><br />HANDLE CreateIoCompletionPort(<br />&nbsp;&nbsp;&nbsp; HANDLE FileHandle,<br />&nbsp;&nbsp;&nbsp; HANDLE ExistingCompletionPort,<br />&nbsp;&nbsp;&nbsp; ULONG_PTR CompletionKey,<br />&nbsp;&nbsp;&nbsp; DWORD NumberOfConcurrentThreads<br />);</p>
<p>在我们深入探讨其中的各个参数之前，首先要注意该函数实际用于两个明显有别的目的：<br />1. 用于创建一个完成端口对象。<br />2. 将一个句柄同完成端口关联到一起。</p>
<p>最开始创建一个完成端口时，唯一感兴趣的参数便是NumberOfConcurrentThreads（并发线程的数量）；前面三个参数都会被忽略。NumberOfConcurrentThreads参数的特殊之处在于，它定义了在一个完成端口上，同时允许执行的线程数量。理想情况下，我们希望每个处理器各自负责一个线程的运行，为完成端口提供服务，避免过于频繁的线程&#8220;场景&#8221;切换。若将该参数设为0，表明系统内安装了多少个处理器，便允许同时运行多少个线程！可用下述代码创建一个I/O完成端口：</p>
<p><br />hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);</p>
<p>该语句的作用是返回一个句柄，在为完成端口分配了一个套接字句柄后，用来对那个端口进行标定（引用）。</p>
<p>一、工作者线程与完成端口<br />成功创建一个完成端口后，便可开始将套接字句柄与对象关联到一起。但在关联套接字之前，首先必须创建一个或多个&#8220;工作者线程&#8221;，以便在I/O请求投递给完成端口对象后，为完成端口提供服务。在这个时候，大家或许会觉得奇怪，到底应创建多少个线程，以便为完成端口提供服务呢？这实际正是完成端口模型显得颇为&#8220;复杂&#8221;的一个方面，因为服务I/O请求所需的数量取决于应用程序的总体设计情况。在此要记住的一个重点在于，在我们调用CreateIoCompletionPort时指定的并发线程数量，与打算创建的工作者线程数量相比，它们代表的并非同一件事情。早些时候，我们曾建议大家用CreateIoCompletionPort函数为每个处理器<br />都指定一个线程（处理器的数量有多少，便指定多少线程）以避免由于频繁的线程&#8220;场景&#8221;交换活动，从而影响系统的整体性能。CreateIoCompletionPort函数的NumberOfConcurrentThreads参数明确指示系统：在一个完成端口上，一次只允许n个工作者线程运行。假如在完成端口上创建的工作者线程数量超出n个，那么在同一时刻，最多只允许n个线程运行。但实际上，在一段较短的时间内，系统有可能超过这个值，但很快便会把它减少至事先在CreateIoCompletionPort函数中设定的值。那么，为何实际创建的工作者线程数量有时要比CreateIoCompletionPort函数设定的多一些呢？这样做有必要吗？如先前所述，这主要取决于<br />应用程序的总体设计情况。假定我们的某个工作者线程调用了一个函数，比如Sleep或WaitForSingleObject，但却进入了暂停（锁定或挂起）状态，那么允许另一个线程代替它的位置。换言之，我们希望随时都能执行尽可能多的线程；当然，最大的线程数量是事先在CreateIoCompletionPort调用里设定好的。这样一来，假如事先预计到自己的线程有可能暂时处于停顿状态，那么最好能够创建比CreateIoCompletionPort的NumberOfConcurrentThreads参数的值多的线程，以便到时候充分发挥系统的潜力。一旦在完成端口上拥有足够多的工作者线程来为I/O请求提供服务，便可着手将套接字句柄同完成端口关联到一起。这要求我们在一个现有的完成端口上，调用CreateIoCompletionPort函数，同时为前三个参数&#8212;&#8212;FileHandle，ExistingCompletionPort和CompletionKey&#8212;&#8212;提供套接字的信息。其中， FileHandle参数指定一个要同完成端口关联在一起的套接字句柄。ExistingCompletionPort参数指定的是一个现有的完成端口。CompletionKey（完成键）参数则指定要与某个特定套接字句柄关联在一起的&#8220;单句柄数据&#8221;；在这个参数中，应用程序可保存与一个套接字对应的任意类型的信息。之所以把它叫作&#8220;单句柄数据&#8221;，是由于它只对<br />应着与那个套接字句柄关联在一起的数据。可将其作为指向一个数据结构的指针，来保存套接字句柄；在那个结构中，同时包含了套接字的句柄，以及与那个套接字有关的其他信息。</p>
<p>根据我们到目前为止学到的东西，首先来构建一个基本的应用程序框架。下面阐述了如何使用完成端口模型，来开发一个ECHO服务器应用。在这个程序中，我们基本上按下述步骤行事：</p>
<p>1) 创建一个完成端口。第四个参数保持为0，指定在完成端口上，每个处理器一次只允许执行一个工作者线程。<br />2) 判断系统内到底安装了多少个处理器。<br />3) 创建工作者线程，根据步骤2)得到的处理器信息，在完成端口上，为已完成的I/O请求提供服务。<br />4) 准备好一个监听套接字，在端口5150上监听进入的连接请求。<br />5) 使用accept函数，接受进入的连接请求。<br />6) 创建一个数据结构，用于容纳&#8220;单句柄数据&#8221;，同时在结构中存入接受的套接字句柄。<br />7) 调用CreateIoCompletionPort，将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键（CompletionKey）参数，将单句柄数据结构传递给CreateIoCompletionPort。<br />8) 开始在已接受的连接上进行I/O操作。在此，我们希望通过重叠I/O机制，在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后，一个工作者线程会为I/O请求提供服务，同时继续处理未来的I/O请求，稍后便会在步骤3 )指定的工作者例程中，体验到这一点。<br />9) 重复步骤5 ) ~ 8 )，直至服务器中止。</p>
<p>二、完成端口和重叠I/O<br />将套接字句柄与一个完成端口关联在一起后，便可以套接字句柄为基础，投递发送与接收请求，开始对I/O请求的处理。接下来，可开始依赖完成端口，来接收有关I/O操作完成情况的通知。从本质上说，完成端口模型利用了Win32重叠I/O机制。在这种机制中，象WSASend和WSARecv这样的Winsock API调用会立即返回。此时，需要由我们的应用程序负责在以后的某个时间，通过一个OVERLAPPED结构，来接收调用的结果。在完成端口模型中，要想做到这一点，需要使用GetQueuedCompletionStatus（获取排队完成状态）函数，让一个或多个工作者线程在完成端口上等待。该函数的定义如下：</p>
<p><br />BOOL GetQueuedCompletionStatus(<br />&nbsp;&nbsp;&nbsp; HANDLE CompletionPort,<br />&nbsp;&nbsp;&nbsp; LPDWORD lpNumberOfBytes,<br />&nbsp;&nbsp;&nbsp; PULONG_PTR lpCompletionKey,<br />&nbsp;&nbsp;&nbsp; LPOVERLAPPED* lpOverlapped,<br />&nbsp;&nbsp;&nbsp; DWORD dwMilliseconds<br />);</p>
<p>其中，CompletionPort参数对应于要在上面等待的完成端口。lpNumberOfBytes参数负责在完成了一次I/O操作后（如WSASend或WSARecv），接收实际传输的字节数。lpCompletionKey参数为原先传递进入CreateIoCompletionPort函数的套接字返回&#8220;单句柄数据&#8221;。如我们早先所述，大家最好将套接字句柄保存在这个&#8220;键&#8221;（Key）中。lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数，因为可用它获取每个I/O操作的数据。而最后一个参数，dwMilliseconds，用于指定调用者希望等待一个完成数据包在完成端口上出现的时间。假如将其设为INFINITE，调用会无休止地等待下去。</p>
<p>三、单句柄数据和单I/O操作数据<br />一个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成通知后，在lpCompletionKey和lpOverlapped参数中，会包含一些必要的套接字信息。利用这些信息，可通过完成端口，继续在一个套接字上的I/O处理。通过这些参数，可获得两方面重要的套接字数据：单句柄数据，以及单I/O操作数据。其中，lpCompletionKey参数包含了&#8220;单句柄数据&#8221;，因为在一个套接字首次与完成端口关联到一起的时候，那些数据便与一个特定的套接字句柄对应起来了。这些数据正是我们在进行CreateIoCompletionPort API调用的时候，通过CompletionKey参数传递的。如早先所述，应用程序可通过该参数传递任意类型的数据。通常情况下，应用程序会将与I/O请求有关的套接字句柄保存在这里。lpOverlapped参数则包含了一个OVERLAPPED结构，在它后面跟随&#8220;单I/O操作数据&#8221;。我们的工作者线程处理一个完成数据包时（将数据原封不动打转回去，接受连接，投递另一个线程，等等），这些信息是它必须要知道的。单I/O操作数据可以是追加到一个OVERLAPPED结构末尾的、任意数量的字节。假如一个函数要求用到一个OVERLAPPED结构，我们便必须将这样的一个结构传递进去，以满足它的要求。要想做到这一点，一个简单的方法是定义一个结构，然后将OVERLAPPED结构作为新结构的第一个元素使用。举个例子来说，可定义下述数据结构，实现对单I/O操作数据的管理：</p>
<p><br />typedef struct<br />{<br />&nbsp;&nbsp;&nbsp; OVERLAPPED Overlapped;<br />&nbsp;&nbsp;&nbsp; WSABUF&nbsp;&nbsp;&nbsp;&nbsp; DataBuf;<br />&nbsp;&nbsp;&nbsp; CHAR&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer[DATA_BUFSIZE];<br />&nbsp;&nbsp;&nbsp; BOOL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OperationType;<br />}PER_IO_OPERATION_DATA</p>
<p>该结构演示了通常要与I/O操作关联在一起的某些重要数据元素，比如刚才完成的那个I/O操作的类型（发送或接收请求）。在这个结构中，我们认为用于已完成I/O操作的数据缓冲区是非常有用的。要想调用一个Winsock API函数，同时为其分配一个OVERLAPPED结构，既可将自己的结构&#8220;造型&#8221;为一个OVERLAPPED指针，亦可简单地撤消对结构中的OVERLAPPED元素的引用。如下例所示：</p>
<p>PER_IO_OPERATION_DATA PerIoData;<br />// 可像下面这样调用一个函数<br />&nbsp; WSARecv(socket, ..., (OVERLAPPED *)&amp;PerIoData);<br />// 或像这样<br />&nbsp; WSARecv(socket, ..., &amp;(PerIoData.Overlapped));</p>
<p>在工作线程的后面部分，等GetQueuedCompletionStatus函数返回了一个重叠结构（和完成键）后，便可通过撤消对OperationType成员的引用，调查到底是哪个操作投递到了这个句柄之上（只需将返回的重叠结构造型为自己的PER_IO_OPERATION_DATA结构）。对单I/O操作数据来说，它最大的一个优点便是允许我们在同一个句柄上，同时管理多个I/O操作（读/写，多个读，多个写，等等）。大家此时或许会产生这样的疑问：在同一个套接字上，真的有必要同时投递多个I/O操作吗？答案在于系统的&#8220;伸缩性&#8221;，或者说&#8220;扩展能力&#8221;。例如，假定我们的机器安装了多个中央处理器，每个处理器都在运行一个工作者线程，那么在同一个时<br />候，完全可能有几个不同的处理器在同一个套接字上，进行数据的收发操作。</p>
<p>最后要注意的一处细节是如何正确地关闭I/O完成端口&#8212;特别是同时运行了一个或多个线程，在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时，强行释放一个OVERLAPPED结构。要想避免出现这种情况，最好的办法是针对每个套接字句柄，调用closesocket函数，任何尚未进行的重叠I/O操作都会完成。一旦所有套接字句柄都已关闭，便需在完成端口上，终止所有工作者线程的运行。要想做到这一点， 需要使用PostQueuedCompletionStatus函数，向每个工作者线程都发送一个特殊的完成数据包。该函数会指示每个线程都&#8220;立即结束并退出&#8221;。下面是PostQueuedCompletionStatus函数的定义：</p>
<p><br />BOOL PostQueuedCompletionStatus(<br />&nbsp;&nbsp;&nbsp; HANDLE CompletionPort,<br />&nbsp;&nbsp;&nbsp; DWORD dwNumberOfBytesTransferred,<br />&nbsp;&nbsp;&nbsp; ULONG_PTR dwCompletionKey,<br />&nbsp;&nbsp;&nbsp; LPOVERLAPPED lpOverlapped<br />);</p>
<p>其中，CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred、dwCompletionKey和lpOverlapped这三个参数来说，每一个都允许我们指定一个值，直接传递给GetQueuedCompletionStatus函数中对应的参数。这样一来，一个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后，便可根据由这三个参数的某一个设置的特殊值，决定何时应该退出。例如，可用dwCompletionPort参数传递0值，而一个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭，便可使用CloseHandle函数，关闭完成端口，最终安全退出程序。</p>
<p>注：CreateIoCompletionPort ，PostQueuedCompletionStatus ，GetQueuedCompletionStatus 等函数的用法说明。</p>
<p>Platform SDK: Storage</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>I/O Completion Ports</p>
<p>I/O completion ports are the mechanism by which an application uses a pool of threads that was created when the application was started to process asynchronous I/O requests. These threads are created for the sole purpose of processing I/O requests. Applications that process many concurrent asynchronous I/O requests can do so more quickly and efficiently by using I/O completion ports than by using creating threads at the time of the I/O request.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>I/O完成端口(s)是一种机制，通过这个机制，应用程序在启动时会首先创建一个线程池，然后该应用程序使用线程池处理异步I/O请求。这些线程被创建的唯一目的就是用于处理I/O请求。对于处理大量并发异步I/O请求的应用程序来说，相比于在I/O请求发生时创建线程来说，使用完成端口(s)它就可以做的更快且更有效率。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The CreateIoCompletionPort function associates an I/O completion port with one or more file handles. When an asynchronous I/O operation started on a file handle associated with a completion port is completed, an I/O completion packet is queued to the port. This can be used to combine the synchronization point for multiple file handles into a single object.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>CreateIoCompletionPort函数会使一个I/O完成端口与一个或多个文件句柄发生关联。当与一个完成端口相关的文件句柄上启动的异步I/O操作完成时，一个I/O完成包就会进入到该完成端口的队列中。对于多个文件句柄来说，就可以把这些多个文件句柄合并成一个单独的对象，这个可以被用来结合同步点？</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>A thread uses the GetQueuedCompletionStatus function to wait for a completion packet to be queued to the completion port, rather than waiting directly for the asynchronous I/O to complete. Threads that block their execution on a completion port are released in last-in-first-out (LIFO) order. This means that when a completion packet is queued to the completion port, the system releases the last thread to block its execution on the port.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>调用GetQueuedCompletionStatus函数，某个线程就会等待一个完成包进入到完成端口的队列中，而不是直接等待异步I/O请求完成。线程（们）就会阻塞于它们的运行在完成端口（按照后进先出队列顺序的被释放）。这就意味着当一个完成包进入到完成端口的队列中时，系统会释放最近被阻塞在该完成端口的线程。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>When a thread calls GetQueuedCompletionStatus, it is associated with the specified completion port until it exits, specifies a different completion port, or frees the completion port. A thread can be associated with at most one completion port.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>调用GetQueuedCompletionStatus，线程就会将会与某个指定的完成端口建立联系，一直延续其该线程的存在周期，或被指定了不同的完成端口，或者释放了与完成端口的联系。一个线程只能与最多不超过一个的完成端口发生联系。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The most important property of a completion port is the concurrency value. The concurrency value of a completion port is specified when the completion port is created. This value limits the number of runnable threads associated with the completion port. When the total number of runnable threads associated with the completion port reaches the concurrency value, the system blocks the execution of any subsequent threads that specify the completion port until the number of runnable threads associated with the completion port drops below the concurrency value. The most efficient scenario occurs when there are completion packets waiting in the queue, but no waits can be satisfied because the port has reached its concurrency limit. In this case, when a running thread calls GetQueuedCompletionStatus, it will immediately pick up the queued completion packet. No context switches will occur, because the running thread is continually picking up completion packets and the other threads are unable to run.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>完成端口最重要的特性就是并发量。完成端口的并发量可以在创建该完成端口时指定。该并发量限制了与该完成端口相关联的可运行线程的数目。当与该完成端口相关联的可运行线程的总数目达到了该并发量，系统就会阻塞任何与该完成端口相关联的后续线程的执行，直到与该完成端口相关联的可运行线程数目下降到小于该并发量为止。最有效的假想是发生在有完成包在队列中等待，而没有等待被满足，因为此时完成端口达到了其并发量的极限。此时，一个正在运行中的线程调用GetQueuedCompletionStatus时，它就会立刻从队列中取走该完成包。这样就不存在着环境的切换，因为该处于运行中的线程就会连续不断地从队列中取走完成包，而其他的线程就不能运行了。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The best value to pick for the concurrency value is the number of CPUs on the machine. If your transaction required a lengthy computation, a larger concurrency value will allow more threads to run. Each transaction will take longer to complete, but more transactions will be processed at the same time. It is easy to experiment with the concurrency value to achieve the best effect for your application.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>对于并发量最好的挑选值就是您计算机中cpu的数目。如果您的事务处理需要一个漫长的计算时间，一个比较大的并发量可以允许更多线程来运行。虽然完成每个事务处理需要花费更长的时间，但更多的事务可以同时被处理。对于应用程序来说，很容易通过测试并发量来获得最好的效果。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The PostQueuedCompletionStatus function allows an application to queue its own special-purpose I/O completion packets to the completion port without starting an asynchronous I/O operation. This is useful for notifying worker threads of external events.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>PostQueuedCompletionStatus函数允许应用程序可以针对自定义的专用I/O完成包进行排队，而无需启动一个异步I/O操作。这点对于通知外部事件的工作者线程来说很有用。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>The completion port is freed when there are no more references to it. The completion port handle and every file handle associated with the completion port reference the completion port. All the handles must be closed to free the completion port. To close the port handle, call the CloseHandle function.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>在没有更多的引用针对某个完成端口时，需要释放该完成端口。该完成端口句柄以及与该完成端口相关联的所有文件句柄都需要被释放。调用CloseHandle可以释放完成端口的句柄。</p>
<p>&nbsp;</p><img src ="http://www.cppblog.com/yehao/aggbug/148154.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-06-06 21:09 <a href="http://www.cppblog.com/yehao/articles/148154.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>解决TCP连接数过多的问题</title><link>http://www.cppblog.com/yehao/articles/146984.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 23 May 2011 09:32:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146984.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146984.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146984.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146984.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146984.html</trackback:ping><description><![CDATA[<div id="blog_text" class="cnt">
<p>TCP状态迁移，CLOSE_WAIT &amp; FIN_WAIT2 的问题 </p>
<div></div>
<div>
<div>
<p><span style="font-size: 12px"><strong>TCP状态迁移</strong><br />大家对netstat -a命令很熟悉，但是，你有没有注意到STATE一栏呢，基本上显示着established,time_wait,close_wait等，这些到底是 什么意思呢，在这篇文章，我将会详细的阐述。<br />大家很明白TCP初始化连接三次握手吧：发SYN包，然后返回SYN/ACK包，再发ACK包，连接正式建立。但是这里有点出入，当请求者收到SYS /ACK包后，就开始建立连接了，而被请求者第三次握手结束后才建立连接。但是大家明白关闭连接的工作原理吗？关闭连接要四次握手：发FIN包，ACK 包，FIN包，ACK包，四次握手！！为什么呢，因为TCP连接是全双工，我关了你的连接，并不等于你关了我的连接。<br /></span><span style="font-size: 12px"><br />客户端TCP状态迁移：<br />CLOSED-&gt;SYN_SENT-&gt;ESTABLISHED-&gt;FIN_WAIT_1-&gt;FIN_WAIT_2-&gt;TIME_WAIT-&gt;CLOSED<br />服务器TCP状态迁移：<br />CLOSED-&gt;LISTEN-&gt;SYN收到 -&gt;ESTABLISHED-&gt;CLOSE_WAIT-&gt;LAST_ACK-&gt;CLOSED<br /><br />当客户端开始连接时，服务器还处于LISTENING，<br />客户端发一个SYN包后，他就处于SYN_SENT状态,服务器就处于SYS收到状态,<br />然后互相确认进入连接状态ESTABLISHED.</span></p>
<p><br /><span style="font-size: 12px">当客户端请求关闭连接时,客户端发送一个FIN包后,客户端就进入FIN_WAIT_1状态,等待对方的确认包,<br />服务器发送一个ACK包给客户,客户端收到ACK包后结束FIN_WAIT_1状态,进入FIN_WAIT_2状态,等待服务器发过来的关闭请求,<br />服务器发一个FIN包后,进入CLOSE_WAIT状态,<br />当客户端收到服务器的FIN包,FIN_WAIT_2状态就结束,然后给服务器端的FIN包给以一个确认包,客户端这时进入TIME_WAIT,<br />当服务器收到确认包后,CLOSE_WAIT状态结束了,<br />这时候服务器端真正的关闭了连接.但是客户端还在TIME_WAIT状态下,<br /><br />什么时候结束呢.我在这里再讲到一个新名词:2MSL等待状态,其实TIME_WAIT就是2MSL等待状态,<br />为什么要设置这个状态,原因是有足够的时间让ACK包到达服务器端,如果服务器端没收到ACK包，超时了，然后重新发一个FIN包，直到服务器收到ACK 包.<br /><br />TIME_WAIT状态等待时间是在TCP重新启动后不连接任何请求的两倍.<br />大家有没有发现一个问题:如果对方在第三次握手的时候出问题,如发FIN包的时候,不知道什么原因丢了这个包,然而这边一直处在FIN_WAIT_2状 态,而且TCP/IP并没有设置这个状态的过期时间,那他一直会保留这个状态下去,越来越多的FIN_WAIT_2状态会导致系统崩溃.</span></p>
<p><span style="font-size: 12px">上面我碰到的这个问题主要因为TCP的结束流程未走完，造成连接未释放。现设客户端主动断开连接，流程如下</span></p>
<div><a href="http://hiphotos.baidu.com/22000254/pic/item/dae8964ee73399e8d0c86a55.jpg" target="_blank">
<div>
<p><span style="font-size: 12px"><img class="blogimg" alt="" src="http://hiphotos.baidu.com/dongaiping/pic/item/f7db664a79ab6e7808f7ef00.jpg" small="0" /></span></p></div></a></div>
<p>&nbsp;</p>
<p><span style="font-size: 12px">如上图所示，<br /><br />Client&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 消息&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server</span></p>
<p><span style="font-size: 12px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; close()<br />------ FIN -------&gt;<br />FIN_WAIT1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CLOSE_WAIT<br />&lt;----- ACK -------<br />FIN_WAIT2 <br />close()<br />&lt;------ FIN ------&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />TIME_WAIT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LAST_ACK&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></p>
<p><span style="font-size: 12px">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ------ ACK -------&gt; <br />CLOSED<br />CLOSED</span></p>
<p><br /><span style="font-size: 12px">由于Server的Socket在客户端已经关闭时而没有调用关闭，<br />造成服务器端的连接处在&#8220;挂起&#8221;状态，而客户端则处在等待应答的状态上。<br />此问题的典型特征是：<br />一端处于FIN_WAIT2 ，而另一端处于CLOSE_WAIT.</span></p>
<p><span style="font-size: 12px">不过，根本问题还是程序写的不好，有待提高</span></p>
<p><span style="font-size: 12px">-------------------------------------------------------------------------</span></p>
<p><span style="font-size: 12px">CLOSE_WAIT，TCP的癌症，TCP的朋友。</span></p>
<p><span style="font-size: 12px">CLOSE_WAIT状态的生成原因<br />首先我们知道，如果我们的服务器程序APACHE处于CLOSE_WAIT状态的话，说明套接字是被动关闭的！</span></p>
<p><span style="font-size: 12px">因为如果是CLIENT端主动断掉当前连接的话，那么双方关闭这个TCP连接共需要四个packet：</span></p>
<p><span style="font-size: 12px">Client ---&gt; FIN ---&gt; Server</span></p>
<p><span style="font-size: 12px">Client &lt;--- ACK &lt;--- Server</span></p>
<p><span style="font-size: 12px">这时候Client端处于FIN_WAIT_2状态；而Server 程序处于CLOSE_WAIT状态。</span></p>
<p><span style="font-size: 12px">Client &lt;--- FIN &lt;--- Server</span></p>
<p><span style="font-size: 12px">这时Server 发送FIN给Client，Server 就置为LAST_ACK状态。</span></p>
<p><span style="font-size: 12px">Client ---&gt; ACK ---&gt; Server</span></p>
<p><span style="font-size: 12px">Client回应了ACK，那么Server 的套接字才会真正置为CLOSED状态。</span></p>
<p><span style="font-size: 12px">Server 程序处于CLOSE_WAIT状态，而不是LAST_ACK状态，说明还没有发FIN给Client，那么可能是在关闭连接之前还有许多数据要发送或者其 他事要做，导致没有发这个FIN packet。</span></p>
<p><span style="font-size: 12px">通常来说，一个CLOSE_WAIT会维持至少2个小时的时间。如果有个流氓特地写了个程序，给你造成一堆的 CLOSE_WAIT，消耗</span><span style="font-size: 12px">你的资源，那么通常是等不到释放那一刻，系统就已经解决崩溃了。</span></p>
<p><span style="font-size: 12px">只能通过修改一下TCP/IP的参数，来缩短这个时间：修改tcp_keepalive_*系列参数有助于解决这个 问题。<br /><br /></span></p>
<p><span style="color: #000000; font-size: 12px">解决这个问题的方法是修改系统的参数，系统默认超时时间的是7200秒，也就是2小时， 这个太大了，可以修改如下几个参数：</span></p>
<p>sysctl -w net.ipv4.tcp_keepalive_time=30<br />sysctl -w net.ipv4.tcp_keepalive_probes=2<br />sysctl -w net.ipv4.tcp_keepalive_intvl=2</p>
<p>然后，执行sysctl命令使修改生效。</p>
<p>&nbsp;</p>
<div>
<p><span style="font-size: 12px">连接进程是通过一系列状态表示的，这些状态有：<br />LISTEN，SYN-SENT，SYN-RECEIVED，ESTABLISHED，FIN-WAIT-1，FIN-WAIT-2，CLOSE- WAIT，CLOSING，LAST-ACK，TIME-WAIT和CLOSED。<br /><br /></span><span style="font-size: 12px">各个状态的意义如下： <br />LISTEN - 侦听来自远方TCP端口的连接请求； <br />SYN-SENT -在发送连接请求后等待匹配的连接请求； <br />SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认； <br />ESTABLISHED- 代表一个打开的连接，数据可以传送给用户； <br />FIN-WAIT-1 - 等待远程TCP的连接中断请求，或先前的连接中断请求的确认；<br />FIN-WAIT-2 - 从远程TCP等待连接中断请求； <br />CLOSE-WAIT - 等待从本地用户发来的连接中断请求； <br />CLOSING -等待远程TCP对连接中断的确认； <br />LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认； <br />TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认； <br />CLOSED - 没有任何连接状态；<br /><br />TCP连接过程是状态的转换，促使发生状态转换的是用户调用：<br />OPEN，SEND，RECEIVE，CLOSE，ABORT和STATUS；<br />传送过来的数据段，特别那些包括以下标记的数据段SYN，ACK，RST和FIN；<br />还有超时，上面所说的都会时TCP状态发生变化。</span></p>
<p>&nbsp;</p></div>
<div><span style="font-size: 12px"></span>&nbsp; 
<p><span style="font-size: 12px">TCP连接的状态转换图</span></p>
<p>&nbsp;</p>
<p>
<table style="table-layout: fixed">
<tbody>
<tr>
<td>
<div>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">这</span><span style="font-size: 10.5pt">个</span><span style="font-size: 10.5pt">图</span><span style="font-size: 10.5pt">n</span><span style="font-size: 10.5pt">多人都 知道，</span><span style="font-size: 10.5pt">它</span><span style="font-size: 10.5pt">对</span><span style="font-size: 10.5pt">排除和定 位网</span><span style="font-size: 10.5pt">络或系统</span><span style="font-size: 10.5pt">故障</span><span style="font-size: 10.5pt">时</span><span style="font-size: 10.5pt">大有帮助，但是怎</span><span style="font-size: 10.5pt">样</span><span style="font-size: 10.5pt">牢牢地</span><span style="font-size: 10.5pt">将</span><span style="font-size: 10.5pt">这张图</span><span style="font-size: 10.5pt">刻在</span><span style="font-size: 10.5pt">脑</span><span style="font-size: 10.5pt">中呢？那</span><span style="font-size: 10.5pt">么</span><span style="font-size: 10.5pt">你就一定要</span><span style="font-size: 10.5pt">对 这张图</span><span style="font-size: 10.5pt">的</span><span style="font-size: 10.5pt">每</span><span style="font-size: 10.5pt">一个状</span><span style="font-size: 10.5pt">态，及转换的过程有深刻地认识，不能只停留在一知半解之中。下面对这张图的</span><span style="font-size: 10.5pt">11</span><span style="font-size: 10.5pt">种状 态详细解释一下，以便加强记忆！不过在这之前，先回顾一下</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">建立连接的三次握手过程，以及关闭连接的四次握手过程。</span></span></p><span style="font-size: 10.5pt">
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">、建立连接协议（三次握手）</span></span><span style="font-size: 10.5pt"><br /></span><span style="font-size: 12px"><span style="font-size: 10.5pt">（</span><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">）客户 端发送一个带</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">标志的</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">报文到服务器。这是三次握手过程中的报文</span><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">（</span><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">） 服务器端回应客户端的，这是三次握手中的第</span><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">个报文，这个报文同时带</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">标志和</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">标 志。因此它表示对刚才客户端</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">报文的回应；同时又标志</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">给客户端，询问客户端是否准备好进行数据通 讯。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">（</span><span style="font-size: 10.5pt">3</span><span style="font-size: 10.5pt">） 客户必须再次回应服务段一个</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文，这是报文段</span><span style="font-size: 10.5pt">3</span><span style="font-size: 10.5pt">。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">、连接终止协议（四次握手）</span></span><span style="font-size: 10.5pt"><br /></span><span style="font-size: 12px"><span style="font-size: 10.5pt">　 　由于</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">连 接是全双工的，因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">来终 止这个方向的连接。收到一个</span><span style="font-size: 10.5pt"> FIN</span><span style="font-size: 10.5pt">只意味着这一方向上没有数据流动，一个</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">连接 在收到一个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">　（</span><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">）</span><span style="font-size: 10.5pt"> TCP</span><span style="font-size: 10.5pt">客 户端发送一个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">，用来关闭客户到服务器的数据传送（报文段</span><span style="font-size: 10.5pt">4</span><span style="font-size: 10.5pt">）。</span></span><span style="font-size: 10.5pt"><br /></span><span style="font-size: 12px"><span style="font-size: 10.5pt">　（</span><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">） 服务器收到这个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">，它发回一个</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">，确认序号为收到的序号加</span><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">（报文段</span><span style="font-size: 10.5pt">5</span><span style="font-size: 10.5pt">）。和</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">一 样，一个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">将占用一个序号。</span></span><span style="font-size: 10.5pt"><br /></span><span style="font-size: 12px"><span style="font-size: 10.5pt">　（</span><span style="font-size: 10.5pt">3</span><span style="font-size: 10.5pt">） 服务器关闭客户端的连接，发送一个</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">给客户端（报文段</span><span style="font-size: 10.5pt">6</span><span style="font-size: 10.5pt">）。</span></span><span style="font-size: 10.5pt"><br /></span><span style="font-size: 12px"><span style="font-size: 10.5pt">　（</span><span style="font-size: 10.5pt">4</span><span style="font-size: 10.5pt">） 客户段发回</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文确认，并将确认序号设置为收到序号加</span><span style="font-size: 10.5pt">1</span><span style="font-size: 10.5pt">（报文段</span><span style="font-size: 10.5pt">7</span><span style="font-size: 10.5pt">）。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">CLOSED: </span><span style="font-size: 10.5pt">这个没什么好说的了，表示初始状态。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">LISTEN: </span><span style="font-size: 10.5pt">这个也是非常容易理解的一个状态，表示服务器端的某个</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">处 于监听状态，可以接受连接了。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">SYN_RCVD: </span><span style="font-size: 10.5pt">这个状态表示接受到了</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">报 文，在正常情况下，这个状态是服务器端的</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">在建立</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">连接时的三次握手会话过程中的一个中间状态，很短暂，基本上用</span><span style="font-size: 10.5pt">netstat</span><span style="font-size: 10.5pt">你是很难看到这种状态的，除非你特意写了一个客户端测试程序，故意将三次</span><span style="font-size: 10.5pt">TCP</span><span style="font-size: 10.5pt">握手 过程中最后一个</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文不予发送。因此这种状态时，当收到客户端的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文 后，它会进入到</span><span style="font-size: 10.5pt">ESTABLISHED</span><span style="font-size: 10.5pt">状态。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">SYN_SENT: </span><span style="font-size: 10.5pt">这个状态与</span><span style="font-size: 10.5pt">SYN_RCVD</span><span style="font-size: 10.5pt">遥想呼应，当客户端</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">执行</span><span style="font-size: 10.5pt">CONNECT</span><span style="font-size: 10.5pt">连接时，它首先发送</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">报文，因此也随即它会进入到了</span><span style="font-size: 10.5pt">SYN_SENT</span><span style="font-size: 10.5pt">状态，并等待服务端的发送三次握手中的第</span><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">个报文。</span><span style="font-size: 10.5pt">SYN_SENT</span><span style="font-size: 10.5pt">状态表示客户端已发送</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">报文。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">ESTABLISHED</span><span style="font-size: 10.5pt">：这个容易理解了，表示连接已经建立了。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">FIN_WAIT_1: </span><span style="font-size: 10.5pt">这个状态要好好解释一下，其实</span><span style="font-size: 10.5pt">FIN_WAIT_1</span><span style="font-size: 10.5pt">和</span><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">状态的真正含义都是表示等待对方的</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报 文。而这两种状态的区别是：</span><span style="font-size: 10.5pt">FIN_WAIT_1</span><span style="font-size: 10.5pt">状态实际上是当</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">在</span><span style="font-size: 10.5pt">ESTABLISHED</span><span style="font-size: 10.5pt">状态时，它想主动关闭连接，向对方发送了</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文，此时该</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">即进入到</span><span style="font-size: 10.5pt">FIN_WAIT_1</span><span style="font-size: 10.5pt">状态。而当对方回应</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文后，则进入到</span><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">状态，当然在实际的正常情况 下，无论对方何种情况下，都应该马上回应</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文，所以</span><span style="font-size: 10.5pt">FIN_WAIT_1</span><span style="font-size: 10.5pt">状态一般是比较难见到的，而</span><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">状态还有时常常可以用</span><span style="font-size: 10.5pt">netstat</span><span style="font-size: 10.5pt">看到。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">：上面已经详细解释了这种状态，实际上</span><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">状态下的</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">，表示半连接，也即有一方要求</span><span style="font-size: 10.5pt">close</span><span style="font-size: 10.5pt">连接，但另外还告诉对方，我暂时还有点 数据需要传送给你，稍后再关闭连接。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">TIME_WAIT: </span><span style="font-size: 10.5pt">表示收到了对方的</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报 文，并发送出了</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文，就等</span><span style="font-size: 10.5pt">2MSL</span><span style="font-size: 10.5pt">后即可回到</span><span style="font-size: 10.5pt">CLOSED</span><span style="font-size: 10.5pt">可用状态了。如果</span><span style="font-size: 10.5pt">FIN_WAIT_1</span><span style="font-size: 10.5pt">状态下，收到了对方同时带</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">标 志和</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">标志的报文时，可以直接进入到</span><span style="font-size: 10.5pt">TIME_WAIT</span><span style="font-size: 10.5pt">状态，而无须经过</span><span style="font-size: 10.5pt">FIN_WAIT_2</span><span style="font-size: 10.5pt">状态。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">CLOSING: </span><span style="font-size: 10.5pt">这种状态比较特殊，实际情况中应该是很少见，属于一种比较罕见的例外状态。正常情况下，当你发 送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文后，按理来说是应该先收到（或同时收到）对方的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报 文，再收到对方的</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文。但是</span><span style="font-size: 10.5pt">CLOSING</span><span style="font-size: 10.5pt">状态表示你发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文后，并没有收到对方的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报 文，反而却也收到了对方的</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文。什么情况下会出现此种情况呢？其实细想一下，也不难得出结论：那就是如果双方几乎在同时</span><span style="font-size: 10.5pt">close</span><span style="font-size: 10.5pt">一 个</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">的话，那么就出现了双方同时发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文的情况，也即会出现</span><span style="font-size: 10.5pt">CLOSING</span><span style="font-size: 10.5pt">状态，表示双方都正在关闭</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">连接。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">CLOSE_WAIT: </span><span style="font-size: 10.5pt">这种状态的含义其实是表示在等待关闭。怎么理解呢？当对方</span><span style="font-size: 10.5pt">close</span><span style="font-size: 10.5pt">一 个</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">后发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文给自己，你系统毫无疑问地会回应一个</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文 给对方，此时则进入到</span><span style="font-size: 10.5pt">CLOSE_WAIT</span><span style="font-size: 10.5pt">状态。接下来呢，实际上你真正需要考虑的事情是察看你是否还有数据发送给对方，如果没有的话， 那么你也就可以</span><span style="font-size: 10.5pt">close</span><span style="font-size: 10.5pt">这个</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">，发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文给对方，也即关闭连接。所以你在</span><span style="font-size: 10.5pt">CLOSE_WAIT</span><span style="font-size: 10.5pt">状态下，需要完成的事情是等待你去关闭连接。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">LAST_ACK: </span><span style="font-size: 10.5pt">这个状态还是比较容易好理解的，它是被动关闭一方在发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报 文后，最后等待对方的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文。当收到</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文后，也即可以进入到</span><span style="font-size: 10.5pt">CLOSED</span><span style="font-size: 10.5pt">可用状态了。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">最后有</span><span style="font-size: 10.5pt">2</span><span style="font-size: 10.5pt">个问题 的回答，我自己分析后的结论（不一定保证</span><span style="font-size: 10.5pt">100%</span><span style="font-size: 10.5pt">正确）</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt"><span>1、<span style="font: 7pt Times New Roman"><span style="font-family: Arial"></span></span></span></span><span style="font-size: 10.5pt">为什么建立连接协议是三次握手，而关闭连接却是四次握手呢？</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">这是因为服务端的</span><span style="font-size: 10.5pt">LISTEN</span><span style="font-size: 10.5pt">状态下的</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">当收到</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">报文的建连请求后，它可以把</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">和</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">（</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">起 应答作用，而</span><span style="font-size: 10.5pt">SYN</span><span style="font-size: 10.5pt">起同步作用）放在一个报文里来发送。但关闭连接时，当收到对方的</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文 通知时，它仅仅表示对方没有数据发送给你了；但未必你所有的数据都全部发送给对方了，所以你可以未必会马上会关闭</span><span style="font-size: 10.5pt">SOCKET,</span><span style="font-size: 10.5pt">也即你可能还需要发送一些数据给对方之后，再发送</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文给对方来表示你同意现在可以关闭连接了，所以它这里的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文 和</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报文多数情况下都是分开发送的。</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt"><span>2、<span style="font: 7pt Times New Roman"><span style="font-family: Arial"></span></span></span></span><span style="font-size: 10.5pt">为什么</span><span style="font-size: 10.5pt">TIME_WAIT</span><span style="font-size: 10.5pt">状态还需要等</span><span style="font-size: 10.5pt">2MSL</span><span style="font-size: 10.5pt">后才能返回到</span><span style="font-size: 10.5pt">CLOSED</span><span style="font-size: 10.5pt">状 态？</span></span></p>
<p><span style="font-size: 12px"><span style="font-size: 10.5pt">这是因为：虽然双方 都同意关闭连接了，而且握手的</span><span style="font-size: 10.5pt">4</span><span style="font-size: 10.5pt">个报文也都协调和发送完毕，按理可以直接回到</span><span style="font-size: 10.5pt">CLOSED</span><span style="font-size: 10.5pt">状 态（就好比从</span><span style="font-size: 10.5pt">SYN_SEND</span><span style="font-size: 10.5pt">状态到</span><span style="font-size: 10.5pt">ESTABLISH</span><span style="font-size: 10.5pt">状态那样）；但是因为我们必须要假想网络是不可靠的，你无法保证你最后发送的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报 文会一定被对方收到，因此对方处于</span><span style="font-size: 10.5pt">LAST_ACK</span><span style="font-size: 10.5pt">状态下的</span><span style="font-size: 10.5pt">SOCKET</span><span style="font-size: 10.5pt">可能会因为超时未收到</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报文，而重发</span><span style="font-size: 10.5pt">FIN</span><span style="font-size: 10.5pt">报 文，所以这个</span><span style="font-size: 10.5pt">TIME_WAIT</span><span style="font-size: 10.5pt">状态的作用就是用来重发可能丢失的</span><span style="font-size: 10.5pt">ACK</span><span style="font-size: 10.5pt">报 文，并保证于此。</span></span></p></span></div></td></tr></tbody></table></p></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span style="font-size: 12px"><span><img class="blogimg" alt="" src="http://hiphotos.baidu.com/dongaiping/pic/item/930eb952c52d5a550df3e300.jpg" width="458" height="467" small="0" /></span><br /></span></p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 断开连接的时候， 当发起主动关闭的左边这方发送一个FIN过去后，<br /><span style="color: #ff0000">右边被动关闭的这方要回应一个ACK，这个ACK是TCP回应的，而不是应用程序发送的，<br />此时，被动关闭的一方就处于CLOSE_WAIT状态了。</span><br /><br />如果此时被动关闭的这一方不再继续调用closesocket,那么他就不会发送接下来的FIN，导致自己老是处于CLOSE_WAIT。<br />只有被动关闭的这一方调用了 closesocket,才会发送一个FIN给主动关闭的这一方，同时也使得自己的状态变迁为LAST_ACK。 <br /><br />比如被动关闭的是客户端.<br /><br />当对方调用closesocket的时候，你的程序正在 <br /><br />int nRet = recv(s,....); <br />if (nRet == SOCKET_ERROR) <br />{ <br />// closesocket(s); <br />return FALSE; <br />} <br /><br />很多人就是忘记了那句closesocket，这种代码太常见了。 <br />我的理解，<br />当主动关闭的一方发送FIN到被动关闭这边后，被动关闭这边的TCP马上回应一个ACK过去，同时向上面应用程序提交一个ERROR，<br />导致上面的SOCKET的send或者recv返回SOCKET_ERROR.<br /><br />正常情况下，如果上面在返回SOCKET_ERROR后调用了closesocket, 那么被动关闭的者一方的TCP就会发送一个FIN过去，自己的状态就变迁到LAST_ACK.</p>
<p>&nbsp;</p>
<p><span style="font-size: 12px"><br /></span></p>
<p><span style="font-size: 12px">服务器上出现大量的close_wait的例子和解决方法（例子从网上找的，基本差不多）<br /><br /></span><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#111;&#114;&#97;&#99;&#108;&#101;&#57;&#105;&#64;&#82;&#72;&#69;&#76;&#51;"><span style="color: #1463c4; font-size: 12px">oracle9i@RHEL3</span></a><span style="font-size: 12px">oracle9i]$ /usr/sbin/lsof -i | grep 6800<br />oracle&nbsp;&nbsp;&nbsp; 22725 oracle9i&nbsp;&nbsp;&nbsp; 3u IPv4 18621468&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800 (LISTEN)<br />oracle&nbsp;&nbsp;&nbsp; 22725 oracle9i&nbsp;&nbsp;&nbsp; 4u IPv4 18621469&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2174 (CLOSE_WAIT)<br />oracle&nbsp;&nbsp;&nbsp; 22725 oracle9i&nbsp;&nbsp;&nbsp; 8u IPv4 18621568&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2175 (CLOSE_WAIT)<br />oracle&nbsp;&nbsp;&nbsp; 22725 oracle9i&nbsp;&nbsp;&nbsp; 9u IPv4 18621578&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2176 (CLOSE_WAIT)<br />oracle&nbsp;&nbsp;&nbsp; 22726 oracle9i&nbsp;&nbsp;&nbsp; 3u IPv4 18621468&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800 (LISTEN)<br />oracle&nbsp;&nbsp;&nbsp; 22726 oracle9i&nbsp;&nbsp;&nbsp; 4u IPv4 18621469&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2174 (CLOSE_WAIT)<br />oracle&nbsp;&nbsp;&nbsp; 22726 oracle9i&nbsp;&nbsp;&nbsp; 8u IPv4 18621568&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2175 (CLOSE_WAIT)<br />oracle&nbsp;&nbsp;&nbsp; 22726 oracle9i&nbsp;&nbsp;&nbsp; 9u IPv4 18621578&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TCP RHEL3:6800-&gt;RHEL3:2176 (CLOSE_WAIT)<br /><br />[oracle9i@RHEL3 oracle9i]$ kill -9 22725<br /><br /># 22725, 22726就是使用该6800端口的进程号(PID)。<br />[oracle9i@RHEL3 oracle9i]$ /usr/sbin/lsof -i | grep 6800<br /></span></p>
<p><span style="font-size: 12px">进程被kill时，会释放占用的所有链接句柄。</span><span style="font-size: 12px"><br /><br /><span style="color: #ff0000">该问题的出现原因网上到处都是，也就是Socket的Client端出现异常没有Close就退出了。<br /></span><span style="color: #800000">//上面这句话不太准确，应该是被动关闭连接一端没有closesocket就退出了，此时被动关闭一端就处于close_wait 状态</span></span></p></div></div></div><img src ="http://www.cppblog.com/yehao/aggbug/146984.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-23 17:32 <a href="http://www.cppblog.com/yehao/articles/146984.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>WinSock重叠I/O模型</title><link>http://www.cppblog.com/yehao/articles/146983.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Mon, 23 May 2011 09:25:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146983.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146983.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146983.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146983.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146983.html</trackback:ping><description><![CDATA[<p><font face="Verdana" color="#000000">转自<a href="http://blog.csdn.net/phunxm/archive/2009/12/27/5085915.aspx">http://blog.csdn.net/phunxm/archive/2009/12/27/5085915.aspx</a><br />一．重叠I/O模型的概念</font></p>
<p><font face="Verdana" color="#000000">当调用ReadFile()和WriteFile()时，如果最后一个参数lpOverlapped设置为NULL，那么线程就阻塞在这里，直到读写完指定的数据后，它们才返回。这样在读写大文件的时候，很多时间都浪费在等待ReadFile()和WriteFile()的返回上面。如果ReadFile()和WriteFile()是往管道里读写数据，那么有可能阻塞得更久，导致程序性能下降。</font></p>
<p><font face="Verdana" color="#000000">为了解决这个问题，Windows引进了重叠I/O的概念，它能够同时以多个线程处理多个I/O。其实你自己开多个线程也可以处理多个I/O，但是系统内部对I/O的处理在性能上有很大的优化。它是Windows下实现异步I/O最常用的方式。</font></p>
<p><font face="Verdana" color="#000000">Windows为几乎全部类型的文件提供这个工具：磁盘文件、通信端口、命名管道和套接字。通常，使用ReadFile()和WriteFile()就可以很好地执行重叠I/O。</font></p>
<p><font face="Verdana" color="#000000">重叠模型的核心是一个重叠数据结构。若想以重叠方式使用文件，必须用 FILE_FLAG_OVERLAPPED标志打开它，例如：</font></p>
<p><font face="Verdana" color="#000000">HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);</font></p>
<p><font face="Verdana" color="#000000">如果没有规定该标志，则针对这个文件（句柄）,重叠I/O是不可用的。如果设置了该标志，当调用ReadFile()和WriteFile()操作这个文件（句柄）时，必须为最后一个参数提供OVERLAPPED结构：</font></p>
<p><font face="Verdana" color="#000000">// WINBASE.H</font></p>
<p><font face="Verdana" color="#000000">typedef struct _OVERLAPPED{</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Internal;</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; InternalHigh;</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; Offset;</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp; OffsetHigh;</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE&nbsp; hEvent; //关键的一个参数</font></p>
<p><font face="Verdana" color="#000000">}OVERLAPPED, *LPOVERLAPPED;</font></p>
<p><font face="Verdana" color="#000000">头两个32位的结构字Internal和InternalHigh由系统内部使用；其次两个32位结构字Offset和OffsetHigh使得可以设置64位的偏移量，该偏移量是要文件中读或写的地方。</font></p>
<p><font face="Verdana" color="#000000">因为I/O异步发生，就不能确定操作是否按顺序完成。因此，这里没有当前位置的概念。对于文件的操作，总是规定该偏移量。在数据流下（如COM端口或socket），没有寻找精确偏移量的方法，所以在这些情况中，系统忽略偏移量。这四个字段不应由应用程序直接进行处理或使用，OVERLAPPED结构的最后一个参数是可选的事件句柄，当I/O完成时，该事件对象受信（signaled）。程序通过等待该对事件对象受信来做善后处理。</font></p>
<p><font face="Verdana" color="#000000">设置了OVERLAPPED参数后，ReadFile()/WriteFile()的调用会立即返回，这时候你可以去做其他的事（所谓异步），系统会自动替你完成ReadFile()/WriteFile()相关的I/O操作。你也可以同时发出几个ReadFile()/WriteFile()的调用（所谓重叠）。当系统完成I/O操作时，会将OVERLAPPED.hEvent置信，我们可以通过调用WaitForSingleObject/WaitForMultipleObjects来等待这个I/O完成通知，在得到通知信号后，就可以调用GetOverlappedResult来查询I/O操作的结果，并进行相关处理。由此可以看出，OVERLAPPED结构在一个重叠I/O请求的初始化及其后续的完成之间，提供了一种沟通或通信机制。注意OVERLAPPED结构的生存周期，一般动态分配，待I/O完成后，回收重叠结构。</font></p>
<p><font face="Verdana" color="#000000">以Win32重叠I/O机制为基础，自WinSock 2发布开始，重叠I/O便已集成到新的WinSock API中，比如WSARecv()/WSASend()。这样一来，重叠I/O模型便能适用于安装了WinSock 2的所有Windows平台。可以一次投递一个或多个WinSock I/O请求。针对那些提交的请求，在它们完成之后，应用程序可为它们提供服务（对I/O的数据进行处理）。</font></p>
<p><font face="Verdana" color="#000000">相应的，要想在一个套接字上使用重叠I/O模型来处理网络数据通信，首先必须使用 WSA_FLAG_OVERLAPPED这个标志来创建一个套接字。如下所示：</font></p>
<p><font face="Verdana" color="#000000">SOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0,&nbsp; WSA_FLAG_OVERLAPPED);</font></p>
<p><font face="Verdana" color="#000000">创建套接字的时候，假如使用的是socket()函数，而非WSASocket()函数，那么会默认设置WSA_FLAG_OVERLAPPED标志。成功创建好了一个套接字，将其与一个本地接口绑定到一起后，便可开始进行这个套接字上的重叠I/O操作，方法是调用下述的WinSock 2函数，同时为它们指定一个WSAOVERLAPPED结构参数（#define WSAOVERLAPPED OVERLAPPED// WINSOCK2.H）：</font></p>
<p><font face="Verdana" color="#000000">（1）WSASend()</font></p>
<p><font face="Verdana" color="#000000">（2）WSASendTo()</font></p>
<p><font face="Verdana" color="#000000">（3）WSARecv()</font></p>
<p><font face="Verdana" color="#000000">（4）WSARecvFrom()</font></p>
<p><font face="Verdana" color="#000000">（5）WSAIoctl()</font></p>
<p><font face="Verdana" color="#000000">（6）AcceptEx()</font></p>
<p><font face="Verdana" color="#000000">（7）TransmitFile()</font></p>
<p><font face="Verdana" color="#000000">若随一个WSAOVERLAPPED结构一起调用这些函数，函数会立即返回，无论套接字是否设为锁定模式。它们依赖于WSAOVERLAPPED结构来返回一个I/O请求操作的结果。</font></p>
<p><font face="Verdana" color="#000000">比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型，WinSock的重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。因为它和这4种模型不同的是，使用重叠模型的应用程序通知缓冲区收发系统直接使用数据。也就是说，如果应用程序投递了一个10KB大小的缓冲区来接收数据，且数据已经到达套接字，则该数据将直接被拷贝到投递的缓冲区。而这4种模型中，数据到达并拷贝到单套接字接收缓冲区（Per Socket Buffer）中，此时应用程序会被系统通知可以读入的字节数。当应用程序调用接收函数之后，数据才从单套接字缓冲区拷贝到应用程序的缓冲区。这样就减少了一次从I/O缓冲区到应用程序缓冲区的拷贝，差别就在于此。</font></p>
<p><font face="Verdana" color="#000000">实际编程时，可以投递一个0字节缓冲区的WSARecv/WSASend操作，这样就没有用户缓冲区与I/O操作相关联，避免了用户缓冲区的锁定（过多的锁定可能导致非分页内存池耗尽，即WSAENOBUFS），应用程序绕开单套接字缓冲区而直接与TCP Stack进行数据交互，从而避免了内存拷贝。当然，只要投递了足够多的重叠发送/接收操作，就能避免额外的内存拷贝，这时将单套接字缓冲区设置为0并不能提升性能。因为应用程序的发送缓冲区将始终被锁定直到可以下传给TCP，所以停用套接字的发送缓冲区对性能的影响比停用接收缓冲区小。然而，如果接收缓冲区被设置为0，而又未投递重叠接收操作，则进来的数据都只能停留在TCP Stack中，而TCP驱动程序的缓冲区最多只能接收窗口大小。TCP缓冲区被定位在非分页内存池中，假如很多连接发数据过来，但我们根本没有投递接收操作，则将消耗大量的非分页内存池。非分页内存池是一种有限的资源，过多的锁定可能导致非分页内存池耗尽，即WSAENOBUFS。</font></p>
<p><font face="Verdana" color="#000000">在Windows NT和Windows 2000中，重叠I/O模型也允许应用程序以一种重叠方式实现对套接字连接的处理。具体的做法是在监听套接字上调用AcceptEx函数。AcceptEx是一个特殊的WinSock扩展函数，由mswsock.dll实现，使用时需包含Mswsock.h头文件，链接Mswsock.lib库文件。该函数最初的设计宗旨是在Windows NT与Windows 2000操作系统上使用Win 32的重叠I/O机制。但事实上，它也适用于WinSock 2中的重叠I/O。AcceptEx的定义如下：</font></p>
<p><font face="Verdana" color="#000000">// MSWSOCK.H</font></p>
<p><font face="Verdana" color="#000000">AcceptEx(</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN SOCKET sListenSocket,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN SOCKET sAcceptSocket,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN PVOID lpOutputBuffer,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN DWORD dwReceiveDataLength,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN DWORD dwLocalAddressLength,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN DWORD dwRemoteAddressLength,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OUT LPDWORD lpdwBytesReceived,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; IN LPOVERLAPPED lpOverlapped);</font></p>
<p><font face="Verdana" color="#000000">参数一sListenSocket参数指定的是一个监听套接字。</font></p>
<p><font face="Verdana" color="#000000">参数二sAcceptSocket参数指定的是另一个套接字，负责对进入连接请求的&#8220;接受&#8221;。 AcceptEx()函数和accept()函数的区别在于，我们必须提供接受的套接字，而不是让函数自动为我们创建。正是由于要提供套接字，所以要求我们事先调用socket()或WSASocket()函数创建一个套接字，以便通过sAcceptSocket参数，将其传递给AcceptEx()。</font></p>
<p><font face="Verdana" color="#000000">参数三lpOutputBuffer指定的是一个特殊的缓冲区，因为它要负责三种数据的接收：服务器的本地地址，客户机的远程地址，以及在新建连接上接收的第一个数据块。存储顺序是：接收到的数据块&#8594;本地地址&#8594;远程地址。</font></p>
<p><font face="Verdana" color="#000000">参数四dwReceiveDataLength以字节为单位，指定了在lpOutputBuffer缓冲区开头保留多大的空间，用于数据的接收。如这个参数设为0，那么只接受连接，不伴随接收数据。</font></p>
<p><font face="Verdana" color="#000000">参数五dwLocalAddressLength和参数六dwRemoteAddressLength也是以字节为单位，指定在lpOutputBuffer缓冲区中，保留多大的空间，在一个套接字被接受的时候，用于本地和远程地址信息的保存。要注意的是，和当前采用的传送协议允许的最大地址长度比较起来，这里指定的缓冲区大小至少应多出16字节。举个例子来说，假定正在使用的是TCP/IP协议，那么这里的大小应设为&#8220;SOCKADDR_IN结构的长度＋16字节&#8221;。</font></p>
<p><font face="Verdana" color="#000000">参数七lpdwBytesReceived参数用于返回接收到的实际数据量，以字节为单位。只有在操作以同步方式完成的前提下，才会设置这个参数。假如AcceptEx()函数返回ERROR_IO_PENDING，那么这个参数永远都不会设置，我们必须利用完成事件通知机制，获知实际读取的字节量。</font></p>
<p><font face="Verdana" color="#000000">最后一个参数是lpOverlapped，它对应的是一个OVERLAPPED结构，允许AcceptEx()以一种异步方式工作。如我们早先所述，只有在一个重叠I/O应用中，该函数才需要使用事件对象通知机制（hEvent字段），这是由于此时没有一个完成例程参数可供使用。</font></p>
<p><font face="Verdana" color="#000000">&nbsp;</font></p>
<p><font face="Verdana" color="#000000">二．获取重叠I/O操作完成结果</font></p>
<p><font face="Verdana" color="#000000">当异步I/O请求挂起后，最终要知道I/O操作是否完成。一个重叠I/O请求最终完成后，应用程序要负责读取重叠I/O操作的结果。对于读，直到I/O完成，接收缓冲器才有效（参考IRP缓冲区管理）。对于写，要知道写是否成功，有几种方法可以做到这点，最直接的方法是调用(WSA)GetOverlappedResult，其函数原型如下：</font></p>
<p><font face="Verdana" color="#000000">WINBASEAPI BOOL WINAPI </font></p>
<p><font face="Verdana" color="#000000">GetOverlappedResult(</font></p>
<p><font face="Verdana" color="#000000">HANDLE hFile,</font></p>
<p><font face="Verdana" color="#000000">LPOVERLAPPED lpOverlapped, </font></p>
<p><font face="Verdana" color="#000000">LPDWORD lpNumberOfBytesTransferred,</font></p>
<p><font face="Verdana" color="#000000">BOOL bWait);</font></p>
<p><font face="Verdana" color="#000000">BOOL WSAAPI WSAGetOverlappedResult(</font></p>
<p><font face="Verdana" color="#000000">SOCKET s,</font></p>
<p><font face="Verdana" color="#000000">LPWSAOVERLAPPED lpOverlapped,</font></p>
<p><font face="Verdana" color="#000000">LPDWORD lpcbTransfer,</font></p>
<p><font face="Verdana" color="#000000">BOOL fWait,</font></p>
<p><font face="Verdana" color="#000000">LPDWORD lpdwFlags);</font></p>
<p><font face="Verdana" color="#000000">参数一为的文件/套接字句柄。</font></p>
<p><font face="Verdana" color="#000000">参数二为参数一关联的(WSA) OVERLAPPED结构，在调用CreateFile()、WSASocket()或AcceptEx()时指定。</font></p>
<p><font face="Verdana" color="#000000">参数三指向字节计数指针，负责接收一次重叠发送或接收操作实际传输的字节数。</font></p>
<p><font face="Verdana" color="#000000">参数四是确定命令是否等待的标志。Wait参数用于决定函数是否应该等待一次重叠操作完成。若将Wait设为TRUE，那么直到操作完成函数才返回；若设为FALSE，而且操作仍然处于未完成状态，那么(WSA)GetOverlappedResult()函数会返回FALSE值。</font></p>
<p><font face="Verdana" color="#000000">如(WSA)GetOverlappedResult()函数调用成功，返回值就是TRUE。这意味着我们的重叠I/O操作已成功完成，而且由参数三BytesTransfered参数指向的值已进行了更新。若返回值是FALSE，那么可能是由下述任何一种原因造成的：</font></p>
<p><font face="Verdana" color="#000000">&#9632; 重叠I/O操作仍处在&#8220;待决&#8221;状态。</font></p>
<p><font face="Verdana" color="#000000">&#9632; 重叠操作已经完成，但含有错误。</font></p>
<p><font face="Verdana" color="#000000">&#9632; 重叠操作的完成状态不可判决，因为在提供给 WSAGetOverlappedResult函数的一个或多个参数中，存在着错误。</font></p>
<p><font face="Verdana" color="#000000">失败后，由BytesTransfered参数指向的值不会进行更新，而且我们的应用程序应调用(WSA)GetLastError()函数，检查到底是何种原因造成了调用失败以使用相应容错处理。如果错误码为SOCKET_ERROR/WSA_IO_INCOMPLETE(Overlapped I/O event is not in a signaled state)或SOCKET_ERROR/WSA_IO_PENDING(Overlapped I/O operation is in progress)，则表明I/O仍在进行。当然，这不是真正错误，任何其他错误码则真正表明一个实际错误。</font></p>
<p><font face="Verdana" color="#000000">下面介绍两种常用重叠I/O完成通知的方法。</font></p>
<p><font face="Verdana" color="#000000">1.使用事件通知</font></p>
<p><font face="Verdana" color="#000000">使用(WSA)GetOverlappedResult()是直截了当的，它吻合重叠I/O的概念。毕竟，如果要等待I/O，也许使用常规I/O命令更好。对于大多数程序，反复检查I/O是否完成，并非最佳。解决方案之一是使用(WSA)OVERLAPPED结构中的hEvent字段，使应用程序将一个事件对象句柄同一个文件/套接字关联起来。</font></p>
<p><font face="Verdana" color="#000000">当指定OVERLAPPED参数给ReadFile()/WriteFile()或WSARecv()/WSASend()后，可以再为(WSA)OVERLAPPED最后一个参数提供自定义的事件对象（通过(WSA)CreateEvent()创建）。</font></p>
<p><font face="Verdana" color="#000000">当I/O完成时，系统更改(WSA)OVERLAPPED结构对应的事件对象的传信状态，使其从&#8220;未传信&#8221;（unsignaled）变成&#8220;已传信&#8221;（signaled）。由于我们之前将事件对象分配给了(WSA)OVERLAPPED结构，所以只需简单地调用WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函数，从而判断出一个（一些）重叠I/O在什么时候完成。通过WaitForSingleObject/WaitForMultipleObjects或WSAWaitForMultipleEvents函数返回的索引可以知道这个重叠I/O完成事件是在哪个HANDLE（File或Socket）上发生的。</font></p>
<p><font face="Verdana" color="#000000">然后调用(WSA)GetOverlappedResult()函数，将发生事件的HANDLE（FILE或SOCKET）传给参数一，将这个HANDLE对应的(WSA)OVERLAPPED结构传给参数二，这样判断重叠调用到底是成功还是失败。如果返回FALSE值，则重叠操作已经完成但含有错误。或者重叠操作的完成状态不可判决，因为在提供给(WSA)GetOverlappedResult()函数的一个或多个参数中存在着错误。失败后，由BytesTransfered参数指向的值不会进行更新，应用程序应调用(WSA)GetLastError()函数，调查到底是何种原因造成了调用失败。</font></p>
<p><font face="Verdana" color="#000000">若(WSA)GetOverlappedResult()函数返回TRUE，则根据先前调用异步I/O函数时设置的缓冲区(ReadFile/WriteFile或WSARecv/WSASend的lpBuffer字段)和BytesTransfered,使用指针偏移定位就可以准确操作接受到的数据了。</font></p>
<p><font face="Verdana" color="#000000">利用事件对象来完成同步通知的方法比重复调用(WSA)GetOverlappedResult()浪费处理器时间的方案要高效得多。但WaitForMultipleObjects/WSAaitForMultipleEvent支持的事件对象个数的上限为MAXIMUM_WAIT_OBJECTS/WSA_MAXIMUM_WAIT_EVENTS=64！</font></p>
<p><font face="Verdana" color="#000000">2.使用完成例程</font></p>
<p><font face="Verdana" color="#000000">对于文件重叠I/O操作，等待I/O操作结束的另外方法是使用ReadFileEx()和WriteFileEx()。这些命令只用于重叠I/O，当为它们的最后一个参数lpCompletionRoutine传递了一个完成例程指针（回调函数地址）时，I/O操作结束时将调用此函数进行处理。</font></p>
<p><font face="Verdana" color="#000000">完成例程指针LPOVERLAPPED_COMPLETION_ROUTINE定义如下：</font></p>
<p><font face="Verdana" color="#000000">// WINBASE.H</font></p>
<p><font face="Verdana" color="#000000">typedef&nbsp; VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwErrorCode,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwNumberOfBytesTransfered,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPOVERLAPPED lpOverlapped );</font></p>
<p><font face="Verdana" color="#000000">相应在WinSock 2中，WSARecv()/WSASend()最后一个参数lpCompletionROUTINE是一个可选的指针，它指向一个完成例程。若指定此参数（自定义函数地址），在重叠请求完成后，将调用完成例程处理。完成例程本质上是一种APC（Asynchronous Procedure Calls）。</font></p>
<p><font face="Verdana" color="#000000">WinSock 2中完成例程指针LPWSAOVERLAPPED_COMPLETION_ROUTINE定义略有不同：</font></p>
<p><font face="Verdana" color="#000000">// WINSOCK2.H</font></p>
<p><font face="Verdana" color="#000000">typedef void (CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwError,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD cbTransferred,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPWSAOVERLAPPED lpOverlapped,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwFlags );</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp;&nbsp; 前三个参数同LPOVERLAPPED_COMPLETION_ROUTINE，参数四一般不用，置0。用完成例程完成一个重叠I/O请求之后，参数中会包含下述信息：</font></p>
<p><font face="Verdana" color="#000000">参数一dwError表明了一个重叠操作（由lpOverlapped指定）的完成状态是什么。</font></p>
<p><font face="Verdana" color="#000000">参数二BytesTransferred参数指定了在重叠操作实际传输的字节量是多大。</font></p>
<p><font face="Verdana" color="#000000">参数三lpOverlapped参数指定的是调用这个完成例程的异步I/O操作函数(ReadFileEx()/WriteFileEx()或WSARecv()/WSASend())的(WSA)OVERLAPPED结构参数。</font></p>
<p><font face="Verdana" color="#000000">提交带有完成例程的重叠I/O请求时，(WSA)OVERLAPPED结构的事件字段hEvent一般不再使用。使用一个含有完成例程指针参数的异步I/O函数发出一个重叠I/O请求之后，一旦重叠I/O操作完成，作为我们的调用线程，必须能够通知完成例程指针所指向的自定义函数开始执行，提供数据处理服务。这样一来，便要求将调用线程置于一种&#8220;可警告的等待状态&#8221;，在I/O操作完成后，能自动调用完成例程。WSAWaitForMultipleEvents()函数可用来将线程置于一种可警告的等待状态。这样做的代价是必须创建一个事件对象可用于WSAWaitForMultipleEvents()函数。假定应用程序只用完成例程对重叠请求进行处理，便不需要引入事件对象。作为一种变通方法，我们的应用程序可用Win32的SleepEx()函数将自己的线程置为一种可警告的等待状态。当然，亦可创建一个伪事件对象，不将它与任何东西关联在一起。假如调用线程经常处于繁忙状态，而且并不处于一种可警告的等待状态，那么完成例程根本不会被通知执行。</font></p>
<p><font face="Verdana" color="#000000">如前面所述，WSAWaitForMultipleEvents()通常会等待同(WSA)OVERLAPPED结构关联在一起的事件对象。该函数也可用于将我们的线程设置为一种可警告的等待状态，为已经完成的重叠I/O请求调用完成例程进行处理（前提是将fAlertable参数设为TRUE）。使用一个含有完成例程指针的异步I/O函数提交了重叠I/O请求之后， WSAWaitForMultipleEvents()的期望返回值是WAIT_IO_COMPLETION（One or more I/O completion routines are queued for execution），而不再是事件对象索引。从宏WAIT_IO_COMPLETION的注解可知，它的意思是有完成例程需要执行。SleepEx()函数的行为实际上和WSAWaitForMultipleEvents()差不多，只是它不需要任何事件对象。对SleepEx函数的定义如下：</font></p>
<p><font face="Verdana" color="#000000">WINBASEAPI DWORD WINAPI</font></p>
<p><font face="Verdana" color="#000000">SleepEx(</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp; DWORD dwMilliseconds,</font></p>
<p><font face="Verdana" color="#000000">&nbsp;&nbsp;&nbsp; BOOL bAlertable );</font></p>
<p><font face="Verdana" color="#000000">其中，dwMilliseconds参数定义了SleepEx()函数的等待时间，以毫秒为单位。假如将dwMilliseconds设为INFINITE，那么SleepEx()会无休止地等待下去。bAlertable参数规定了一个完成例程的执行方式，若将它设置为FALSE，则使用一个含有完成例程指针的异步I/O函数提交了重叠I/O请求后，I/O完成例程不会被通知执行，而且SleepEx()函数不会返回，除非超过由dwMilliseconds规定的时间；若将它设置为TRUE，则完成例程会被通知执行，同时SleepEx()函数返回WAIT_IO_COMPLETION。</font></p>
<p><font face="Verdana" color="#000000">在完成例程处理模型中，投递重叠I/O请求的同时注册完成例程，待I/O完成时由系统回调，并克服了事件通知模型的个数限制。利用完成例程处理重叠I/O的WinSock程序的编写步骤如下：</font></p>
<p><font face="Verdana" color="#000000">(1) 新建一个监听套接字，在指定端口上监听客户端的连接请求。</font></p>
<p><font face="Verdana" color="#000000">(2) 接受一个客户端的连接请求，并返回一个会话套接字负责与客户端通信。</font></p>
<p><font face="Verdana" color="#000000">(3) 为会话套接字关联一个WSAOVERLAPPED结构。</font></p>
<p><font face="Verdana" color="#000000">(4) 在套接字上投递一个异步WSARecv请求，方法是将WSAOVERLAPPED指定成为参数，同时提供一个完成例程。</font></p>
<p><font face="Verdana" color="#000000">(5) 在将fAlertable参数设为TRUE的前提下，调用WSAWaitForMultipleEvents，并等待一个重叠I/O请求完成。重叠请求完成后，完成例程会自动执行，而且WSAWaitForMultipleEvents会返回一个WAIT_IO_COMPLETION。在完成例程内，可随一个完成例程一道投递另一个重叠WSARecv请求。</font></p>
<p><font face="Verdana" color="#000000">(6) 检查WSAWaitForMultipleEvents是否返回WAIT_IO_COMPLETION。</font></p>
<p><font face="Verdana" color="#000000">(7) 重复步骤(5)和(6)。</font></p>
<p><font face="Verdana" color="#000000">当调用accept处理连接时，一般创建一个AcceptEvent伪事件，当有客户连接时，需要手动SetEvent(AcceptEvent)；当调用AcceptEx处理重叠的连接时，一般为ListenSocket创建一个ListenOverlapped结构，并为其指定一个伪事件，当有客户连接时，系统自动将其置信。这些伪事件的作用在于，当含有完成例程指针的异步I/O操作(如WSARecv)完成时，设置了fAlertable的WSAWaitForMultipleEvents返回WAIT_IO_COMPLETION，并调用完成例程指针指向的完成例程对数据进行处理。</font></p>
<p><font face="Verdana" color="#000000">重叠I/O模型的缺点是它一般要为每一个I/O请求都开一个线程，当同时有成千上万个请求发生时，系统处理线程上下文切换是非常耗时的。所以这也就引出了更为先进的完成端口模型IOCP，用线程池来解决这个问题。</font></p>
<p><font face="Verdana" color="#000000">&nbsp;</font></p>
<p><font face="Verdana" color="#000000">参考：</font></p>
<p><font face="Verdana" color="#000000">《Windows 2000 Systems Programming Black Book》&nbsp; Al Williams</font></p>
<p><font face="Verdana" color="#000000">《Network Programming for Microsoft Windows》&nbsp; Anthony Jones,Jim Ohlund</font></p>
<p><font face="Verdana" color="#000000"></font>&nbsp;</p>
<p><font face="Verdana" color="#000000">本文来自CSDN博客，转载请标明出处：http://blog.csdn.net/phunxm/archive/2009/12/27/5085915.aspx</font></p><img src ="http://www.cppblog.com/yehao/aggbug/146983.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-23 17:25 <a href="http://www.cppblog.com/yehao/articles/146983.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>套接字Select I/O模型</title><link>http://www.cppblog.com/yehao/articles/146682.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 18 May 2011 10:16:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146682.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146682.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146682.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146682.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146682.html</trackback:ping><description><![CDATA[<span style="color: #800080">转自<a href="http://www.cnblogs.com/yunboy4/archive/2009/08/04/1538915.html">http://www.cnblogs.com/yunboy4/archive/2009/08/04/1538915.html</a>&nbsp; 
<p>select模型</p>
<p>select（选择）模型是winsock中常见的I/O模型。之所以称其为&#8220;select模型&#8221;，是由于它的<br />&#8220;中心思想&#8221;是利用select函数，实现对I/O的管理！最初设计该模型时，主要面向的是某些使用<br />Unix操作系统的计算机，它们采用的是Berkeley套接字方案。select模型已经集成到Winsock1.1中。<br /><br />1</span><span style="color: #000000">.通过调用select函数可以确定一个或多个套接字的状态，判断套接字上是否有数据，或<br />者能否向一个套接字写入数据。&nbsp;<br />&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;select&nbsp;(</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nfds,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">忽略</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;FAR&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">readfds,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">等待可读性检查的套接字组的地址</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;FAR&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">writefds,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">等待可写性检查的套接字组的地址</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;FAR&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">exceptfds,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">等待错误检查的套接字组的地址</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">const</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;timeval&nbsp;FAR&nbsp;</span><span style="color: #000000">*</span><span style="color: #000000">timeout);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">struct&nbsp;timeval结构体地址，select()&nbsp;最多等待的时间<br /></span><span style="color: #008000">//</span><span style="color: #008000">返回值&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0--超时，SOCKET_ERROR--失败<br /></span><span style="color: #008000">//</span><span style="color: #008000">说明：此函数的作用是删除fd_set结构体中没有IO操作的套接字</span><span style="color: #008000"><br />/*</span><span style="color: #008000">注意：在3个套接字组中至少有一个不为NULL；在非空集合中必须包含一个套接字句柄。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果timeout设为(0,0)，select()&nbsp;会立即返回，允许应用程序对select操作进行&#8220;轮询&#8221;。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fd_set&nbsp;fdread;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(&amp;fdread);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(s,&nbsp;&amp;fdread);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select(0,&nbsp;&amp;fdread,&nbsp;NULL,&nbsp;NULL,&nbsp;NULL);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(FD_ISSET(s,&nbsp;&amp;fdread))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//套接字可读<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br /></span><span style="color: #008000">*/</span><span style="color: #000000"><br /><br /><br /></span><span style="color: #800080">2</span><span style="color: #000000">.管理套接字的结构体<br />定义：<br />typedef&nbsp;</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;fd_set&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_int&nbsp;&nbsp;&nbsp;fd_count;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">/*</span><span style="color: #008000">&nbsp;how&nbsp;many&nbsp;are&nbsp;SET?&nbsp;</span><span style="color: #008000">*/</span><span style="color: #000000">&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">元素的个数</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;&nbsp;fd_array[FD_SETSIZE];&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">/*</span><span style="color: #008000">&nbsp;an&nbsp;array&nbsp;of&nbsp;SOCKETs&nbsp;</span><span style="color: #008000">*/</span><span style="color: #000000"><br />}&nbsp;fd_set;<br /><br />对struct&nbsp;fd_set结构体操作的宏<br />FD_SETSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;容量，指定fd_array数组大小，默认为64，也可自己修改宏<br />FD_ZERO(</span><span style="color: #000000">*</span><span style="color: #0000ff">set</span><span style="color: #000000">)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;置空，使数组的元素值都为3435973836，元素个数为0.<br />FD_SET(s,&nbsp;</span><span style="color: #000000">*</span><span style="color: #0000ff">set</span><span style="color: #000000">)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;添加，向&nbsp;</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;fd_set结构体添加套接字s<br />FD_ISSET(s,&nbsp;</span><span style="color: #000000">*</span><span style="color: #0000ff">set</span><span style="color: #000000">)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;判断，判断s是否为&nbsp;</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;fd_set结构体中的一员<br />FD_CLR(s,&nbsp;</span><span style="color: #000000">*</span><span style="color: #0000ff">set</span><span style="color: #000000">)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;删除，从&nbsp;</span><span style="color: #0000ff">struct</span><span style="color: #000000">&nbsp;fd_set结构体中删除成员s&nbsp;&nbsp;&nbsp;&nbsp;<br /><br /></span><span style="color: #800080">3</span><span style="color: #000000">.用Select模型获取网络事件<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;FD_SET&nbsp;AllSockFd;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">装有所有的套接字</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(</span><span style="color: #000000">&amp;</span><span style="color: #000000">AllSockFd);<br />&nbsp;&nbsp;&nbsp;&nbsp;AllSockFd&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;ClientSockFd&nbsp;;<br />&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(ListenSock,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">AllSockFd);<br />&nbsp;&nbsp;&nbsp;&nbsp;FD_SET&nbsp;ReadSockFd;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">读集合</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;FD_SET&nbsp;WriteSockFd;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">写集合</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">while</span><span style="color: #000000">(</span><span style="color: #800080">1</span><span style="color: #000000">)<br />&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(</span><span style="color: #000000">&amp;</span><span style="color: #000000">ReadSockFd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_ZERO(</span><span style="color: #000000">&amp;</span><span style="color: #000000">WriteSockFd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ReadSockFd&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;AllSockFd;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WriteSockFd&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;AllSockFd;<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nRet&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;select(</span><span style="color: #800080">0</span><span style="color: #000000">,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">ReadSockFd,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">WriteSockFd,&nbsp;NULL,&nbsp;NULL);&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(SOCKET_ERROR&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;nRet)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">continue</span><span style="color: #000000">;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">有请求事件发生</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">&nbsp;(FD_ISSET(ListenSock,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">ReadSockFd))<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">接受请求</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;ClientSock;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u_short&nbsp;Port;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">bool</span><span style="color: #000000">&nbsp;nRe&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;(</span><span style="color: #000000">*</span><span style="color: #000000">(Pam.pListenSock)).Accept(</span><span style="color: #000000">&amp;</span><span style="color: #000000">ClientSock,&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">Port);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(nRe)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FD_SET(ClientSock,&nbsp;Pam.pClientSockFd);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">设置套接字发送缓冲区80K</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nBuf&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;SOCKET_BUFF;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nBufLen&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(nBuf);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;nRe&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;setsockopt(ClientSock,&nbsp;SOL_SOCKET,&nbsp;SO_SNDBUF,&nbsp;(</span><span style="color: #0000ff">char</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">nBuf,&nbsp;nBufLen);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(SOCKET_ERROR&nbsp;</span><span style="color: #000000">==</span><span style="color: #000000">&nbsp;nRe)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AfxMessageBox(</span><span style="color: #800000">"</span><span style="color: #800000">setsockopt&nbsp;error!</span><span style="color: #800000">"</span><span style="color: #000000">);&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">检查缓冲区是否设置成功</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nRe&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;getsockopt(ClientSock,&nbsp;SOL_SOCKET,&nbsp;SO_SNDBUF,&nbsp;(</span><span style="color: #0000ff">char</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">nBuf,&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">nBufLen);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(SOCKET_BUFF&nbsp;</span><span style="color: #000000">!=</span><span style="color: #000000">&nbsp;nBuf)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AfxMessageBox(</span><span style="color: #800000">"</span><span style="color: #800000">检查缓冲区:setsockopt&nbsp;error!</span><span style="color: #800000">"</span><span style="color: #000000">);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">else</span><span style="color: #000000"><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AfxMessageBox(</span><span style="color: #800000">"</span><span style="color: #800000">已连接客户端!</span><span style="color: #800000">"</span><span style="color: #000000">);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">判断是否可读或可写</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">for</span><span style="color: #000000">(u_int&nbsp;n&nbsp;</span><span style="color: #000000">=</span><span style="color: #000000">&nbsp;</span><span style="color: #800080">0</span><span style="color: #000000">;n&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">&nbsp;ClientSockFd.fd_count;n</span><span style="color: #000000">++</span><span style="color: #000000">)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(FD_ISSET(ClientSockFd.fd_array[n],&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">ReadSockFd))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">发现可读</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">接收数据<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">如果失败&nbsp;删除此元素&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">if</span><span style="color: #000000">(FD_ISSET(ClientSockFd.fd_array[n],&nbsp;</span><span style="color: #000000">&amp;</span><span style="color: #000000">WriteSockFd))&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">发现可写&nbsp;&nbsp;</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">发送缓冲区未满可以发送<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #008000">//</span><span style="color: #008000">如果失败&nbsp;删除此元素</span><span style="color: #008000"><br /></span><span style="color: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />&nbsp;&nbsp;&nbsp;&nbsp;}</span></p><img src ="http://www.cppblog.com/yehao/aggbug/146682.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-18 18:16 <a href="http://www.cppblog.com/yehao/articles/146682.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>I/O完成端口简单例子</title><link>http://www.cppblog.com/yehao/articles/146661.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 18 May 2011 07:11:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146661.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146661.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146661.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146661.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146661.html</trackback:ping><description><![CDATA[<div class="cnblogs_code"><pre><div><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #000000">转自<a href="http://www.cnblogs.com/pen-ink/articles/1834088.html">http://www.cnblogs.com/pen-ink/articles/1834088.html</a><br /><br /><br />HANDLE WINAPI CreateIoCompletionPort(<br />  __in          HANDLE FileHandle,<br />  __in          HANDLE ExistingCompletionPort,<br />  __in          ULONG_PTR CompletionKey,<br />  __in          DWORD NumberOfConcurrentThreads<br />);<br /></span><span style="color: #008000">/*</span><span style="color: #008000"><br />这个函数完成两个不同的任务：1、创建一个完成端口对象；2、将一个或者多个文件句柄关联到I/O完成端口对象。可分成两个小函数对CreateIoCompletionPort调用进行抽象。<br /></span><span style="color: #008000">*/</span><span style="color: #000000"><br /></span><span style="color: #008000">//</span><span style="color: #008000">1、创建I/O完成端口</span><span style="color: #008000"><br /></span><span style="color: #000000">HANDLE CreateNewCompletionPort(DWORD dwNumberOfConcurrentThreads)<br />{<br />    </span><span style="color: #0000ff">return</span><span style="color: #000000"> (CreateNewCompletionPort(INVALID_HANDLE_VALUE, NULL, </span><span style="color: #800080">0</span><span style="color: #000000">, dwNumberOfConcurrentThreads));<br />}<br /></span><span style="color: #008000">//</span><span style="color: #008000">2、将设备与I/O完成端口关联起来</span><span style="color: #008000"><br /></span><span style="color: #000000">BOOL AssociateDeviceWithCompletionPort(HANDLE hCompletionPort, HANDLE hDevice, DWORD dwCompletionKey)<br />{<br />    HANDLE h </span><span style="color: #000000">=</span><span style="color: #000000"> CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, </span><span style="color: #800080">0</span><span style="color: #000000">);<br />    </span><span style="color: #0000ff">return</span><span style="color: #000000"> (h </span><span style="color: #000000">==</span><span style="color: #000000"> hCompletionPort);<br />}<br /></span></div></pre></div>
<p>﻿</p>
<div class="cnblogs_code"><pre><div><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><span style="color: #808080">////////////////////////////////////////////////</span><span style="color: #008000">/</span><span style="color: #808080"><br /></span><span style="color: #008000">//</span><span style="color: #008000"> IOCPDemo.cpp文件            调试通过</span><span style="color: #008000"><br /></span><span style="color: #000000"><br />#include </span><span style="color: #800000">"</span><span style="color: #800000">WSAInit.h</span><span style="color: #800000">"</span><span style="color: #000000"><br />#include </span><span style="color: #000000">&lt;</span><span style="color: #000000">stdio.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br />#include </span><span style="color: #000000">&lt;</span><span style="color: #000000">windows.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><br /></span><span style="color: #008000">//</span><span style="color: #008000"> 初始化Winsock库</span><span style="color: #008000"><br /></span><span style="color: #000000">CWSAInit theSock;<br /><br /></span><span style="color: #0000ff">#define</span><span style="color: #000000"> BUFFER_SIZE 1024</span><span style="color: #000000"><br /></span><span style="color: #0000ff">#define</span><span style="color: #000000"> OP_READ   1</span><span style="color: #000000"><br /></span><span style="color: #0000ff">#define</span><span style="color: #000000"> OP_WRITE  2</span><span style="color: #000000"><br /></span><span style="color: #0000ff">#define</span><span style="color: #000000"> OP_ACCEPT 3</span><span style="color: #000000"><br /><br />typedef </span><span style="color: #0000ff">struct</span><span style="color: #000000"> _PER_HANDLE_DATA        </span><span style="color: #008000">//</span><span style="color: #008000"> per-handle数据</span><span style="color: #008000"><br /></span><span style="color: #000000">{<br />    SOCKET s;            </span><span style="color: #008000">//</span><span style="color: #008000"> 对应的套节字句柄</span><span style="color: #008000"><br /></span><span style="color: #000000">    sockaddr_in addr;    </span><span style="color: #008000">//</span><span style="color: #008000"> 客户方地址</span><span style="color: #008000"><br /></span><span style="color: #000000">    </span><span style="color: #0000ff">char</span><span style="color: #000000"> buf[BUFFER_SIZE];    </span><span style="color: #008000">//</span><span style="color: #008000"> 数据缓冲区</span><span style="color: #008000"><br /></span><span style="color: #000000">    </span><span style="color: #0000ff">int</span><span style="color: #000000"> nOperationType;        </span><span style="color: #008000">//</span><span style="color: #008000"> 操作类型</span><span style="color: #008000"><br /></span><span style="color: #000000">} PER_HANDLE_DATA, </span><span style="color: #000000">*</span><span style="color: #000000">PPER_HANDLE_DATA;<br /><br />DWORD WINAPI ServerThread(LPVOID lpParam)<br />{<br />    </span><span style="color: #008000">//</span><span style="color: #008000"> 得到完成端口对象句柄</span><span style="color: #008000"><br /></span><span style="color: #000000">    HANDLE hCompletion </span><span style="color: #000000">=</span><span style="color: #000000"> (HANDLE)lpParam;<br /><br />    DWORD dwTrans;<br />    PPER_HANDLE_DATA pPerHandle;<br />    OVERLAPPED </span><span style="color: #000000">*</span><span style="color: #000000">pOverlapped;<br />    </span><span style="color: #0000ff">while</span><span style="color: #000000">(TRUE)<br />    {<br />        </span><span style="color: #008000">//</span><span style="color: #008000"> 在关联到此完成端口的所有套节字上等待I/O完成</span><span style="color: #008000"><br /></span><span style="color: #000000">        BOOL bOK </span><span style="color: #000000">=</span><span style="color: #000000"> ::GetQueuedCompletionStatus(hCompletion, <br />            </span><span style="color: #000000">&amp;</span><span style="color: #000000">dwTrans, (PULONG_PTR)</span><span style="color: #000000">&amp;</span><span style="color: #000000">pPerHandle, </span><span style="color: #000000">&amp;</span><span style="color: #000000">pOverlapped, WSA_INFINITE);<br />        </span><span style="color: #0000ff">if</span><span style="color: #000000">(</span><span style="color: #000000">!</span><span style="color: #000000">bOK)                        </span><span style="color: #008000">//</span><span style="color: #008000"> 在此套节字上有错误发生</span><span style="color: #008000"><br /></span><span style="color: #000000">        {<br />            ::closesocket(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s);<br />            ::GlobalFree(pPerHandle);<br />            ::GlobalFree(pOverlapped);<br />            </span><span style="color: #0000ff">continue</span><span style="color: #000000">;<br />        }<br /><br />        </span><span style="color: #0000ff">if</span><span style="color: #000000">(dwTrans </span><span style="color: #000000">==</span><span style="color: #000000"> </span><span style="color: #800080">0</span><span style="color: #000000"> </span><span style="color: #000000">&amp;&amp;</span><span style="color: #000000">                </span><span style="color: #008000">//</span><span style="color: #008000"> 套节字被对方关闭</span><span style="color: #008000"><br /></span><span style="color: #000000">            (pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">nOperationType </span><span style="color: #000000">==</span><span style="color: #000000"> OP_READ </span><span style="color: #000000">||</span><span style="color: #000000"> pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">nOperationType </span><span style="color: #000000">==</span><span style="color: #000000"> OP_WRITE))    <br /><br />        {<br />            ::closesocket(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s);<br />            ::GlobalFree(pPerHandle);<br />            ::GlobalFree(pOverlapped);<br />            </span><span style="color: #0000ff">continue</span><span style="color: #000000">;<br />        }<br /><br />        </span><span style="color: #0000ff">switch</span><span style="color: #000000">(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">nOperationType)    </span><span style="color: #008000">//</span><span style="color: #008000"> 通过per-I/O数据中的nOperationType域查看什么I/O请求完成了</span><span style="color: #008000"><br /></span><span style="color: #000000">        {<br />        </span><span style="color: #0000ff">case</span><span style="color: #000000"> OP_READ:    </span><span style="color: #008000">//</span><span style="color: #008000"> 完成一个接收请求</span><span style="color: #008000"><br /></span><span style="color: #000000">            {<br />                pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">buf[dwTrans] </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #800000">'</span><span style="color: #800000">\0</span><span style="color: #800000">'</span><span style="color: #000000">;<br />                printf(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000"> buf);<br /><br />                </span><span style="color: #008000">//</span><span style="color: #008000"> 继续投递接收I/O请求</span><span style="color: #008000"><br /></span><span style="color: #000000">                WSABUF buf;<br />                buf.buf </span><span style="color: #000000">=</span><span style="color: #000000"> pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">buf ;<br />                buf.len </span><span style="color: #000000">=</span><span style="color: #000000"> BUFFER_SIZE;<br />                pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">nOperationType </span><span style="color: #000000">=</span><span style="color: #000000"> OP_READ;<br />    <br />                DWORD nFlags </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #800080">0</span><span style="color: #000000">;<br />                ::WSARecv(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s, </span><span style="color: #000000">&amp;</span><span style="color: #000000">buf, </span><span style="color: #800080">1</span><span style="color: #000000">, </span><span style="color: #000000">&amp;</span><span style="color: #000000">dwTrans, </span><span style="color: #000000">&amp;</span><span style="color: #000000">nFlags, pOverlapped, NULL);<br />            }<br />            </span><span style="color: #0000ff">break</span><span style="color: #000000">;<br />        </span><span style="color: #0000ff">case</span><span style="color: #000000"> OP_WRITE: </span><span style="color: #008000">//</span><span style="color: #008000"> 本例中没有投递这些类型的I/O请求</span><span style="color: #008000"><br /></span><span style="color: #000000">        </span><span style="color: #0000ff">case</span><span style="color: #000000"> OP_ACCEPT:<br />            </span><span style="color: #0000ff">break</span><span style="color: #000000">;<br />        }<br />    }<br />    </span><span style="color: #0000ff">return</span><span style="color: #000000"> </span><span style="color: #800080">0</span><span style="color: #000000">;<br />}<br /><br /><br /></span><span style="color: #0000ff">void</span><span style="color: #000000"> main()<br />{<br />    </span><span style="color: #0000ff">int</span><span style="color: #000000"> nPort </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #800080">4567</span><span style="color: #000000">;<br />    </span><span style="color: #008000">//</span><span style="color: #008000"> 创建完成端口对象，创建工作线程处理完成端口对象中事件</span><span style="color: #008000"><br /></span><span style="color: #000000">    HANDLE hCompletion </span><span style="color: #000000">=</span><span style="color: #000000"> ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, </span><span style="color: #800080">0</span><span style="color: #000000">, </span><span style="color: #800080">0</span><span style="color: #000000">, </span><span style="color: #800080">0</span><span style="color: #000000">);<br />    ::CreateThread(NULL, </span><span style="color: #800080">0</span><span style="color: #000000">, ServerThread, (LPVOID)hCompletion, </span><span style="color: #800080">0</span><span style="color: #000000">, </span><span style="color: #800080">0</span><span style="color: #000000">);<br /><br />    </span><span style="color: #008000">//</span><span style="color: #008000"> 创建监听套节字，绑定到本地地址，开始监听</span><span style="color: #008000"><br /></span><span style="color: #000000">    SOCKET sListen </span><span style="color: #000000">=</span><span style="color: #000000"> ::socket(AF_INET, SOCK_STREAM, </span><span style="color: #800080">0</span><span style="color: #000000">);<br />    SOCKADDR_IN si;<br />    si.sin_family </span><span style="color: #000000">=</span><span style="color: #000000"> AF_INET;<br />    si.sin_port </span><span style="color: #000000">=</span><span style="color: #000000"> ::ntohs(nPort);<br />    si.sin_addr.S_un.S_addr </span><span style="color: #000000">=</span><span style="color: #000000"> INADDR_ANY;<br />    ::bind(sListen, (sockaddr</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">si, </span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(si));<br />    ::listen(sListen, </span><span style="color: #800080">5</span><span style="color: #000000">);<br /><br />    </span><span style="color: #008000">//</span><span style="color: #008000"> 循环处理到来的连接</span><span style="color: #008000"><br /></span><span style="color: #000000">    </span><span style="color: #0000ff">while</span><span style="color: #000000">(TRUE)<br />    {<br />        </span><span style="color: #008000">//</span><span style="color: #008000"> 等待接受未决的连接请求</span><span style="color: #008000"><br /></span><span style="color: #000000">        SOCKADDR_IN saRemote;<br />        </span><span style="color: #0000ff">int</span><span style="color: #000000"> nRemoteLen </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(saRemote);<br />        SOCKET sNew </span><span style="color: #000000">=</span><span style="color: #000000"> ::accept(sListen, (sockaddr</span><span style="color: #000000">*</span><span style="color: #000000">)</span><span style="color: #000000">&amp;</span><span style="color: #000000">saRemote, </span><span style="color: #000000">&amp;</span><span style="color: #000000">nRemoteLen);<br />        </span><span style="color: #008000">//</span><span style="color: #008000"> 接受到新连接之后，为它创建一个per-handle数据，并将它们关联到完成端口对象。</span><span style="color: #008000"><br /></span><span style="color: #000000">        PPER_HANDLE_DATA pPerHandle </span><span style="color: #000000">=</span><span style="color: #000000"> <br />            (PPER_HANDLE_DATA)::GlobalAlloc(GPTR, </span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(PER_HANDLE_DATA));<br />        pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s </span><span style="color: #000000">=</span><span style="color: #000000"> sNew;<br />        memcpy(</span><span style="color: #000000">&amp;</span><span style="color: #000000">pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">addr, </span><span style="color: #000000">&amp;</span><span style="color: #000000">saRemote, nRemoteLen);<br />        pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">nOperationType </span><span style="color: #000000">=</span><span style="color: #000000"> OP_READ;<br />        ::CreateIoCompletionPort((HANDLE)pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s, hCompletion, (ULONG_PTR)pPerHandle, </span><span style="color: #800080">0</span><span style="color: #000000">);<br />        </span><span style="color: #008000">//</span><span style="color: #008000"> 投递一个接收请求</span><span style="color: #008000"><br /></span><span style="color: #000000">        OVERLAPPED </span><span style="color: #000000">*</span><span style="color: #000000">pol </span><span style="color: #000000">=</span><span style="color: #000000"> (OVERLAPPED </span><span style="color: #000000">*</span><span style="color: #000000">)::GlobalAlloc(GPTR, </span><span style="color: #0000ff">sizeof</span><span style="color: #000000">(OVERLAPPED));<br />        WSABUF buf;<br />        buf.buf </span><span style="color: #000000">=</span><span style="color: #000000"> pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">buf;<br />        buf.len </span><span style="color: #000000">=</span><span style="color: #000000"> BUFFER_SIZE;    <br />        DWORD dwRecv;<br />        DWORD dwFlags </span><span style="color: #000000">=</span><span style="color: #000000"> </span><span style="color: #800080">0</span><span style="color: #000000">;<br />        ::WSARecv(pPerHandle</span><span style="color: #000000">-&gt;</span><span style="color: #000000">s, </span><span style="color: #000000">&amp;</span><span style="color: #000000">buf, </span><span style="color: #800080">1</span><span style="color: #000000">, </span><span style="color: #000000">&amp;</span><span style="color: #000000">dwRecv, </span><span style="color: #000000">&amp;</span><span style="color: #000000">dwFlags, pol, NULL);<br />    }<br />}<br /></span></div></pre></div><img src ="http://www.cppblog.com/yehao/aggbug/146661.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-18 15:11 <a href="http://www.cppblog.com/yehao/articles/146661.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows I/O完成端口</title><link>http://www.cppblog.com/yehao/articles/146659.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 18 May 2011 06:52:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146659.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146659.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146659.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146659.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146659.html</trackback:ping><description><![CDATA[<p><strong>转自<a href="http://ffwmxr.blog.163.com/blog/static/66372722200981761742868/">http://ffwmxr.blog.163.com/blog/static/66372722200981761742868/</a><br /><br />WINDOWS完成端口编程<br /></strong>1、基本概念<br />2、WINDOWS完成端口的特点<br />3、完成端口（Completion Ports ）相关数据结构和创建<br />4、完成端口线程的工作原理<br />5、Windows完成端口的实例代码</p>
<p><strong>WINDOWS完成端口编程<br /></strong>&nbsp;&nbsp; 摘要：开发网络程序从来都不是一件容易的事情，尽管只需要遵守很少的一些规则：创建socket，发起连接，接受连接，发送和接收数据，等等。真正的困难在于：让你的程序可以适应从单单一个连接到几千个连接乃至于上万个连接。利用Windows完成端口进行重叠I/O的技术，可以很方便地在Windows平台上开发出支持大量连接的网络服务程序。本文介绍在Windows平台上使用完成端口模型开发的基本原理，同时给出实际的例子。本文主要关注C/S结构的服务器端程序，因为一般来说，开发一个大容量、具有可扩展性的winsock程序就是指服务程序。<br /><strong><br />1、基本概念<br /></strong>&nbsp;&nbsp;&nbsp;设备---指windows操作系统上允许通信的任何东西，比如文件、目录、串行口、并行口、邮件槽、命名管道、无名管道、套接字、控制台、逻辑磁盘、物理磁盘等。<font color="#3366ff">绝大多数与设备打交道的函数都是CreateFile/ReadFile/WriteFile等，</font><font color="#3366ff">所以我们不能看到**File函数就只想到文件设备</font>。</p>
<p>&nbsp;&nbsp;&nbsp;与设备通信有两种方式，同步方式和异步方式：<font color="#3366ff">同步方式</font><font color="#000000">下</font>，当调用ReadFile这类函数时，函数会等待系统执行完所要求的工作，然后才返回；<font color="#3366ff">异步方式</font><font color="#000000">下</font>，ReadFile这类函数会直接返回，系统自己去完成对设备的操作，然后以某种方式通知完成操作。</p>
<p>&nbsp;&nbsp; <font color="#3366ff">重叠I/O</font>----顾名思义，就是当你调用了某个函数（比如ReadFile）就立刻返回接着做自己的其他动作的时候，系统同时也在对I/0设备进行你所请求的操作，在这段时间内你的程序和系统的内部动作是重叠的，因此有更好的性能。所以，重叠I/O是在异步方式下使用I/O设备的。<font color="#3366ff">重叠I/O需要使用的一个非常重要的数据结构：OVERLAPPED</font>。<br /><strong><br />2、WINDOWS完成端口的特点<br /></strong>&nbsp;&nbsp; Win32重叠I/O(Overlapped I/O)机制允许发起一个操作，并在操作完成之后接收信息。对于那种需要很长时间才能完成的操作来说，重叠IO机制尤其有用，因为发起重叠操作的线程在重叠请求发出后就可以自由地做别的事情了。在WinNT和Win2000上，提供的真正可扩展的I/O模型就是使用完成端口（Completion Port）的重叠I/O。<font color="#000000">完成端口---是一种WINDOWS内核对象</font>。<font color="#000000">完成端口用于异步方式的重叠I/0情况下，当然重叠I/O不一定非得使用完成端口不可，同样设备内核对象、事件对象、告警I/0等也可使用。</font>但是<font color="#3366ff">完成端口内部提供了线程池的管理，可以避免反复创建线程的开销，同时可以根据CPU的个数灵活地决定线程个数，而且可以减少线程调度的次数从而提高性能</font>。其实类似于WSAAsyncSelect和select函数的机制更容易兼容Unix，但是难以实现我们想要的&#8220;扩展性&#8221;。而且windows完成端口机制在操作系统的内部已经作了优化，从而具备了更高的效率。所以，我们选择完成端口开始我们的服务器程序开发。<br />&nbsp;&nbsp; 1）发起操作不一定完成：系统会在完成的时候通知你，通过用户在完成端口上的等待，处理操作的结果。所以要有检查完成端口和取操作结果的线程。在完成端口上守候的线程系统有优化，除非在执行的线程发生阻塞，不会有新的线程被激活，以此来减少线程切换造成的性能代价。所以如果程序中没有太多的阻塞操作，就没有必要启动太多的线程，<font color="#000000">使用CPU数量的两倍，一般这么多线程就够了</font>。<br />&nbsp;&nbsp; 2）操作与相关数据的绑定方式：在提交数据的时候用户对数据打上相应的标记，记录操作的类型，在用户处理操作结果的时候，通过检查自己打的标记和系统的操作结果进行相应的处理。 <br />&nbsp;&nbsp; 3）操作返回的方式：一般操作完成后要通知程序进行后续处理。但写操作可以不通知用户，此时如果用户写操作不能马上完成，写操作的相关数据会被暂存到非交换缓冲区中，在操作完成的时候，系统会自动释放缓冲区，此时发起完写操作，使用的内存就可以释放了。但如果占用非交换缓冲太多会使系统停止响应。<br /><strong><br />3、完成端口（Completion Ports ）相关数据结构和创建<br /></strong>&nbsp;&nbsp;&nbsp; 其实可以把完成端口看成系统维护的一个队列，操作系统把重叠IO操作完成的事件通知放到该队列里，由于是暴露 &#8220;操作完成&#8221;的事件通知，所以命名为&#8220;完成端口&#8221;（Completion Ports）。一个socket被创建后，就可以在任何时刻和一个完成端口联系起来。</p>
<p><font color="#3366ff">OVERLAPPED数据结构</font><br />typedef struct _OVERLAPPED { <br />&nbsp;&nbsp;&nbsp; ULONG_PTR Internal;&nbsp; //被系统内部赋值，用来表示系统状态 <br />&nbsp;&nbsp;&nbsp; ULONG_PTR InternalHigh;&nbsp; //被系统内部赋值，表示传输的字节数 <br />&nbsp;&nbsp;&nbsp; union { <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct { <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD Offset;&nbsp; //与OffsetHigh合成一个64位的整数，用来表示从文件头部的多少字节开始操作&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD OffsetHigh;&nbsp; //如果不是对文件I/O来操作，则Offset必须设定为0&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PVOID Pointer; <br />&nbsp;&nbsp;&nbsp; };&nbsp;<br />&nbsp;&nbsp; HANDLE hEvent;&nbsp; //如果不使用，就务必设为0；否则请赋一个有效的Event句柄 <br />} OVERLAPPED, *LPOVERLAPPED; <br /><br />下面是<font color="#000000">异步方式使用ReadFile的一个例子</font> <br />OVERLAPPED Overlapped; <br />Overlapped.Offset=345; <br />Overlapped.OffsetHigh=0; <br />Overlapped.hEvent=0; <br />//假定其他参数都已经被初始化 <br />ReadFile(hFile,buffer,sizeof(buffer),&amp;dwNumBytesRead,&amp;Overlapped); <br />这样就完成了异步方式读文件的操作，然后ReadFile函数返回，由操作系统做自己的事情。</p>
<p>下面介绍几个与OVERLAPPED结构相关的函数。</p>
<p><font color="#3366ff">等待重叠I/0操作完成的函数</font><br />BOOL <font color="#3366ff">GetOverlappedResult</font> (<br />HANDLE hFile,<br />LPOVERLAPPED lpOverlapped, //接受返回的重叠I/0结构<br />LPDWORD lpcbTransfer, //成功传输了多少字节数<br />BOOL fWait&nbsp; //TRUE只有当操作完成才返回，FALSE直接返回，如果操作没有完成，<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //通过用GetLastError( )函数会返回ERROR_IO_INCOMPLETE <br />);</p>
<p><font color="#3366ff"><font color="#000000">而</font><font color="#3366ff">宏</font>HasOverlappedIoCompleted</font>可以帮助我们测试重叠I/0操作是否完成，该宏对OVERLAPPED结构的Internal成员进行了测试，查看是否等于STATUS_PENDING值。</p>
<p>&nbsp;&nbsp; 一般来说，<font color="#000000">一个应用程序可以创建多个工作线程来处理完成端口上的通知事件。</font>工作线程的数量依赖于程序的具体需要。但是在理想的情况下，应该对应一个CPU 创建一个线程。因为在完成端口理想模型中，每个线程都可以从系统获得一个&#8220;原子&#8221;性的时间片，轮番运行并检查完成端口，线程的切换是额外的开销。但在实际开发的时候，还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行堵塞操作，系统则将其挂起，让别的线程获得运行时间。因此，如果有这样的情况，可以多创建几个线程来尽量利用时间。</p>
<p><font color="#3366ff">创建完成端口的函数</font><font color="#000000"><br />完</font>成端口是一个内核对象，使用时它总是要和至少一个有效的设备句柄相关联，完成端口是一个复杂的内核对象，创建它的函数是：<br />HANDLE <font color="#3366ff">CreateIoCompletionPort</font>( <br />&nbsp;&nbsp;&nbsp; IN HANDLE FileHandle, <br />&nbsp;&nbsp;&nbsp; IN HANDLE ExistingCompletionPort, <br />&nbsp;&nbsp;&nbsp; IN ULONG_PTR CompletionKey, <br />&nbsp;&nbsp;&nbsp; IN DWORD NumberOfConcurrentThreads <br />&nbsp;&nbsp;&nbsp; ); </p>
<p><font color="#339966">通常创建工作分两步：<br /></font>第一步，创建一个新的完成端口内核对象，可以使用下面的函数：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE CreateNewCompletionPort(DWORD dwNumberOfThreads) <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp; &nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,NULL,dwNumberOfThreads); <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br />第二步，将刚创建的完成端口和一个有效的设备句柄关联起来，可以使用下面的函数：<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool AssicoateDeviceWithCompletionPort(HANDLE hCompPort,HANDLE hDevice,DWORD dwCompKey) <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br />&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; HANDLE h=CreateIoCompletionPort(hDevice,hCompPort,dwCompKey,0); <br />&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; return h==hCompPort; <br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }; <br /><font color="#339966">说明如下：</font> <br />1）CreateIoCompletionPort函数也可以一次性的既创建完成端口对象，又关联到一个有效的设备句柄。<br />2）<font color="#3366ff">CompletionKey是一个可以自己定义的参数，我们可以把一个结构的地址赋给它，然后在合适的时候取出来使用</font>，最好要保证结构里面的内存不是分配在栈上，除非你有十分的把握内存会保留到你要使用的那一刻。<br />3）NumberOfConcurrentThreads用来指定要允许同时运行的的线程的最大个数，通常我们指定为0，这样系统会根据CPU的个数来自动确定。<br />4）创建和关联的动作完成后，系统会将完成端口关联的设备句柄、完成键作为一条纪录加入到这个完成端口的设备列表中。如果你有多个完成端口，就会有多个对应的设备列表。如果设备句柄被关闭，则表中该纪录会被自动删除。<br /><strong><br />4、完成端口线程的工作原理</strong></p>
<p><strong>1）完成端口管理线程池<br /></strong>&nbsp;&nbsp; 完成端口可以帮助我们管理线程池，但是线程池中的线程需要我们自己使用_beginthreadex来创建，<font color="#000000">凭什么通知完成端口管理我们的新线程呢？</font>答案在函数GetQueuedCompletionStatus。该函数原型： <br />BOOL <font color="#3366ff">GetQueuedCompletionStatus</font>( <br />&nbsp;&nbsp;&nbsp; IN HANDLE CompletionPort, <br />&nbsp;&nbsp;&nbsp; OUT LPDWORD lpNumberOfBytesTransferred, <br />&nbsp;&nbsp;&nbsp; OUT PULONG_PTR lpCompletionKey, <br />&nbsp;&nbsp;&nbsp; OUT LPOVERLAPPED *lpOverlapped, <br />&nbsp;&nbsp;&nbsp; IN DWORD dwMilliseconds <br />);&nbsp;</p>
<p>&nbsp;&nbsp; 这个函数试图从指定的完成端口的I/0完成队列中提取纪录。只有当重叠I/O动作完成的时候，完成队列中才有纪录。<font color="#3366ff">凡是调用这个函数的线程将会被放入到完成端口的等待线程队列中，因此完成端口就可以在自己的线程池中帮助我们维护这个线程</font>。完成端口的I/0完成队列中存放了当重叠I/0完成的结果---- 一条纪录，该纪录拥有四个字段，前三项就对应GetQueuedCompletionStatus函数的2、3、4参数，最后一个字段是错误信息dwError。<font color="#3366ff"><font color="#000000">我们</font>也可以通过调用PostQueudCompletionStatus模拟完成一个重叠I/0操作</font>。&nbsp;</p>
<p>&nbsp;&nbsp; <font color="#3366ff">当I/0完成队列中出现了纪录，完成端口将会检查等待线程队列</font><font color="#000000">，该队列中的线程都是通过调用GetQueuedCompletionStatus函数使自己加入队列的。</font>等待线程队列很简单，只是保存了这些线程的ID。<font color="#3366ff">完成端口按照后进先出的原则将一个线程队列的ID放入到释放线程列表中，同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态中变为可调度状态等待CPU的调度</font>。所以我们的线程要想成为完成端口管理的线程，就必须要调用GetQueuedCompletionStatus函数。出于性能的优化，实际上完成端口还维护了一个暂停线程列表，具体细节可以参考《Windows高级编程指南》，我们现在知道的知识，已经足够了。</p>
<p><strong>2）线程间数据传递<br /></strong>&nbsp;&nbsp; 完成端口线程间传递数据最常用的办法是在_beginthreadex函数中将参数传递给线程函数，或者使用全局变量。但<font color="#3366ff">完成端口也有自己的传递数据的方法，答案就在于CompletionKey和OVERLAPPED参数</font>。<br />CompletionKey被保存在完成端口的设备表中，是和设备句柄一一对应的，我们可以将与设备句柄相关的数据保存到CompletionKey中，或者将CompletionKey表示为结构指针，这样就可以传递更加丰富的内容。这些内容只能在一开始关联完成端口和设备句柄的时候做，因此不能在以后动态改变。</p>
<p>&nbsp;&nbsp; <font color="#3366ff">OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传递给完成端口的</font>。我们可以看到，如果我们不是对文件设备做操作，该结构的成员变量就对我们几乎毫无作用。我们需要附加信息，可以创建自己的结构，然后将OVERLAPPED结构变量作为我们结构变量的第一个成员，然后传递第一个成员变量的地址给ReadFile这样的函数。因为类型匹配，当然可以通过编译。当GetQueuedCompletionStatus函数返回时，我们可以获取到第一个成员变量的地址，然后一个简单的强制转换，我们就可以把它当作完整的自定义结构的指针使用，这样就可以传递很多附加的数据了。太好了！只有一点要注意，如果跨线程传递，请注意将数据分配到堆上，并且接收端应该将数据用完后释放。我们通常需要将ReadFile这样的异步函数的所需要的缓冲区放到我们自定义的结构中，这样当GetQueuedCompletionStatus被返回时，我们的自定义结构的缓冲区变量中就存放了I/0操作的数据。<font color="#3366ff">CompletionKey和OVERLAPPED参数，都可以通过GetQueuedCompletionStatus函数获得</font>。</p>
<p><strong>3）线程的安全退出<br /></strong>&nbsp;&nbsp;&nbsp;很多线程为了不止一次地执行异步数据处理，需要使用如下语句<br />while (true)<br />{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ......<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <font color="#000000">GetQueuedCompletionStatus</font>(...);&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;......<br />}<br />那么<font color="#3366ff">线程如何退出</font>呢，答案就在于上面曾提到过的<font color="#3366ff">PostQueudCompletionStatus</font>函数，我们可以向它发送一个自定义的包含了OVERLAPPED成员变量的结构地址，里面含一个状态变量，当状态变量为退出标志时，线程就执行清除动作然后退出。<br /><strong><br />5、Windows完成端口的实例代码<br /></strong>DWORD WINAPI WorkerThread(LPVOID lpParam)<br />{ <br />ULONG_PTR *PerHandleKey;<br />OVERLAPPED *Overlap;<br />OVERLAPPEDPLUS *OverlapPlus;<br />OVERLAPPEDPLUS *newolp;<br />DWORD dwBytesXfered;<br />while (1)<br />{<br />&nbsp; ret = <font color="#008000">GetQueuedCompletionStatus</font>(hIocp, &amp;dwBytesXfered, (PULONG_PTR)&amp;PerHandleKey, &amp;Overlap, INFINITE);<br />&nbsp; if (ret == 0)<br />&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp; // Operation failed<br />&nbsp;&nbsp;&nbsp; continue;<br />&nbsp;&nbsp; }<br />&nbsp;OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);<br />&nbsp;switch (OverlapPlus-&gt;OpCode)<br />&nbsp;{<br />&nbsp;case OP_ACCEPT:<br />&nbsp;&nbsp; // Client socket is contained in OverlapPlus.sclient<br />&nbsp; &nbsp;// Add client to completion port<br />&nbsp; <font color="#008000">CreateIoCompletionPort</font>((HANDLE)OverlapPlus-&gt;sclient, hIocp, (ULONG_PTR)0, 0);<br />&nbsp;&nbsp;// Need a new OVERLAPPEDPLUS structure<br />&nbsp; // for the newly accepted socket. Perhaps<br />&nbsp; // keep a look aside list of free structures.<br />&nbsp; newolp = AllocateOverlappedPlus();<br />&nbsp; if (!newolp)<br />&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp; // Error<br />&nbsp;&nbsp; } <br />&nbsp; newolp-&gt;s = OverlapPlus-&gt;sclient;<br />&nbsp; newolp-&gt;OpCode = OP_READ;<br />&nbsp; // This function divpares the da<wbr>ta to be sent<br />&nbsp; PrepareSendBuffer(&amp;newolp-&gt;wbuf);<br />&nbsp; ret = <font color="#008000">WSASend</font>(newolp-&gt;s, &amp;newolp-&gt;wbuf, 1, &amp;newolp-&gt;dwBytes, 0, &amp;newolp.ol, NULL);<br />&nbsp; if (ret == SOCKET_ERROR)<br />&nbsp; &nbsp;{<br />&nbsp;&nbsp;&nbsp; if (WSAGetLastError() != WSA_IO_PENDING)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Error<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp; &nbsp;}<br />&nbsp; // Put structure in look aside list for later use<br />&nbsp; FreeOverlappedPlus(OverlapPlus);<br />&nbsp; // Signal accept thread to issue another AcceptEx<br />&nbsp; <font color="#008000">SetEvent</font>(hAcceptThread);<br />&nbsp; break;<br />case OP_READ:<br />&nbsp; // Process the da<wbr>ta read <br />&nbsp; // Repost the read if necessary, reusing the same<br />&nbsp; // receive buffer as before<br />&nbsp; memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));<br />&nbsp; ret = <font color="#008000">WSARecv</font>(OverlapPlus-&gt;s, &amp;OverlapPlus-&gt;wbuf, 1, &amp;OverlapPlus-&gt;dwBytes, &amp;OverlapPlus-&gt;dwFlags, &amp;OverlapPlus-&gt;ol, NULL);<br />&nbsp; if (ret == SOCKET_ERROR)<br />&nbsp; {<br />&nbsp;&nbsp;&nbsp; if (WSAGetLastError() != WSA_IO_PENDING)<br />&nbsp;&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Error<br />&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp; }<br />&nbsp; break;<br />case OP_WRITE:<br />&nbsp; // Process the da<wbr>ta sent, etc.<br />&nbsp; break;<br />} // switch<br />} // while<br />} // WorkerThread<br />　</p>
<p>查看以上代码，注意如果Overlapped操作立刻失败（比如，返回SOCKET_ERROR或其他非WSA_IO_PENDING的错误），则没有任何完成通知时间会被放到完成端口队列里。反之，则一定有相应的通知时间被放到完成端口队列。更完善的关于Winsock的完成端口机制，可以参考 MSDN的Microsoft PlatForm SDK，那里有完成端口的例子。</p><img src ="http://www.cppblog.com/yehao/aggbug/146659.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-18 14:52 <a href="http://www.cppblog.com/yehao/articles/146659.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解I/O Completion Port(完成端口) </title><link>http://www.cppblog.com/yehao/articles/146645.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Wed, 18 May 2011 04:47:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/146645.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/146645.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/146645.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/146645.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/146645.html</trackback:ping><description><![CDATA[<font face="Arial" size="2" bgcolor="#000000">转自<a href="http://www.cnblogs.com/flying_bat/archive/2006/09/29/517987.html">http://www.cnblogs.com/flying_bat/archive/2006/09/29/517987.html</a><br /><br />欢迎阅读此篇IOCP教程。我将先给出IOCP的定义然后给出它的实现方法，最后剖析一个Echo程序来为您拨开IOCP的谜云，除去你心中对IOCP的烦恼。OK，但我不能保证你明白IOCP的一切，但我会尽我最大的努力。以下是我会在这篇文章中提到的相关技术：<br />　　I/O端口<br />　　同步/异步<br />　　堵塞/非堵塞<br />　　服务端/客户端<br />　　多线程程序设计<br />　　Winsock API 2.0<br /><br />　　在这之前，我曾经开发过一个项目，其中一块需要网络支持，当时还考虑到了代码的可移植性，只要使用select,connect,accept,listen,send还有recv,再加上几个#ifdef的封装以用来处理Winsock和BSD套接字[socket]中间的不兼容性，一个网络子系统只用了几个小时很少的代码就写出来了，至今还让我很回味。那以后很长时间也就没再碰了。<br /><br />　　前些日子，我们策划做一个网络游戏，我主动承担下网络这一块，想想这还不是小case,心里偷着乐啊。网络游戏好啊，网络游戏为成百上千的玩家提供了乐趣和令人着秘的游戏体验，他们在线上互相战斗或是加入队伍去战胜共同的敌人。我信心满满的准备开写我的网络，于是乎，发现过去的阻塞同步模式模式根本不能拿到一个巨量多玩家[MMP]的架构中去，直接被否定掉了。于是乎，就有了IOCP，如果能过很轻易而举的搞掂IOCP，也就不会有这篇教程了。下面请诸位跟随我进入正题。<br /><br /><br /><strong>什么是IOCP？</strong><br />先让我们看看对IOCP的评价<br />I/O完成端口可能是Win32提供的最复杂的内核对象。<br />[Advanced Windows 3rd] Jeffrey Richter<br />这是[IOCP]实现高容量网络服务器的最佳方法。<br />[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports] <br />Microsoft Corporation<br />完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。<br />[Windows网络编程2nd] Anthony Jones &amp; Jim Ohlund<br />I/O completion ports特别显得重要，因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程，帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在SMP系统中产生的&#8221;scalable&#8221;服务器。<br />[Win32多线程程序设计] Jim Beveridge &amp; Robert Wiener <br /><br /><br /><strong>看来我们完全有理由相信IOCP是大型网络架构的首选。那IOCP到底是什么呢？</strong><br /><br />　　微软在Winsock2中引入了IOCP这一概念 。IOCP全称I/O Completion Port，中文译为I/O完成端口。IOCP是一个异步I/O的API，它可以高效地将I/O事件通知给应用程序。与使用select()或是其它异步方法不同的是，一个套接字[socket]与一个完成端口关联了起来，然后就可继续进行正常的Winsock操作了。然而，当一个事件发生的时候，此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。<br /><br />　　这里我要对上面的一些概念略作补充，在解释[完成]两字之前，我想先简单的提一下同步和异步这两个概念，逻辑上来讲做完一件事后再去做另一件事就是同步，而同时一起做两件或两件以上事的话就是异步了。你也可以拿单线程和多线程来作比喻。但是我们一定要将同步和堵塞，异步和非堵塞区分开来，所谓的堵塞函数诸如accept(&#8230;)，当调用此函数后，此时线程将挂起，直到操作系统来通知它，&#8221;HEY兄弟，有人连进来了&#8221;，那个挂起的线程将继续进行工作，也就符合&#8221;生产者-消费者&#8221;模型。堵塞和同步看上去有两分相似，但却是完全不同的概念。大家都知道I/O设备是个相对慢速的设备，不论打印机，调制解调器，甚至硬盘，与CPU相比都是奇慢无比的，坐下来等I/O的完成是一件不甚明智的事情，有时候数据的流动率非常惊人，把数据从你的文件服务器中以Ethernet速度搬走，其速度可能高达每秒一百万字节，如果你尝试从文件服务器中读取100KB，在用户的眼光来看几乎是瞬间完成，但是，要知道，你的线程执行这个命令，已经浪费了10个一百万次CPU周期。所以说，我们一般使用另一个线程来进行I/O。重叠IO[overlapped I/O]是Win32的一项技术，你可以要求操作系统为你传送数据，并且在传送完毕时通知你。这也就是[完成]的含义。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上，操作系统内部正是以线程来完成overlapped I/O。你可以获得线程所有利益，而不需要付出什么痛苦的代价。<br /><br />　　完成端口中所谓的[端口]并不是我们在TCP/IP中所提到的端口，可以说是完全没有关系。我到现在也没想通一个I/O设备[I/O Device]和端口[IOCP中的Port]有什么关系。估计这个端口也迷惑了不少人。IOCP只不过是用来进行读写操作，和文件I/O倒是有些类似。既然是一个读写设备，我们所能要求它的只是在处理读与写上的高效。在文章的第三部分你会轻而易举的发现IOCP设计的真正用意。<br /><br /><br /><strong>IOCP和网络又有什么关系？</strong><br /><br /></font></font><font color="#99ccff"><font face="宋体" size="2">int main()<br />{<br />&nbsp;&nbsp;&nbsp; WSAStartup(MAKEWORD(2, 2), &amp;wsaData);<br />&nbsp;&nbsp;&nbsp; ListeningSocket = socket(AF_INET, SOCK_STREAM, 0); <br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">bind(ListeningSocket, (SOCKADDR*)&amp;ServerAddr, sizeof(ServerAddr));<br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">listen(ListeningSocket, 5);<br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">int nlistenAddrLen = sizeof(ClientAddr);<br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">while(TRUE)<br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">NewConnection = accept(ListeningSocket, (SOCKADDR*)&amp;ClientAddr, &amp;nlistenAddrLen);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, (void*) NewConnection, 0, &amp;dwTreadId);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">CloseHandle(hThread);<br />&nbsp;&nbsp;&nbsp; </font><font face="宋体" size="2">}<br />&nbsp;&nbsp;&nbsp; </font></font><font face="宋体" color="#99ccff" size="2">return 0;<br />}</font><font face="Arial"><br /><br /><font size="2">　　相信只要写过网络的朋友，应该对这样的结构在熟悉不过了。accept后线程被挂起，等待一个客户发出请求，而后创建新线程来处理请求。当新线程处理客户请求时，起初的线程循环回去等待另一个客户请求。处理客户请求的线程处理完毕后终结。<br /><br />　　在上述的并发模型中，对每个客户请求都创建了一个线程。其优点在于等待请求的线程只需做很少的工作。大多数时间中，该线程在休眠[因为recv处于堵塞状态]。<br /><br />　　但是当并发模型应用在服务器端[基于Windows NT]，Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。<br /><br />　　大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。<br /><br />　　我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢?!<br /><br />　　这个问题的解决方法就是一个称为I/O完成端口的内核对象，他首次在Windows NT3.5中被引入。<br /><br />　　其实我们上面的构想应该就差不多是IOCP的设计机理。其实说穿了IOCP不就是一个消息队列嘛！你说这和[端口]这两字有何联系。我的理解就是IOCP最多是应用程序和操作系统沟通的一个接口罢了。<br /><br />　　至于IOCP的具体设计那我也很难说得上来，毕竟我没看过实现的代码，但你完全可以进行模拟，只不过性能可能&#8230;，如果想深入理解IOCP， Jeffrey Ritchter的Advanced Windows 3rd其中第13章和第14张有很多宝贵的内容，你可以拿来窥视一下系统是如何完成这一切的。<br /><br /><br /><strong>实现方法</strong><br /><br />Microsoft为IOCP提供了相应的API函数，主要的就两个，我们逐一的来看一下：<br /></font></font><font size="2"><font face="宋体" color="#99ccff">HANDLE CreateIoCompletionPort (<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">HANDLE FileHandle,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // handle to file<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">HANDLE ExistingCompletionPort,&nbsp; // handle to I/O completion port<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体" color="#99ccff">ULONG_PTR CompletionKey, </font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体">&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">// completion key<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font></font><font face="宋体" color="#99ccff" size="2">DWORD NumberOfConcurrentThreads // number of threads to execute concurrently<br />);</font><font face="Arial"><br /><br /><font size="2">在讨论各参数之前，首先要注意该函数实际用于两个截然不同的目的：<br />1．用于创建一个完成端口对象<br />2．将一个句柄[HANDLE]和完成端口关联到一起<br /><br />　　在创建一个完成一个端口的时候，我们只需要填写一下NumberOfConcurrentThreads这个参数就可以了。它告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下，所开线程数和CPU数量相同，但经验给我们一个公式：<br />　　线程数 = CPU数 * 2 + 2<br />要使完成端口有用，你必须把它同一个或多个设备相关联。这也是调用CreateIoCompletionPort完成的。你要向该函数传递一个已有的完成端口的句柄，我们既然要处理网络事件，那也就是将客户的socket作为HANDLE传进去。和一个完成键[对你有意义的一个32位值，也就是一个指针，操作系统并不关心你传什么]。每当你向端口关联一个设备时，系统向该完成端口的设备列表中加入一条信息纪录。<br /><br />另一个API就是<br /></font></font><font size="2"><font face="宋体" color="#99ccff">BOOL GetQueuedCompletionStatus(<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体" color="#99ccff">HANDLE CompletionPort, </font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体">&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">// handle to completion port<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体" color="#99ccff">LPDWORD lpNumberOfBytes,</font><font face="宋体">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">// bytes transferred<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">PULONG_PTR lpCompletionKey,&nbsp;&nbsp; // file completion key<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font></font><font size="2"><font face="宋体" color="#99ccff">LPOVERLAPPED *lpOverlapped,&nbsp;&nbsp; // buffer<br /></font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体" color="#99ccff">DWORD dwMilliseconds </font><font face="宋体">&nbsp;&nbsp;&nbsp; </font><font face="宋体">&nbsp; 　 </font></font><font face="宋体" color="#99ccff" size="2">// optional timeout value<br />);</font><font face="Arial"><br /><br /><font size="2">第一个参数指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个I/O完成端口，所有的I/O请求完成以后的通知都将发给该端口。简单的说，GetQueuedCompletionStatus使调用线程挂起，直到指定的端口的I/O完成队列中出现了一项或直到超时。同I/O完成端口相关联的第3个数据结构是使线程得到完成I/O项中的信息：传输的字节数，完成键和OVERLAPPED结构的地址。该信息是通过传递给GetQueuedCompletionSatatus的lpdwNumberOfBytesTransferred，lpdwCompletionKey和lpOverlapped参数返回给线程的。<br /><br />根据到目前为止已经讲到的东西，首先来构建一个frame。下面为您说明了如何使用完成端口来开发一个echo服务器。大致如下：<br />　　1.初始化Winsock<br />　　2.创建一个完成端口<br />　　3.根据服务器线程数创建一定量的线程数<br />　　4.准备好一个socket进行bind然后listen<br />　　5.进入循环accept等待客户请求<br />　　6.创建一个数据结构容纳socket和其他相关信息<br />　　7.将连进来的socket同完成端口相关联<br />　　8.投递一个准备接受的请求<br />以后就不断的重复5至8的过程<br />那好，我们用具体的代码来展示一下细节的操作。<br />至此文章也该告一段落了,我带着您做了一趟旋风般的旅游,游览了所谓的完成端口。</font></font><img src ="http://www.cppblog.com/yehao/aggbug/146645.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-18 12:47 <a href="http://www.cppblog.com/yehao/articles/146645.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HTTP协议详解</title><link>http://www.cppblog.com/yehao/articles/145474.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 01 May 2011 11:13:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145474.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145474.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145474.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145474.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145474.html</trackback:ping><description><![CDATA[<p>引言&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>HTTP是一个属于应用层的面向对象的协议，由于其简捷、快速的方式，适用于分布式超媒体信息系统。它于1990年提出，经过几年的使用与发展，得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版，HTTP/1.1的规范化工作正在进行之中，而且HTTP-NG(Next Generation of HTTP)的建议已经提出。<br>HTTP协议的主要特点可概括如下：<br>1.支持客户/服务器模式。<br>2.简单快速：客户向服务器请求服务时，只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单，使得HTTP服务器的程序规模小，因而通信速度很快。<br>3.灵活：HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。<br>4.无连接：无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求，并收到客户的应答后，即断开连接。采用这种方式可以节省传输时间。<br>5.无状态：HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>一、HTTP协议详解之URL篇</p>
<p>&nbsp;&nbsp;&nbsp; http（超文本传输协议）是一个基于请求与响应模式的、无状态的、应用层的协议，常基于TCP的连接方式，HTTP1.1版本中给出一种持续连接的机制，绝大多数的Web开发，都是构建在HTTP协议之上的Web应用。</p>
<p>HTTP URL (URL是一种特殊类型的URI，包含了用于查找某个资源的足够的信息)的格式如下：<br><a href="http://host[%22:%22port][abs_path/">http://host[":"port][abs_path</a>]<br>http表示要通过HTTP协议来定位网络资源；host表示合法的Internet主机域名或者IP地址；port指定一个端口号，为空则使用缺省端口80；abs_path指定请求资源的URI；如果URL中没有给出abs_path，那么当它作为请求URI时，必须以&#8220;/&#8221;的形式给出，通常这个工作浏览器自动帮我们完成。<br>eg:<br>1、输入：<a href="http://www.guet.edu.cn/">www.guet.edu.cn</a><br>浏览器自动转换成：<a href="http://www.guet.edu.cn/">http://www.guet.edu.cn/</a><br>2、http:192.168.0.116:8080/index.jsp </p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>二、HTTP协议详解之请求篇</p>
<p>&nbsp;&nbsp;&nbsp; http请求由三部分组成，分别是：请求行、消息报头、请求正文</p>
<p>1、请求行以一个方法符号开头，以空格分开，后面跟着请求的URI和协议的版本，格式如下：Method Request-URI HTTP-Version CRLF&nbsp; <br>其中 Method表示请求方法；Request-URI是一个统一资源标识符；HTTP-Version表示请求的HTTP协议版本；CRLF表示回车和换行（除了作为结尾的CRLF外，不允许出现单独的CR或LF字符）。</p>
<p>请求方法（所有方法全为大写）有多种，各个方法的解释如下：<br>GET&nbsp;&nbsp;&nbsp;&nbsp; 请求获取Request-URI所标识的资源<br>POST&nbsp;&nbsp;&nbsp; 在Request-URI所标识的资源后附加新的数据<br>HEAD&nbsp;&nbsp;&nbsp; 请求获取由Request-URI所标识的资源的响应消息报头<br>PUT&nbsp;&nbsp;&nbsp;&nbsp; 请求服务器存储一个资源，并用Request-URI作为其标识<br>DELETE&nbsp; 请求服务器删除Request-URI所标识的资源<br>TRACE&nbsp;&nbsp; 请求服务器回送收到的请求信息，主要用于测试或诊断<br>CONNECT 保留将来使用<br>OPTIONS 请求查询服务器的性能，或者查询与资源相关的选项和需求<br>应用举例：<br>GET方法：在浏览器的地址栏中输入网址的方式访问网页时，浏览器采用GET方法向服务器获取资源，eg:GET /form.html HTTP/1.1 (CRLF)</p>
<p>POST方法要求被请求服务器接受附在请求后面的数据，常用于提交表单。<br>eg：POST /reg.jsp HTTP/ (CRLF)<br>Accept:image/gif,image/x-xbit,... (CRLF)<br>...<br>HOST:www.guet.edu.cn (CRLF)<br>Content-Length:22 (CRLF)<br>Connection:Keep-Alive (CRLF)<br>Cache-Control:no-cache (CRLF)<br>(CRLF)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //该CRLF表示消息报头已经结束，在此之前为消息报头<br>user=jeffrey&amp;pwd=1234&nbsp; //此行以下为提交的数据</p>
<p>HEAD方法与GET方法几乎是一样的，对于HEAD请求的回应部分来说，它的HTTP头部中包含的信息与通过GET请求所得到的信息是相同的。利用这个方法，不必传输整个资源内容，就可以得到Request-URI所标识的资源的信息。该方法常用于测试超链接的有效性，是否可以访问，以及最近是否更新。<br>2、请求报头后述<br>3、请求正文(略) </p>
<p>&nbsp;</p>
<p>三、HTTP协议详解之响应篇</p>
<p>&nbsp;&nbsp;&nbsp; 在接收和解释请求消息后，服务器返回一个HTTP响应消息。</p>
<p>HTTP响应也是由三个部分组成，分别是：状态行、消息报头、响应正文<br>1、状态行格式如下：<br>HTTP-Version Status-Code Reason-Phrase CRLF<br>其中，HTTP-Version表示服务器HTTP协议的版本；Status-Code表示服务器发回的响应状态代码；Reason-Phrase表示状态代码的文本描述。<br>状态代码有三位数字组成，第一个数字定义了响应的类别，且有五种可能取值：<br>1xx：指示信息--表示请求已接收，继续处理<br>2xx：成功--表示请求已被成功接收、理解、接受<br>3xx：重定向--要完成请求必须进行更进一步的操作<br>4xx：客户端错误--请求有语法错误或请求无法实现<br>5xx：服务器端错误--服务器未能实现合法的请求<br>常见状态代码、状态描述、说明：<br>200 OK&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //客户端请求成功<br>400 Bad Request&nbsp; //客户端请求有语法错误，不能被服务器所理解<br>401 Unauthorized //请求未经授权，这个状态代码必须和WWW-Authenticate报&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //头域一起使用 <br>403 Forbidden&nbsp; //服务器收到请求，但是拒绝提供服务<br>404 Not Found&nbsp; //请求资源不存在，eg：输入了错误的URL<br>500 Internal Server Error //服务器发生不可预期的错误<br>503 Server Unavailable&nbsp; //服务器当前不能处理客户端的请求，一段时间后，&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //可能恢复正常<br>eg：HTTP/1.1 200 OK （CRLF）</p>
<p>2、响应报头后述</p>
<p>3、响应正文就是服务器返回的资源的内容 </p>
<p>&nbsp;</p>
<p>四、HTTP协议详解之消息报头篇</p>
<p>&nbsp;&nbsp;&nbsp; HTTP消息由客户端到服务器的请求和服务器到客户端的响应组成。请求消息和响应消息都是由开始行（对于请求消息，开始行就是请求行，对于响应消息，开始行就是状态行），消息报头（可选），空行（只有CRLF的行），消息正文（可选）组成。</p>
<p>HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。<br>每一个报头域都是由名字+&#8220;：&#8221;+空格+值 组成，消息报头域的名字是大小写无关的。</p>
<p>1、普通报头<br>在普通报头中，有少数报头域用于所有的请求和响应消息，但并不用于被传输的实体，只用于传输的消息。<br>eg：<br>Cache-Control&nbsp;&nbsp; 用于指定缓存指令，缓存指令是单向的（响应中出现的缓存指令在请求中未必会出现），且是独立的（一个消息的缓存指令不会影响另一个消息处理的缓存机制），HTTP1.0使用的类似的报头域为Pragma。<br>请求时的缓存指令包括：no-cache（用于指示请求或响应消息不能缓存）、no-store、max-age、max-stale、min-fresh、only-if-cached;<br>响应时的缓存指令包括：public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.<br>eg：为了指示IE浏览器（客户端）不要缓存页面，服务器端的JSP程序可以编写如下：response.sehHeader("Cache-Control","no-cache");<br>//response.setHeader("Pragma","no-cache");作用相当于上述代码，通常两者//合用<br>这句代码将在发送的响应消息中设置普通报头域：Cache-Control:no-cache</p>
<p><br>Date普通报头域表示消息产生的日期和时间</p>
<p>Connection普通报头域允许发送指定连接的选项。例如指定连接是连续，或者指定&#8220;close&#8221;选项，通知服务器，在响应完成后，关闭连接</p>
<p>2、请求报头<br>请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。<br>常用的请求报头<br>Accept<br>Accept请求报头域用于指定客户端接受哪些类型的信息。eg：Accept：image/gif，表明客户端希望接受GIF图象格式的资源；Accept：text/html，表明客户端希望接受html文本。<br>Accept-Charset<br>Accept-Charset请求报头域用于指定客户端接受的字符集。eg：Accept-Charset:iso-8859-1,gb2312.如果在请求消息中没有设置这个域，缺省是任何字符集都可以接受。<br>Accept-Encoding<br>Accept-Encoding请求报头域类似于Accept，但是它是用于指定可接受的内容编码。eg：Accept-Encoding:gzip.deflate.如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。<br>Accept-Language<br>Accept-Language请求报头域类似于Accept，但是它是用于指定一种自然语言。eg：Accept-Language:zh-cn.如果请求消息中没有设置这个报头域，服务器假定客户端对各种语言都可以接受。<br>Authorization<br>Authorization请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时，如果收到服务器的响应代码为401（未授权），可以发送一个包含Authorization请求报头域的请求，要求服务器对其进行验证。<br>Host（发送请求时，该报头域是必需的）<br>Host请求报头域主要用于指定被请求资源的Internet主机和端口号，它通常从HTTP URL中提取出来的，eg：<br>我们在浏览器中输入：<a href="http://www.guet.edu.cn/index.html">http://www.guet.edu.cn/index.html</a><br>浏览器发送的请求消息中，就会包含Host请求报头域，如下：<br>Host：<a href="http://www.guet.edu.cn/">www.guet.edu.cn</a><br>此处使用缺省端口号80，若指定了端口号，则变成：Host：<a href="http://www.guet.edu.cn/">www.guet.edu.cn</a>:指定端口号<br>User-Agent<br>我们上网登陆论坛的时候，往往会看到一些欢迎信息，其中列出了你的操作系统的名称和版本，你所使用的浏览器的名称和版本，这往往让很多人感到很神奇，实际上，服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息。User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过，这个报头域不是必需的，如果我们自己编写一个浏览器，不使用User-Agent请求报头域，那么服务器端就无法得知我们的信息了。<br>请求报头举例：<br>GET /form.html HTTP/1.1 (CRLF)<br>Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/* (CRLF)<br>Accept-Language:zh-cn (CRLF)<br>Accept-Encoding:gzip,deflate (CRLF)<br>If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT (CRLF)<br>If-None-Match:W/"80b1a4c018f3c41:8317" (CRLF)<br>User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0) (CRLF)<br>Host:www.guet.edu.cn (CRLF)<br>Connection:Keep-Alive (CRLF)<br>(CRLF)</p>
<p>3、响应报头<br>响应报头允许服务器传递不能放在状态行中的附加响应信息，以及关于服务器的信息和对Request-URI所标识的资源进行下一步访问的信息。<br>常用的响应报头<br>Location<br>Location响应报头域用于重定向接受者到一个新的位置。Location响应报头域常用在更换域名的时候。<br>Server<br>Server响应报头域包含了服务器用来处理请求的软件信息。与User-Agent请求报头域是相对应的。下面是<br>Server响应报头域的一个例子：<br>Server：Apache-Coyote/1.1<br>WWW-Authenticate<br>WWW-Authenticate响应报头域必须被包含在401（未授权的）响应消息中，客户端收到401响应消息时候，并发送Authorization报头域请求服务器对其进行验证时，服务端响应报头就包含该报头域。<br>eg：WWW-Authenticate:Basic realm="Basic Auth Test!"&nbsp; //可以看出服务器对请求资源采用的是基本验证机制。</p>
<p><br>4、实体报头<br>请求和响应消息都可以传送一个实体。一个实体由实体报头域和实体正文组成，但并不是说实体报头域和实体正文要在一起发送，可以只发送实体报头域。实体报头定义了关于实体正文（eg：有无实体正文）和请求所标识的资源的元信息。<br>常用的实体报头<br>Content-Encoding<br>Content-Encoding实体报头域被用作媒体类型的修饰符，它的值指示了已经被应用到实体正文的附加内容的编码，因而要获得Content-Type报头域中所引用的媒体类型，必须采用相应的解码机制。Content-Encoding这样用于记录文档的压缩方法，eg：Content-Encoding：gzip<br>Content-Language<br>Content-Language实体报头域描述了资源所用的自然语言。没有设置该域则认为实体内容将提供给所有的语言阅读<br>者。eg：Content-Language:da<br>Content-Length<br>Content-Length实体报头域用于指明实体正文的长度，以字节方式存储的十进制数字来表示。<br>Content-Type<br>Content-Type实体报头域用语指明发送给接收者的实体正文的媒体类型。eg：<br>Content-Type:text/html;charset=ISO-8859-1<br>Content-Type:text/html;charset=GB2312<br>Last-Modified<br>Last-Modified实体报头域用于指示资源的最后修改日期和时间。<br>Expires<br>Expires实体报头域给出响应过期的日期和时间。为了让代理服务器或浏览器在一段时间以后更新缓存中(再次访问曾访问过的页面时，直接从缓存中加载，缩短响应时间和降低服务器负载)的页面，我们可以使用Expires实体报头域指定页面过期的时间。eg：Expires：Thu，15 Sep 2006 16:23:12 GMT<br>HTTP1.1的客户端和缓存必须将其他非法的日期格式（包括0）看作已经过期。eg：为了让浏览器不要缓存页面，我们也可以利用Expires实体报头域，设置为0，jsp中程序如下：response.setDateHeader("Expires","0");</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>五、利用telnet观察http协议的通讯过程</p>
<p>&nbsp;&nbsp;&nbsp; 实验目的及原理：<br>&nbsp;&nbsp;&nbsp; 利用MS的telnet工具，通过手动输入http请求信息的方式，向服务器发出请求，服务器接收、解释和接受请求后，会返回一个响应，该响应会在telnet窗口上显示出来，从而从感性上加深对http协议的通讯过程的认识。</p>
<p>&nbsp;&nbsp;&nbsp; 实验步骤：</p>
<p>1、打开telnet<br>1.1 打开telnet<br>运行--&gt;cmd--&gt;telnet</p>
<p>1.2 打开telnet回显功能<br>set localecho</p>
<p>2、连接服务器并发送请求<br>2.1 open <a href="http://www.guet.edu.cn/">www.guet.edu.cn</a> 80&nbsp; //注意端口号不能省略</p>
<p>&nbsp;&nbsp;&nbsp; HEAD /index.asp HTTP/1.0<br>&nbsp;&nbsp;&nbsp; Host:www.guet.edu.cn<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; /*我们可以变换请求方法,请求桂林电子主页内容,输入消息如下*/<br>&nbsp;&nbsp;&nbsp; open <a href="http://www.guet.edu.cn/">www.guet.edu.cn</a> 80 <br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; GET /index.asp HTTP/1.0&nbsp; //请求资源的内容<br>&nbsp;&nbsp;&nbsp; Host:www.guet.edu.cn&nbsp;&nbsp; </p>
<p>2.2 open <a href="http://www.sina.com.cn/">www.sina.com.cn</a> 80&nbsp; //在命令提示符号下直接输入telnet <a href="http://www.sina.com.cn/">www.sina.com.cn</a> 80<br>&nbsp;&nbsp;&nbsp; HEAD /index.asp HTTP/1.0<br>&nbsp;&nbsp;&nbsp; Host:www.sina.com.cn<br>&nbsp;</p>
<p>3 实验结果：</p>
<p>3.1 请求信息2.1得到的响应是:</p>
<p>HTTP/1.1 200 OK&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //请求成功<br>Server: Microsoft-IIS/5.0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //web服务器<br>Date: Thu,08 Mar 200707:17:51 GMT<br>Connection: Keep-Alive&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>Content-Length: 23330<br>Content-Type: text/html<br>Expries: Thu,08 Mar 2007 07:16:51 GMT<br>Set-Cookie: ASPSESSIONIDQAQBQQQB=BEJCDGKADEDJKLKKAJEOIMMH; path=/<br>Cache-control: private</p>
<p>//资源内容省略</p>
<p>3.2 请求信息2.2得到的响应是:</p>
<p>HTTP/1.0 404 Not Found&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //请求失败<br>Date: Thu, 08 Mar 2007 07:50:50 GMT<br>Server: Apache/2.0.54 &lt;Unix&gt;<br>Last-Modified: Thu, 30 Nov 2006 11:35:41 GMT<br>ETag: "6277a-415-e7c76980"<br>Accept-Ranges: bytes<br>X-Powered-By: mod_xlayout_jh/0.0.1vhs.markII.remix<br>Vary: Accept-Encoding<br>Content-Type: text/html<br>X-Cache: MISS from zjm152-78.sina.com.cn<br>Via: 1.0 zjm152-78.sina.com.cn:80&lt;squid/2.6.STABLES-20061207&gt;<br>X-Cache: MISS from th-143.sina.com.cn<br>Connection: close</p>
<p><br>失去了跟主机的连接</p>
<p>按任意键继续...</p>
<p><br>4 .注意事项：1、出现输入错误，则请求不会成功。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2、报头域不分大小写。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3、更深一步了解HTTP协议，可以查看RFC2616，在<a href="http://www.letf.org/rfc">http://www.letf.org/rfc</a>上找到该文件。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4、开发后台程序必须掌握http协议</p>
<p>&nbsp;</p>
<p>六、HTTP协议相关技术补充</p>
<p>&nbsp;&nbsp;&nbsp; 1、基础：<br>&nbsp;&nbsp;&nbsp; 高层协议有：文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等<br>中介由三种：代理(Proxy)、网关(Gateway)和通道(Tunnel)，一个代理根据URI的绝对格式来接受请求，重写全部或部分消息，通过 URI的标识把已格式化过的请求发送到服务器。网关是一个接收代理，作为一些其它服务器的上层，并且如果必须的话，可以把请求翻译给下层的服务器协议。一 个通道作为不改变消息的两个连接之间的中继点。当通讯需要通过一个中介(例如：防火墙等)或者是中介不能识别消息的内容时，通道经常被使用。<br>&nbsp;&nbsp;&nbsp;&nbsp; 代理(Proxy)：一个中间程序，它可以充当一个服务器，也可以充当一个客户机，为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的 服务器中。一个代理在发送请求信息之前，必须解释并且如果可能重写它。代理经常作为通过防火墙的客户机端的门户，代理还可以作为一个帮助应用来通过协议处 理没有被用户代理完成的请求。<br>网关(Gateway)：一个作为其它服务器中间媒介的服务器。与代理不同的是，网关接受请求就好象对被请求的资源来说它就是源服务器；发出请求的客户机并没有意识到它在同网关打交道。<br>　　网关经常作为通过防火墙的服务器端的门户，网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。<br>&nbsp;&nbsp;&nbsp; 通道(Tunnel)：是作为两个连接中继的中介程序。一旦激活，通道便被认为不属于HTTP通讯，尽管通道可能是被一个HTTP请求初始化的。当被中继 的连接两端关闭时，通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。</p>
<p><br>2、协议分析的优势—HTTP分析器检测网络攻击<br>以模块化的方式对高层协议进行分析处理，将是未来入侵检测的方向。<br>HTTP及其代理的常用端口80、3128和8080在network部分用port标签进行了规定</p>
<p>3、HTTP协议Content Lenth限制漏洞导致拒绝服务攻击<br>使用POST方法时，可以设置ContentLenth来定义需要传送的数据长度，例如ContentLenth:999999999，在传送完成前，内 存不会释放，攻击者可以利用这个缺陷，连续向WEB服务器发送垃圾数据直至WEB服务器内存耗尽。这种攻击方法基本不会留下痕迹。<br><a href="http://www.cnpaf.net/Class/HTTP/0532918532667330.html">http://www.cnpaf.net/Class/HTTP/0532918532667330.html</a></p>
<p><br>4、利用HTTP协议的特性进行拒绝服务攻击的一些构思<br>服务器端忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求（毕竟客户端的正常请求比率非常之小），此时从正常客户的角度看来，服务器失去响应，这种情况我们称作：服务器端受到了SYNFlood攻击（SYN洪水攻击）。<br>而Smurf、TearDrop等是利用ICMP报文来Flood和IP碎片攻击的。本文用&#8220;正常连接&#8221;的方法来产生拒绝服务攻击。<br>19端口在早期已经有人用来做Chargen攻击了，即Chargen_Denial_of_Service，但是！他们用的方法是在两台Chargen 服务器之间产生UDP连接，让服务器处理过多信息而DOWN掉，那么，干掉一台WEB服务器的条件就必须有2个：1.有Chargen服务2.有HTTP 服务<br>方法：攻击者伪造源IP给N台Chargen发送连接请求（Connect），Chargen接收到连接后就会返回每秒72字节的字符流（实际上根据网络实际情况，这个速度更快）给服务器。</p>
<p><br>5、Http指纹识别技术<br>&nbsp;&nbsp; Http指纹识别的原理大致上也是相同的：记录不同服务器对Http协议执行中的微小差别进行识别.Http指纹识别比TCP/IP堆栈指纹识别复杂许 多,理由是定制Http服务器的配置文件、增加插件或组件使得更改Http的响应信息变的很容易,这样使得识别变的困难；然而定制TCP/IP堆栈的行为 需要对核心层进行修改,所以就容易识别.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 要让服务器返回不同的Banner信息的设置是很简单的,象Apache这样的开放源代码的Http服务器,用户可以在源代码里修改Banner信息,然 后重起Http服务就生效了；对于没有公开源代码的Http服务器比如微软的IIS或者是Netscape,可以在存放Banner信息的Dll文件中修 改,相关的文章有讨论的,这里不再赘述,当然这样的修改的效果还是不错的.另外一种模糊Banner信息的方法是使用插件。<br>常用测试请求：<br>1：HEAD/Http/1.0发送基本的Http请求<br>2：DELETE/Http/1.0发送那些不被允许的请求,比如Delete请求<br>3：GET/Http/3.0发送一个非法版本的Http协议请求<br>4：GET/JUNK/1.0发送一个不正确规格的Http协议请求<br>Http指纹识别工具Httprint,它通过运用统计学原理,组合模糊的逻辑学技术,能很有效的确定Http服务器的类型.它可以被用来收集和分析不同Http服务器产生的签名。</p>
<p><br>6、其他：为了提高用户使用浏览器时的性能，现代浏览器还支持并发的访问方式，浏览一个网页时同时建立多个连接，以迅速获得一个网页上的多个图标，这样能更快速完成整个网页的传输。<br>HTTP1.1中提供了这种持续连接的方式，而下一代HTTP协议：HTTP-NG更增加了有关会话控制、丰富的内容协商等方式的支持，来提供<br>更高效率的连接。</p>
<p>&nbsp;</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/gueter/archive/2007/03/08/1524447.aspx">http://blog.csdn.net/gueter/archive/2007/03/08/1524447.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/145474.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-01 19:13 <a href="http://www.cppblog.com/yehao/articles/145474.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP三次握手及四次挥手详细图解</title><link>http://www.cppblog.com/yehao/articles/145459.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 01 May 2011 08:20:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145459.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145459.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145459.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145459.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145459.html</trackback:ping><description><![CDATA[相对于SOCKET开发者,TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的.因此开发者并不需要控制这个过程.但是对于理解TCP底层运作机制,相当有帮助.<br>而且对于有网络协议工程师之类笔试,几乎是必考的内容.企业对这个问题热情之高,出乎我的意料：-）。有时上午面试前强调这个问题，并重复讲一次，下午几乎每一个人都被问到这个问题。<br>因此在这里详细解释一下这两个过程。<br><strong><font size=3><u>TCP三次握手<br></u></font></strong>所谓三次握手(Three-way Handshake)，是指建立一个TCP连接时，需要客户端和服务器总共发送3个包。<br>三次握手的目的是连接服务器指定端口，建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中，客户端执行connect()时。将触发三次握手。
<div>
<div align=center>&nbsp;</div>
<div align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327002629.png"></div>
</div>
<ul>
    <li>第一次握手:<br>客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口，以及初始序号X,保存在包头的序列号(Sequence Number)字段里。</li>
</ul>
<div align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327002911.png"></div>
<ul>
    <li>第二次握手:<br>服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时，将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。</li>
</ul>
<p><br></p>
<p align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327003054.png" width=500></p>
<ul>
    <li>第三次握手.<br>客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1</li>
</ul>
<div align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327003214.png" width=500></div>
<p><strong><font size=4><u>SYN攻击</u></font></strong></p>
<p>&nbsp;&nbsp; 在三次握手过程中，服务器发送SYN-ACK之后，收到客户端的ACK之前的TCP连接称为半连接(half-open connect).此时服务器处于Syn_RECV状态.当收到ACK后，服务器转入ESTABLISHED状态.</p>
<p>&nbsp; Syn攻击就是 攻击客户端 在短时间内伪造大量不存在的IP地址，向服务器不断地发送syn包，服务器回复确认包，并等待客户的确认，由于源地址是不存在的，服务器需要不断的重发直 至超时，这些伪造的SYN包将长时间占用未连接队列，正常的SYN请求被丢弃，目标系统运行缓慢，严重者引起网络堵塞甚至系统瘫痪。</p>
<p>&nbsp;Syn攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便，当你在服务器上看到大量的半连接状态时，特别是源IP地址是随机的，基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被Syn攻击</p>
<p><strong>netstat -n -p TCP | grep SYN_RECV</strong></p>
<p>一般较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击，修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等.</p>
<p>但是不能完全防范syn攻击。<br></p>
<p><strong><u><font size=4>TCP 四次挥手</font></u></strong></p>
<p>TCP的连接的拆除需要发送四个包，因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作，在socket编程中，任何一方执行close()操作即可产生挥手操作。<br></p>
<div align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327022731.jpg"></div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>参见wireshark抓包，实测的抓包结果并没有严格按挥手时序。我估计是时间间隔太短造成。</p>
<div align=center><img border=0 src="http://blogimg.chinaunix.net/blog/upfile2/100327023334.png"></div>
<img src ="http://www.cppblog.com/yehao/aggbug/145459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-01 16:20 <a href="http://www.cppblog.com/yehao/articles/145459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOCKET CLOSE_WAIT状态的说明</title><link>http://www.cppblog.com/yehao/articles/145458.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 01 May 2011 07:55:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145458.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145458.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145458.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145458.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145458.html</trackback:ping><description><![CDATA[<p>CLOSE_WAIT出现的原因: 就是某一方在网络连接断开后，对等方没有检测到这个错误（对方断开）而没有调用 closesocket，导致了这个状态的出现;<br>&nbsp;<br>断开连接的时候: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当发起主动关闭的左边这方发送一个FIN过去后，右边被动关闭的这方要回应一个ACK，这个ACK是TCP回应的(同时TCP向上层应用程序提交一个ERROR,导致上面的SOCKET的send或者recv返回SOCKET_ERROR)，而不是应用程序发送的，此时，被动关闭的一方就处于CLOSE_WAIT状态了。如果此时被动关闭的这一方不再继续调用closesocket,那么他就不会发送接下来的FIN，导致自己老是处于CLOSE_WAIT。只有被动关闭的这一方调用了closesocket,才会发送一个FIN给主动关闭的这一方，同时也使得自己的状态变迁为LAST_ACK,待接收到主动关闭方发送的ACK后，才会将SOCKET置为CLOSED。 <br>+ expand sourceview plaincopy to clipboardprint?<br>int nRet = recv(sockConnected, szRecvBuffer,sizeof(szRecvBuffer),0);&nbsp;&nbsp;&nbsp; <br>///&nbsp;&nbsp;&nbsp; <br>/// 当对方调用closesocket的时候，我的程序正在recv,&nbsp;&nbsp; <br>/// 这时候有可能对方发送的FIN包我没有收到，而是由TCP代回了一个ACK包,&nbsp;&nbsp; <br>/// 所以我这边程序进入CLOSE_WAIT状态。&nbsp;&nbsp;&nbsp; <br>/// 所以建议在这里判断是否已出错，是就主动closesocket。&nbsp;&nbsp;&nbsp; <br>/// 因为前面已经设置了recv超时时间为30秒，那么如果真的是超时了，&nbsp;&nbsp;&nbsp; <br>/// 这里收到的错误应该是WSAETIMEDOUT，这种情况下也可以关闭连接的&nbsp;&nbsp;&nbsp; <br>if (nRet == SOCKET_ERROR)&nbsp;&nbsp;&nbsp; <br>{&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; TRACE_INFO(_T("=用recv接收发生Socket错误="));&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; closesocket(sockConnected);&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; return FALSE;&nbsp;&nbsp; <br>}&nbsp;&nbsp; <br>int nRet = recv(sockConnected, szRecvBuffer,sizeof(szRecvBuffer),0); <br>/// <br>/// 当对方调用closesocket的时候，我的程序正在recv,<br>/// 这时候有可能对方发送的FIN包我没有收到，而是由TCP代回了一个ACK包,<br>/// 所以我这边程序进入CLOSE_WAIT状态。 <br>/// 所以建议在这里判断是否已出错，是就主动closesocket。 <br>/// 因为前面已经设置了recv超时时间为30秒，那么如果真的是超时了， <br>/// 这里收到的错误应该是WSAETIMEDOUT，这种情况下也可以关闭连接的 <br>if (nRet == SOCKET_ERROR) <br>{ <br>&nbsp;&nbsp; TRACE_INFO(_T("=用recv接收发生Socket错误=")); <br>&nbsp;&nbsp; closesocket(sockConnected); <br>&nbsp;&nbsp; return FALSE;<br>} <br>&nbsp;&nbsp; <br>检测到SOCKET_ORROR 则主动调用closesocket() 关闭套接字； <br>***************************************************************<br>首先我们知道，如果我们的Client程序处于CLOSE_WAIT状态的话，说明套接字是被动关闭的！<br>因为如果是Server端主动断掉当前连接的话，那么双方关闭这个TCP连接共需要四个packet：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server ---&gt; FIN ---&gt; Client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server &lt;--- ACK &lt;--- Client<br>&nbsp;&nbsp;&nbsp; 这时候Server端处于FIN_WAIT_2状态；而我们的程序处于CLOSE_WAIT状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server &lt;--- FIN &lt;--- Client<br>这时Client发送FIN给Server，Client就置为LAST_ACK状态。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Server ---&gt; ACK ---&gt; Client<br>Server回应了ACK，那么Client的套接字才会真正置为CLOSED状态。</p>
<p><br>我们的程序处于CLOSE_WAIT状态，而不是LAST_ACK状态，说明还没有发FIN给Server，那么可能是在关闭连接之前还有许多数据要发送或者其他事要做，导致没有发这个FIN packet。<br>原因知道了，那么为什么不发FIN包呢，难道会在关闭己方连接前有那么多事情要做吗？<br>还有一个问题，为什么有数千个连接都处于这个状态呢？难道那段时间内，服务器端总是主动拆除我们的连接吗？<br>不管怎么样，我们必须防止类似情况再度发生！<br>首先，我们要防止不断开辟新的端口，这可以通过设置SO_REUSEADDR套接字选项做到：<br>重用本地地址和端口<br>以前我总是一个端口不行，就换一个新的使用，所以导致让数千个端口进入CLOSE_WAIT状态。如果下次还发生这种尴尬状况，我希望加一个限定，只是当前这个端口处于CLOSE_WAIT状态！<br>在调用<br>sockConnected = socket(AF_INET, SOCK_STREAM, 0);<br>之后，我们要设置该套接字的选项来重用：<br>/// 允许重用本地地址和端口:<br>/// 这样的好处是，即使socket断了，调用前面的socket函数也不会占用另一个，而是始终就是一个端口<br>/// 这样防止socket始终连接不上，那么按照原来的做法，会不断地换端口。<br>int nREUSEADDR = 1;<br>setsockopt(sockConnected,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOL_SOCKET,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SO_REUSEADDR,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (const char*)&amp;nREUSEADDR,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(int)); </p>
<p>教科书上是这么说的：这样，假如服务器关闭或者退出，造成本地地址和端口都处于TIME_WAIT状态，那么SO_REUSEADDR就显得非常有用。<br>也许我们无法避免被冻结在CLOSE_WAIT状态永远不出现，但起码可以保证不会占用新的端口。<br>其次，我们要设置SO_LINGER套接字选项：（相关介绍可参考：SO_LINGER 选项设置）<br>从容关闭还是强行关闭？<br>LINGER是&#8220;拖延&#8221;的意思。<br>默认情况下(Win2k)，SO_DONTLINGER套接字选项的是1；SO_LINGER选项是，linger为{l_onoff：0，l_linger：0}。<br>如果在发送数据的过程中(send()没有完成，还有数据没发送)而调用了closesocket()，以前我们一般采取的措施是&#8220;从容关闭&#8221;：<br>因为在退出服务或者每次重新建立socket之前，我都会先调用<br>/// 先将双向的通讯关闭<br>&nbsp;&nbsp;&nbsp;&nbsp; shutdown(sockConnected, SD_BOTH);<br>&nbsp;&nbsp;&nbsp;&nbsp; /// 安全起见，每次建立Socket连接前，先把这个旧连接关闭<br>closesocket(sockConnected);<br>我们这次要这么做：<br>设置SO_LINGER为零（亦即linger结构中的l_onoff域设为非零，但l_linger为0），便不用担心closesocket调用进入&#8220;锁定&#8221;状态（等待完成），不论是否有排队数据未发送或未被确认。这种关闭方式称为&#8220;强行关闭&#8221;，因为套接字的虚电路立即被复位，尚未发出的所有数据都会丢失。在远端的recv()调用都会失败，并返回WSAECONNRESET错误。<br>在connect成功建立连接之后设置该选项：<br>linger m_sLinger;<br>m_sLinger.l_onoff = 1; // (在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)<br>m_sLinger.l_linger = 0; // (容许逗留的时间为0秒)<br>setsockopt(sockConnected,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOL_SOCKET,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SO_LINGER,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (const char*)&amp;m_sLinger,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(linger)); </p>
<p>总结<br>也许我们避免不了CLOSE_WAIT状态冻结的再次出现，但我们会使影响降到最小，希望那个重用套接字选项能够使得下一次重新建立连接时可以把CLOSE_WAIT状态踢掉。</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/factor2000/archive/2009/02/23/3929197.aspx">http://blog.csdn.net/factor2000/archive/2009/02/23/3929197.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/145458.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-05-01 15:55 <a href="http://www.cppblog.com/yehao/articles/145458.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Socket五种I/O模型——代码全攻略</title><link>http://www.cppblog.com/yehao/articles/145071.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Tue, 26 Apr 2011 09:30:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/145071.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/145071.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/145071.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/145071.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/145071.html</trackback:ping><description><![CDATA[<p>如果你想在Windows平台上构建服务器应用，那么I/O模型是你必须考虑的。Windows操作系统提供了选择（Select）、异步选择（WSAAsyncSelect）、事件选择（WSAEventSelect）、重叠I/O（Overlapped I/O）和完成端口（Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确，而且综合考虑到程序的扩展性和可移植性等因素，作出自己的选择。</p>
<p>我会以一个回应反射式服务器（与《Windows网络编程》第八章一样）来介绍这五种I/O模型。<br>我们假设客户端的代码如下（为代码直观，省去所有错误检查，以下同）：</p>
<p>#include &lt;WINSOCK2.H&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define SERVER_ADDRESS "137.117.2.148"<br>#define PORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient;<br>&nbsp; SOCKADDR_IN server;<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret;<br>&nbsp; <br>&nbsp; // Initialize Windows socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create client socket<br>&nbsp; sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Connect to server<br>&nbsp; memset(&amp;server, 0, sizeof(SOCKADDR_IN));<br>&nbsp; server.sin_family = AF_INET;<br>&nbsp; server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);<br>&nbsp; server.sin_port = htons(PORT);</p>
<p>&nbsp; connect(sClient, (struct sockaddr *)&amp;server, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; printf("Send:");<br>&nbsp; gets(szMessage);</p>
<p>&nbsp;&nbsp;&nbsp; // Send message<br>&nbsp;&nbsp;&nbsp; send(sClient, szMessage, strlen(szMessage), 0);</p>
<p>&nbsp;&nbsp;&nbsp; // Receive message<br>&nbsp;&nbsp;&nbsp; ret = recv(sClient, szMessage, MSGSIZE, 0);<br>&nbsp;&nbsp;&nbsp; szMessage[ret] = '\0';</p>
<p>&nbsp;&nbsp;&nbsp; printf("Received [%d bytes]: '%s'\n", ret, szMessage);<br>&nbsp; }</p>
<p>&nbsp; // Clean up<br>&nbsp; closesocket(sClient);<br>&nbsp; WSACleanup();<br>&nbsp; return 0;<br>}</p>
<p>客户端所做的事情相当简单，创建套接字，连接服务器，然后不停的发送和接收数据。</p>
<p>比较容易想到的一种服务器模型就是采用一个主线程，负责监听客户端的连接请求，当接收到某个客户端的连接请求后，创建一个专门用于和该客户端通信的套接字和一个辅助线程。以后该客户端和服务器的交互都在这个辅助线程内完成。这种方法比较直观，程序非常简单而且可移植性好，但是不能利用平台相关的特性。例如，如果连接数增多的时候（成千上万的连接），那么线程数成倍增长，操作系统忙于频繁的线程间切换，而且大部分线程在其生命周期内都是处于非活动状态的，这大大浪费了系统的资源。所以，如果你已经知道你的代码只会运行在Windows平台上，建议采用Winsock I/O模型。</p>
<p>一.选择模型<br>Select（选择）模型是Winsock中最常见的I/O模型。之所以称其为&#8220;Select模型&#8221;，是由于它的&#8220;中心思想&#8221;便是利用select函数，实现对I/O的管理。最初设计该模型时，主要面向的是某些使用UNIX操作系统的计算机，它们采用的是Berkeley套接字方案。Select模型已集成到Winsock 1.1中，它使那些想避免在套接字调用过程中被无辜&#8220;锁定&#8221;的应用程序，采取一种有序的方式，同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案，所以假如有一个Berkeley套接字应用使用了select函数，那么从理论角度讲，毋需对其进行任何修改，便可正常运行。（节选自《Windows网络编程》第八章)<br>下面的这段程序就是利用选择模型实现的Echo服务器的代码（已经不能再精简了）：</p>
<p>#include &lt;winsock.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE&nbsp;&nbsp;&nbsp; 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>int&nbsp;&nbsp;&nbsp; g_iTotalConn = 0;<br>SOCKET g_CliSocketArr[FD_SETSIZE];</p>
<p>DWORD WINAPI WorkerThread(LPVOID lpParameter);</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen, sClient;<br>&nbsp; SOCKADDR_IN local, client;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwThreadId;</p>
<p>&nbsp; // Initialize Windows socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create listening socket<br>&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Bind<br>&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp;local.sin_family = AF_INET;<br>&nbsp;local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; // Listen<br>&nbsp; listen(sListen, 3);</p>
<p>&nbsp; // Create worker thread<br>&nbsp; CreateThread(NULL, 0, WorkerThread, NULL, 0, &amp;dwThreadId);&nbsp; </p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; // Accept a connection<br>&nbsp;&nbsp;&nbsp; sClient = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));</p>
<p>&nbsp;&nbsp;&nbsp; // Add socket to g_CliSocketArr<br>&nbsp;&nbsp;&nbsp; g_CliSocketArr[g_iTotalConn++] = sClient;<br>&nbsp; }<br>&nbsp; <br>&nbsp; return 0;<br>}</p>
<p>DWORD WINAPI WorkerThread(LPVOID lpParam)<br>{<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i;<br>&nbsp; fd_set&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fdread;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret;<br>&nbsp; struct timeval tv = {1, 0};<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>&nbsp; <br>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; FD_ZERO(&amp;fdread);<br>&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; g_iTotalConn; i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FD_SET(g_CliSocketArr[i], &amp;fdread);<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; // We only care read event<br>&nbsp;&nbsp;&nbsp; ret = select(0, &amp;fdread, NULL, NULL, &amp;tv);</p>
<p>&nbsp;&nbsp;&nbsp; if (ret == 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Time expired<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; for (i = 0; i &lt; g_iTotalConn; i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (FD_ISSET(g_CliSocketArr[i], &amp;fdread))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // A read event happened on g_CliSocketArr[i]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = recv(g_CliSocketArr[i], szMessage, MSGSIZE, 0);<br>&nbsp;&nbsp;&nbsp; if (ret == 0 || (ret == SOCKET_ERROR &amp;&amp; WSAGetLastError() == WSAECONNRESET))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; // Client socket closed<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("Client socket %d closed.\n", g_CliSocketArr[i]);<br>&nbsp;&nbsp;&nbsp;&nbsp; closesocket(g_CliSocketArr[i]);<br>&nbsp;&nbsp;&nbsp;&nbsp; if (i &lt; g_iTotalConn - 1)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; // We received a message from client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[ret] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp; send(g_CliSocketArr[i], szMessage, strlen(szMessage), 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; <br>&nbsp; return 0;<br>}</p>
<p>服务器的几个主要动作如下：<br>1.创建监听套接字，绑定，监听；<br>2.创建工作者线程；<br>3.创建一个套接字数组，用来存放当前所有活动的客户端套接字，每accept一个连接就更新一次数组；<br>4.接受客户端的连接。这里有一点需要注意的，就是我没有重新定义FD_SETSIZE宏，所以服务器最多支持的并发连接数为64。而且，这里决不能无条件的accept,服务器应该根据当前的连接数来决定是否接受来自某个客户端的连接。一种比较好的实现方案就是采用WSAAccept函数，而且让WSAAccept回调自己实现的Condition Function。如下所示：</p>
<p>int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData)<br>{<br>&nbsp;if (当前连接数 &lt; FD_SETSIZE)<br>&nbsp; return CF_ACCEPT;<br>&nbsp;else<br>&nbsp; return CF_REJECT;<br>}</p>
<p>工作者线程里面是一个死循环，一次循环完成的动作是：<br>1.将当前所有的客户端套接字加入到读集fdread中；<br>2.调用select函数；<br>3.查看某个套接字是否仍然处于读集中，如果是，则接收数据。如果接收的数据长度为0，或者发生WSAECONNRESET错误，则表示客户端套接字主动关闭，这时需要将服务器中对应的套接字所绑定的资源释放掉，然后调整我们的套接字数组（将数组中最后一个套接字挪到当前的位置上）</p>
<p>除了需要有条件接受客户端的连接外，还需要在连接数为0的情形下做特殊处理，因为如果读集中没有任何套接字，select函数会立刻返回，这将导致工作者线程成为一个毫无停顿的死循环，CPU的占用率马上达到100%。</p>
<p>二.异步选择<br>Winsock提供了一个有用的异步I/O模型。利用这个模型，应用程序可在一个套接字上，接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后，调用WSAAsyncSelect函数。该模型最早出现于Winsock的1.1版本中，用于帮助应用程序开发者面向一些早期的16位Windows平台（如Windows for Workgroups），适应其&#8220;落后&#8221;的多任务消息环境。应用程序仍可从这种模型中得到好处，特别是它们用一个标准的Windows例程（常称为"WndProc"），对窗口消息进行管理的时候。该模型亦得到了Microsoft Foundation Class（微软基本类，MFC）对象CSocket的采纳。（节选自《Windows网络编程》第八章)<br>我还是先贴出代码，然后做详细解释：<br>#include &lt;winsock.h&gt;<br>#include &lt;tchar.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE&nbsp;&nbsp; 1024<br>#define WM_SOCKET WM_USER+0</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);</p>
<p>int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)<br>{<br>&nbsp; static TCHAR szAppName[] = _T("AsyncSelect Model");<br>&nbsp; HWND&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hwnd ;<br>&nbsp; MSG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; msg ;<br>&nbsp; WNDCLASS&nbsp;&nbsp;&nbsp;&nbsp; wndclass ;</p>
<p>&nbsp; wndclass.style&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = CS_HREDRAW | CS_VREDRAW ;<br>&nbsp; wndclass.lpfnWndProc&nbsp;&nbsp; = WndProc ;<br>&nbsp; wndclass.cbClsExtra&nbsp;&nbsp;&nbsp; = 0 ;<br>&nbsp; wndclass.cbWndExtra&nbsp;&nbsp;&nbsp; = 0 ;<br>&nbsp; wndclass.hInstance&nbsp;&nbsp;&nbsp;&nbsp; = hInstance ;<br>&nbsp; wndclass.hIcon&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = LoadIcon (NULL, IDI_APPLICATION) ;<br>&nbsp; wndclass.hCursor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = LoadCursor (NULL, IDC_ARROW) ;<br>&nbsp; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;<br>&nbsp; wndclass.lpszMenuName&nbsp; = NULL ;<br>&nbsp; wndclass.lpszClassName = szAppName ;</p>
<p>&nbsp; if (!RegisterClass(&amp;wndclass))<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;<br>&nbsp;&nbsp;&nbsp; return 0 ;<br>&nbsp; }</p>
<p>&nbsp; hwnd = CreateWindow (szAppName,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // window class name<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TEXT ("AsyncSelect Model"), // window caption<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WS_OVERLAPPEDWINDOW,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // window style<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // initial x position<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // initial y position<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // initial x size<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CW_USEDEFAULT,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // initial y size<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // parent window handle<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // window menu handle<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hInstance,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // program instance handle<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL) ;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // creation parameters</p>
<p>&nbsp; ShowWindow(hwnd, iCmdShow);<br>&nbsp; UpdateWindow(hwnd);</p>
<p>&nbsp; while (GetMessage(&amp;msg, NULL, 0, 0))<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; TranslateMessage(&amp;msg) ;<br>&nbsp;&nbsp;&nbsp; DispatchMessage(&amp;msg) ;<br>&nbsp; }<br>&nbsp; <br>&nbsp; return msg.wParam;<br>}</p>
<p>LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsd;<br>&nbsp; static SOCKET sListen;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient;<br>&nbsp; SOCKADDR_IN&nbsp;&nbsp; local, client;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret, iAddrSize = sizeof(client);<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];</p>
<p>&nbsp; switch (message)<br>&nbsp; {<br>&nbsp;case WM_CREATE:<br>&nbsp;&nbsp;&nbsp; // Initialize Windows Socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsd);<br>&nbsp; <br>&nbsp; // Create listening socket<br>&nbsp;&nbsp;&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp; // Bind<br>&nbsp;&nbsp;&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp; local.sin_family = AF_INET;<br>&nbsp; local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(local));<br>&nbsp; <br>&nbsp; // Listen<br>&nbsp;&nbsp;&nbsp; listen(sListen, 3);</p>
<p>&nbsp;&nbsp;&nbsp; // Associate listening socket with FD_ACCEPT event<br>&nbsp; WSAAsyncSelect(sListen, hwnd, WM_SOCKET, FD_ACCEPT);<br>&nbsp; return 0;</p>
<p>&nbsp; case WM_DESTROY:<br>&nbsp;&nbsp;&nbsp; closesocket(sListen);<br>&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp; PostQuitMessage(0);<br>&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp; <br>&nbsp; case WM_SOCKET:<br>&nbsp;&nbsp;&nbsp; if (WSAGETSELECTERROR(lParam))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(wParam);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; switch (WSAGETSELECTEVENT(lParam))<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp; case FD_ACCEPT:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Accept a connection from client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient = accept(wParam, (struct sockaddr *)&amp;client, &amp;iAddrSize);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Associate client socket with FD_READ and FD_CLOSE event<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSAAsyncSelect(sClient, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;</p>
<p>&nbsp;&nbsp;&nbsp; case FD_READ:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = recv(wParam, szMessage, MSGSIZE, 0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ret == 0 || ret == SOCKET_ERROR &amp;&amp; WSAGetLastError() == WSAECONNRESET)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(wParam);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[ret] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(wParam, szMessage, strlen(szMessage), 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; case FD_CLOSE:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(wParam);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp; }<br>&nbsp; <br>&nbsp; return DefWindowProc(hwnd, message, wParam, lParam);<br>}</p>
<p><br>&nbsp;在我看来，WSAAsyncSelect是最简单的一种Winsock I/O模型（之所以说它简单是因为一个主线程就搞定了）。使用Raw Windows API写过窗口类应用程序的人应该都能看得懂。这里，我们需要做的仅仅是：<br>1.在WM_CREATE消息处理函数中，初始化Windows Socket library，创建监听套接字，绑定，监听，并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件；<br>2.自定义一个消息WM_SOCKET，一旦在我们所关心的套接字（监听套接字和客户端套接字）上发生了某个事件，系统就会调用WndProc并且message参数被设置为WM_SOCKET；<br>3.在WM_SOCKET的消息处理函数中，分别对FD_ACCEPT、FD_READ和FD_CLOSE事件进行处理；<br>4.在窗口销毁消息(WM_DESTROY)的处理函数中，我们关闭监听套接字，清除Windows Socket library</p>
<p>下面这张用于WSAAsyncSelect函数的网络事件类型表可以让你对各个网络事件有更清楚的认识：<br>表1</p>
<p>FD_READ 应用程序想要接收有关是否可读的通知，以便读入数据 <br>FD_WRITE 应用程序想要接收有关是否可写的通知，以便写入数据 <br>FD_OOB 应用程序想接收是否有带外（OOB）数据抵达的通知 <br>FD_ACCEPT 应用程序想接收与进入连接有关的通知 <br>FD_CONNECT 应用程序想接收与一次连接或者多点join操作完成的通知 <br>FD_CLOSE 应用程序想接收与套接字关闭有关的通知 <br>FD_QOS 应用程序想接收套接字&#8220;服务质量&#8221;（QoS）发生更改的通知 <br>FD_GROUP_QOS&nbsp; 应用程序想接收套接字组&#8220;服务质量&#8221;发生更改的通知（现在没什么用处，为未来套接字组的使用保留） <br>FD_ROUTING_INTERFACE_CHANGE 应用程序想接收在指定的方向上，与路由接口发生变化的通知 <br>FD_ADDRESS_LIST_CHANGE&nbsp; 应用程序想接收针对套接字的协议家族，本地地址列表发生变化的通知 </p>
<p>三.事件选择<br>Winsock提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是，它也允许应用程序在一个或多个套接字上，接收以事件为基础的网络事件通知。对于表1总结的、由WSAAsyncSelect模型采用的网络事件来说，它们均可原封不动地移植到新模型。在用新模型开发的应用程序中，也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄，而非投递至一个窗口例程。（节选自《Windows网络编程》第八章)<br>还是让我们先看代码然后进行分析：<br>#include &lt;winsock2.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_iTotalConn = 0;<br>SOCKET&nbsp;&nbsp; g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];<br>WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];</p>
<p>DWORD WINAPI WorkerThread(LPVOID);<br>void Cleanup(int index);</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen, sClient;<br>&nbsp; SOCKADDR_IN local, client;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwThreadId;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);</p>
<p>&nbsp; // Initialize Windows Socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create listening socket<br>&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Bind<br>&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp;local.sin_family = AF_INET;<br>&nbsp;local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; // Listen<br>&nbsp; listen(sListen, 3);</p>
<p>&nbsp; // Create worker thread<br>&nbsp; CreateThread(NULL, 0, WorkerThread, NULL, 0, &amp;dwThreadId);</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; // Accept a connection<br>&nbsp;&nbsp;&nbsp; sClient = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));</p>
<p>&nbsp;&nbsp;&nbsp; // Associate socket with network event<br>&nbsp;&nbsp;&nbsp; g_CliSocketArr[g_iTotalConn] = sClient;<br>&nbsp;&nbsp;&nbsp; g_CliEventArr[g_iTotalConn] = WSACreateEvent();<br>&nbsp;&nbsp;&nbsp; WSAEventSelect(g_CliSocketArr[g_iTotalConn],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliEventArr[g_iTotalConn],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FD_READ | FD_CLOSE);<br>&nbsp;&nbsp;&nbsp; g_iTotalConn++;<br>&nbsp; }<br>}</p>
<p>DWORD WINAPI WorkerThread(LPVOID lpParam)<br>{<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret, index;<br>&nbsp; WSANETWORKEVENTS NetworkEvents;<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);<br>&nbsp;&nbsp;&nbsp; if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; index = ret - WSA_WAIT_EVENT_0;<br>&nbsp;&nbsp;&nbsp; WSAEnumNetworkEvents(g_CliSocketArr[index], g_CliEventArr[index], &amp;NetworkEvents);</p>
<p>&nbsp;&nbsp;&nbsp; if (NetworkEvents.lNetworkEvents &amp; FD_READ)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Receive message from client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = recv(g_CliSocketArr[index], szMessage, MSGSIZE, 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (ret == 0 || (ret == SOCKET_ERROR &amp;&amp; WSAGetLastError() == WSAECONNRESET))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cleanup(index);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[ret] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(g_CliSocketArr[index], szMessage, strlen(szMessage), 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if (NetworkEvents.lNetworkEvents &amp; FD_CLOSE)<br>&nbsp; {<br>&nbsp;&nbsp; Cleanup(index);<br>&nbsp; }<br>&nbsp; }<br>&nbsp; return 0;<br>}</p>
<p>void Cleanup(int index)<br>{<br>&nbsp; closesocket(g_CliSocketArr[index]);<br>&nbsp;WSACloseEvent(g_CliEventArr[index]);</p>
<p>&nbsp;if (index &lt; g_iTotalConn - 1)<br>&nbsp;{<br>&nbsp; g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];<br>&nbsp; g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];<br>&nbsp;}<br>&nbsp;<br>&nbsp;g_iTotalConn--;<br>}</p>
<p><br>事件选择模型也比较简单，实现起来也不是太复杂，它的基本思想是将每个套接字都和一个WSAEVENT对象对应起来，并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件（FD_READ和FD_CLOSE），与之相关联的WSAEVENT对象被Signaled。程序定义了两个全局数组，一个套接字数组，一个WSAEVENT对象数组，其大小都是MAXIMUM_WAIT_OBJECTS（64），两个数组中的元素一一对应。<br>同样的，这里的程序没有考虑两个问题，一是不能无条件的调用accept，因为我们支持的并发连接数有限。解决方法是将套接字按MAXIMUM_WAIT_OBJECTS分组，每MAXIMUM_WAIT_OBJECTS个套接字一组，每一组分配一个工作者线程；或者采用WSAAccept代替accept，并回调自己定义的Condition Function。第二个问题是没有对连接数为0的情形做特殊处理，程序在连接数为0的时候CPU占用率为100%。</p>
<p>四.重叠I/O模型<br>Winsock2的发布使得Socket I/O有了和文件I/O统一的接口。我们可以通过使用Win32文件操纵函数ReadFile和WriteFile来进行Socket I/O。伴随而来的，用于普通文件I/O的重叠I/O模型和完成端口模型对Socket I/O也适用了。这些模型的优点是可以达到更佳的系统性能，但是实现较为复杂，里面涉及较多的C语言技巧。例如我们在完成端口模型中会经常用到所谓的&#8220;尾随数据&#8221;。</p>
<p>1.用事件通知方式实现的重叠I/O模型<br>#include &lt;winsock2.h&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>typedef struct<br>{<br>&nbsp; WSAOVERLAPPED overlap;<br>&nbsp; WSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer;<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NumberOfBytesRecvd;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags;<br>}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;</p>
<p>int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_iTotalConn = 0;<br>SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];<br>WSAEVENT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliEventArr[MAXIMUM_WAIT_OBJECTS];<br>LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];</p>
<p>DWORD WINAPI WorkerThread(LPVOID);<br>void Cleanup(int);</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen, sClient;<br>&nbsp; SOCKADDR_IN local, client;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwThreadId;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);</p>
<p>&nbsp; // Initialize Windows Socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create listening socket<br>&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Bind<br>&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp;local.sin_family = AF_INET;<br>&nbsp;local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; // Listen<br>&nbsp; listen(sListen, 3);</p>
<p>&nbsp; // Create worker thread<br>&nbsp; CreateThread(NULL, 0, WorkerThread, NULL, 0, &amp;dwThreadId);</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; // Accept a connection<br>&nbsp;&nbsp;&nbsp; sClient = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));</p>
<p>&nbsp;&nbsp;&nbsp; g_CliSocketArr[g_iTotalConn] = sClient;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // Allocate a PER_IO_OPERATION_DATA structure<br>&nbsp;&nbsp;&nbsp; g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetProcessHeap(),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HEAP_ZERO_MEMORY,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp; g_pPerIODataArr[g_iTotalConn]-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp; g_pPerIODataArr[g_iTotalConn]-&gt;Buffer.buf = g_pPerIODataArr[g_iTotalConn]-&gt;szMessage;<br>&nbsp;&nbsp;&nbsp; g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]-&gt;overlap.hEvent = WSACreateEvent();</p>
<p>&nbsp;&nbsp;&nbsp; // Launch an asynchronous operation<br>&nbsp;&nbsp;&nbsp; WSARecv(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliSocketArr[g_iTotalConn],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[g_iTotalConn]-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[g_iTotalConn]-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[g_iTotalConn]-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[g_iTotalConn]-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; g_iTotalConn++;<br>&nbsp; }<br>&nbsp; <br>&nbsp; closesocket(sListen);<br>&nbsp; WSACleanup();<br>&nbsp; return 0;<br>}</p>
<p>DWORD WINAPI WorkerThread(LPVOID lpParam)<br>{<br>&nbsp; int&nbsp;&nbsp; ret, index;<br>&nbsp; DWORD cbTransferred;</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);<br>&nbsp;&nbsp;&nbsp; if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; index = ret - WSA_WAIT_EVENT_0;<br>&nbsp;&nbsp;&nbsp; WSAResetEvent(g_CliEventArr[index]);</p>
<p>&nbsp;&nbsp;&nbsp; WSAGetOverlappedResult(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliSocketArr[index],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[index]-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;cbTransferred,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TRUE,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[g_iTotalConn]-&gt;Flags);</p>
<p>&nbsp;&nbsp;&nbsp; if (cbTransferred == 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // The connection was closed by client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cleanup(index);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // g_pPerIODataArr[index]-&gt;szMessage contains the received data<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_pPerIODataArr[index]-&gt;szMessage[cbTransferred] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(g_CliSocketArr[index], g_pPerIODataArr[index]-&gt;szMessage,\<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cbTransferred, 0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Launch another asynchronous operation<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_CliSocketArr[index],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[index]-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[index]-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[index]-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;g_pPerIODataArr[index]-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }</p>
<p>&nbsp; return 0;<br>}</p>
<p>void Cleanup(int index)<br>{<br>&nbsp; closesocket(g_CliSocketArr[index]);<br>&nbsp; WSACloseEvent(g_CliEventArr[index]);<br>&nbsp; HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);</p>
<p>&nbsp; if (index &lt; g_iTotalConn - 1)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];<br>&nbsp;&nbsp;&nbsp; g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];<br>&nbsp;&nbsp;&nbsp; g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];<br>&nbsp; }</p>
<p>&nbsp; g_pPerIODataArr[--g_iTotalConn] = NULL;<br>}</p>
<p><br>&nbsp;这个模型与上述其他模型不同的是它使用Winsock2提供的异步I/O函数WSARecv。在调用WSARecv时，指定一个WSAOVERLAPPED结构，这个调用不是阻塞的，也就是说，它会立刻返回。一旦有数据到达的时候，被指定的WSAOVERLAPPED结构中的hEvent被Signaled。由于下面这个语句<br>g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]-&gt;overlap.hEvent；<br>使得与该套接字相关联的WSAEVENT对象也被Signaled，所以WSAWaitForMultipleEvents的调用操作成功返回。我们现在应该做的就是用与调用WSARecv相同的WSAOVERLAPPED结构为参数调用WSAGetOverlappedResult，从而得到本次I/O传送的字节数等相关信息。在取得接收的数据后，把数据原封不动的发送到客户端，然后重新激活一个WSARecv异步操作。</p>
<p>2.用完成例程方式实现的重叠I/O模型<br>#include &lt;WINSOCK2.H&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>typedef struct<br>{<br>&nbsp;WSAOVERLAPPED overlap;<br>&nbsp;WSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer;<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NumberOfBytesRecvd;<br>&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags; <br>&nbsp;SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient;<br>}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;</p>
<p>DWORD WINAPI WorkerThread(LPVOID);<br>void CALLBACK CompletionROUTINE(DWORD, DWORD, LPWSAOVERLAPPED, DWORD);</p>
<p>SOCKET g_sNewClientConnection;<br>BOOL&nbsp;&nbsp; g_bNewConnectionArrived = FALSE;</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen;<br>&nbsp; SOCKADDR_IN local, client;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwThreadId;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);</p>
<p>&nbsp; // Initialize Windows Socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create listening socket<br>&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Bind<br>&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp;local.sin_family = AF_INET;<br>&nbsp;local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; // Listen<br>&nbsp; listen(sListen, 3);</p>
<p>&nbsp; // Create worker thread<br>&nbsp; CreateThread(NULL, 0, WorkerThread, NULL, 0, &amp;dwThreadId);</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; // Accept a connection<br>&nbsp;&nbsp;&nbsp; g_sNewClientConnection = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>&nbsp;&nbsp;&nbsp; g_bNewConnectionArrived = TRUE;<br>&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));<br>&nbsp; }<br>}</p>
<p>DWORD WINAPI WorkerThread(LPVOID lpParam)<br>{<br>&nbsp;LPPER_IO_OPERATION_DATA lpPerIOData = NULL;</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; if (g_bNewConnectionArrived)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Launch an asynchronous operation for new arrived connection<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetProcessHeap(),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HEAP_ZERO_MEMORY,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;sClient = g_sNewClientConnection;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(lpPerIOData-&gt;sClient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionROUTINE);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; g_bNewConnectionArrived = FALSE;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; SleepEx(1000, TRUE);<br>&nbsp; }<br>&nbsp; return 0;<br>}</p>
<p>void CALLBACK CompletionROUTINE(DWORD dwError,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD cbTransferred,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPWSAOVERLAPPED lpOverlapped,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD dwFlags)<br>{<br>&nbsp; LPPER_IO_OPERATION_DATA lpPerIOData = (LPPER_IO_OPERATION_DATA)lpOverlapped;<br>&nbsp; <br>&nbsp; if (dwError != 0 || cbTransferred == 0)<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp; // Connection was closed by client<br>&nbsp; closesocket(lpPerIOData-&gt;sClient);<br>&nbsp; HeapFree(GetProcessHeap(), 0, lpPerIOData);<br>&nbsp;}<br>&nbsp; else<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;szMessage[cbTransferred] = '\0';<br>&nbsp;&nbsp;&nbsp; send(lpPerIOData-&gt;sClient, lpPerIOData-&gt;szMessage, cbTransferred, 0);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // Launch another asynchronous operation<br>&nbsp;&nbsp;&nbsp; memset(&amp;lpPerIOData-&gt;overlap, 0, sizeof(WSAOVERLAPPED));<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; WSARecv(lpPerIOData-&gt;sClient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionROUTINE);<br>&nbsp; }<br>}</p>
<p>用完成例程来实现重叠I/O比用事件通知简单得多。在这个模型中，主线程只用不停的接受连接即可；辅助线程判断有没有新的客户端连接被建立，如果有，就为那个客户端套接字激活一个异步的WSARecv操作，然后调用SleepEx使线程处于一种可警告的等待状态，以使得I/O完成后CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx，则内核在完成一次I/O操作后，无法调用完成例程（因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内）。<br>完成例程内的实现代码比较简单，它取出接收到的数据，然后将数据原封不动的发送给客户端，最后重新激活另一个WSARecv异步操作。注意，在这里用到了&#8220;尾随数据&#8221;。我们在调用WSARecv的时候，参数lpOverlapped实际上指向一个比它大得多的结构PER_IO_OPERATION_DATA，这个结构除了WSAOVERLAPPED以外，还被我们附加了缓冲区的结构信息，另外还包括客户端套接字等重要的信息。这样，在完成例程中通过参数lpOverlapped拿到的不仅仅是WSAOVERLAPPED结构，还有后边尾随的包含客户端套接字和接收数据缓冲区等重要信息。这样的C语言技巧在我后面介绍完成端口的时候还会使用到。</p>
<p>五.完成端口模型<br>&#8220;完成端口&#8221;模型是迄今为止最为复杂的一种I/O模型。然而，假若一个应用程序同时需要管理为数众多的套接字，那么采用这种模型，往往可以达到最佳的系统性能！但不幸的是，该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性，只有在你的应用程序需要同时管理数百乃至上千个套接字的时候，而且希望随着系统内安装的CPU数量的增多，应用程序的性能也可以线性提升，才应考虑采用&#8220;完成端口&#8221;模型。要记住的一个基本准则是，假如要为Windows NT或Windows 2000开发高性能的服务器应用，同时希望为大量套接字I/O请求提供服务（Web服务器便是这方面的典型例子），那么I/O完成端口模型便是最佳选择！（节选自《Windows网络编程》第八章）<br>完成端口模型是我最喜爱的一种模型。虽然其实现比较复杂（其实我觉得它的实现比用事件通知实现的重叠I/O简单多了），但其效率是惊人的。我在T公司的时候曾经帮同事写过一个邮件服务器的性能测试程序，用的就是完成端口模型。结果表明，完成端口模型在多连接（成千上万）的情况下，仅仅依靠一两个辅助线程，就可以达到非常高的吞吐量。下面我还是从代码说起：<br>#include &lt;WINSOCK2.H&gt;<br>#include &lt;stdio.h&gt;</p>
<p>#define PORT&nbsp;&nbsp;&nbsp; 5150<br>#define MSGSIZE 1024</p>
<p>#pragma comment(lib, "ws2_32.lib")</p>
<p>typedef enum<br>{<br>&nbsp; RECV_POSTED<br>}OPERATION_TYPE;</p>
<p>typedef struct<br>{<br>&nbsp;WSAOVERLAPPED&nbsp; overlap;<br>&nbsp;WSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Buffer;<br>&nbsp; char&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; szMessage[MSGSIZE];<br>&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NumberOfBytesRecvd;<br>&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags;<br>&nbsp;OPERATION_TYPE OperationType;<br>}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;</p>
<p>DWORD WINAPI WorkerThread(LPVOID);</p>
<p>int main()<br>{<br>&nbsp; WSADATA&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; wsaData;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sListen, sClient;<br>&nbsp; SOCKADDR_IN&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; local, client;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i, dwThreadId;<br>&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; iaddrSize = sizeof(SOCKADDR_IN);<br>&nbsp; HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort = INVALID_HANDLE_VALUE;<br>&nbsp; SYSTEM_INFO&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; systeminfo;<br>&nbsp; LPPER_IO_OPERATION_DATA lpPerIOData = NULL;</p>
<p>&nbsp; // Initialize Windows Socket library<br>&nbsp; WSAStartup(0x0202, &amp;wsaData);</p>
<p>&nbsp; // Create completion port<br>&nbsp; CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);</p>
<p>&nbsp; // Create worker thread<br>&nbsp; GetSystemInfo(&amp;systeminfo);<br>&nbsp; for (i = 0; i &lt; systeminfo.dwNumberOfProcessors; i++)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &amp;dwThreadId);<br>&nbsp; }<br>&nbsp; <br>&nbsp; // Create listening socket<br>&nbsp; sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</p>
<p>&nbsp; // Bind<br>&nbsp; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);<br>&nbsp;local.sin_family = AF_INET;<br>&nbsp;local.sin_port = htons(PORT);<br>&nbsp; bind(sListen, (struct sockaddr *)&amp;local, sizeof(SOCKADDR_IN));</p>
<p>&nbsp; // Listen<br>&nbsp; listen(sListen, 3);</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; // Accept a connection<br>&nbsp;&nbsp;&nbsp; sClient = accept(sListen, (struct sockaddr *)&amp;client, &amp;iaddrSize);<br>&nbsp;&nbsp;&nbsp; printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));</p>
<p>&nbsp;&nbsp;&nbsp; // Associate the newly arrived client socket with completion port<br>&nbsp;&nbsp;&nbsp; CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)sClient, 0);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; // Launch an asynchronous operation for new arrived connection<br>&nbsp;&nbsp;&nbsp; lpPerIOData = (LPPER_IO_OPERATION_DATA)HeapAlloc(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetProcessHeap(),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HEAP_ZERO_MEMORY,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage;<br>&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;OperationType = RECV_POSTED;<br>&nbsp;&nbsp;&nbsp; WSARecv(sClient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp; }</p>
<p>&nbsp; PostQueuedCompletionStatus(CompletionPort, 0xFFFFFFFF, 0, NULL);<br>&nbsp;CloseHandle(CompletionPort);<br>&nbsp;closesocket(sListen);<br>&nbsp;WSACleanup();<br>&nbsp;return 0;<br>}</p>
<p>DWORD WINAPI WorkerThread(LPVOID CompletionPortID)<br>{<br>&nbsp; HANDLE&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort=(HANDLE)CompletionPortID;<br>&nbsp; DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dwBytesTransferred;<br>&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sClient;<br>&nbsp; LPPER_IO_OPERATION_DATA lpPerIOData = NULL;</p>
<p>&nbsp; while (TRUE)<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; GetQueuedCompletionStatus(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CompletionPort,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;dwBytesTransferred,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;sClient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPOVERLAPPED *)&amp;lpPerIOData,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; INFINITE);<br>&nbsp;&nbsp;&nbsp; if (dwBytesTransferred == 0xFFFFFFFF)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; if (lpPerIOData-&gt;OperationType == RECV_POSTED)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (dwBytesTransferred == 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Connection was closed by client<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(sClient);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HeapFree(GetProcessHeap(), 0, lpPerIOData);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;szMessage[dwBytesTransferred] = '\0';<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; send(sClient, lpPerIOData-&gt;szMessage, dwBytesTransferred, 0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Launch another asynchronous operation for sClient<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(lpPerIOData, 0, sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.len = MSGSIZE;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;Buffer.buf = lpPerIOData-&gt;szMessage;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpPerIOData-&gt;OperationType = RECV_POSTED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(sClient,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Buffer,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;NumberOfBytesRecvd,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;Flags,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;lpPerIOData-&gt;overlap,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp;return 0;<br>}</p>
<p><br>&nbsp;首先，说说主线程：<br>1.创建完成端口对象<br>2.创建工作者线程（这里工作者线程的数量是按照CPU的个数来决定的，这样可以达到最佳性能）<br>3.创建监听套接字，绑定，监听，然后程序进入循环<br>4.在循环中，我做了以下几件事情：<br>&nbsp;(1).接受一个客户端连接<br>&nbsp;(2).将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort，但这次的作用不同)，注意，按道理来讲，此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键，一般来讲，程序都是传递一个单句柄数据结构的地址，该单句柄数据包含了和该客户端连接有关的信息，由于我们只关心套接字句柄，所以直接将套接字句柄作为完成键传递；<br>&nbsp;(3).触发一个WSARecv异步调用，这次又用到了&#8220;尾随数据&#8221;，使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后，此外，还有操作类型等重要信息。<br>&nbsp;<br>在工作者线程的循环中，我们<br>1.调用GetQueuedCompletionStatus取得本次I/O的相关信息（例如套接字句柄、传送的字节数、单I/O数据结构的地址等等）<br>2.通过单I/O数据结构找到接收数据缓冲区，然后将数据原封不动的发送到客户端<br>3.再次触发一个WSARecv异步操作</p>
<p>六.五种I/O模型的比较<br>我会从以下几个方面来进行比较<br>*有无每线程64连接数限制<br>如果在选择模型中没有重新定义FD_SETSIZE宏，则每个fd_set默认可以装下64个SOCKET。同样的，受MAXIMUM_WAIT_OBJECTS宏的影响，事件选择、用事件通知实现的重叠I/O都有每线程最大64连接数限制。如果连接数成千上万，则必须对客户端套接字进行分组，这样，势必增加程序的复杂度。<br>相反，异步选择、用完成例程实现的重叠I/O和完成端口不受此限制。</p>
<p>*线程数<br>除了异步选择以外，其他模型至少需要2个线程。一个主线程和一个辅助线程。同样的，如果连接数大于64，则选择模型、事件选择和用事件通知实现的重叠I/O的线程数还要增加。</p>
<p>*实现的复杂度<br>我的个人看法是，在实现难度上，异步选择&lt;选择&lt;用完成例程实现的重叠I/O&lt;事件选择&lt;完成端口&lt;用事件通知实现的重叠I/O</p>
<p>*性能<br>由于选择模型中每次都要重设读集，在select函数返回后还要针对所有套接字进行逐一测试，我的感觉是效率比较差；完成端口和用完成例程实现的重叠I/O基本上不涉及全局数据，效率应该是最高的，而且在多处理器情形下完成端口还要高一些；事件选择和用事件通知实现的重叠I/O在实现机制上都是采用WSAWaitForMultipleEvents，感觉效率差不多；至于异步选择，不好比较。所以我的结论是:选择&lt;用事件通知实现的重叠I/O&lt;事件选择&lt;用完成例程实现的重叠I/O&lt;完成端口<br></p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/mlite/archive/2006/04/30/699340.aspx">http://blog.csdn.net/mlite/archive/2006/04/30/699340.aspx</a></p>
<img src ="http://www.cppblog.com/yehao/aggbug/145071.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-04-26 17:30 <a href="http://www.cppblog.com/yehao/articles/145071.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解完成端口(IO completion port) </title><link>http://www.cppblog.com/yehao/articles/144909.html</link><dc:creator>厚积薄发</dc:creator><author>厚积薄发</author><pubDate>Sun, 24 Apr 2011 10:59:00 GMT</pubDate><guid>http://www.cppblog.com/yehao/articles/144909.html</guid><wfw:comment>http://www.cppblog.com/yehao/comments/144909.html</wfw:comment><comments>http://www.cppblog.com/yehao/articles/144909.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yehao/comments/commentRss/144909.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yehao/services/trackbacks/144909.html</trackback:ping><description><![CDATA[<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/kevinlynx/archive/2008/01/27/2068739.aspx">http://blog.csdn.net/kevinlynx/archive/2008/01/27/2068739.aspx</a></p>
<p><br>&nbsp; 关于完成端口网上有很多文章，不过我个人觉得大多都讲得不够清楚。给的例子要不就是给一个复杂的封装，要不就是给一个简单的收发数据。注意，完成端口不仅仅用于网络数据的收发，它可以用于windows 平台的各种IO操作。不过我这里只关注在winsock编程中的应用。</p>
<p>&nbsp;&nbsp;&nbsp; 要写出一篇真的让人能够明白的文章，不那么容易。这里我只暂时贴些我的理解。迟些时候如果有空的话，我倒有兴趣写个详细的入门文章。</p>
<p>1.26.2008</p>
<p>Kevin Lynx</p>
<p>&nbsp;<br>理解完成端口：<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 就目前所了解的信息来说，完成端口通常都会与重叠IO有关联。完成端口可被看作是一个队列。各种操作都会被放到该队列里，程序在迟些时候查询此队列获取之前提交的IO操作结果。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注意，无论是重叠IO还是完成端口，都不仅仅用于socket的操作，他们是用于各种IO的操作。</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/kevinlynx/4_gif_818.gif"><br>IOCP不是为每一个客户端连接建立一个线程。</p>
<p>要区分IOCP和事件通知模型的区别，事件通知模型是先得到事件，然后根据事件类型去获取或者发送数据；而IOCP则是先提交动作（发送或接收），后得到通知，当得到通知时，通常就意味着之前提交的动作已经完成了。</p>
<p>&nbsp;<br>When you use IOCP, you spawn a pool of threads once - and they are used to handle the network I/O in your application. Technically, in Windows 2000, you don't even have to spawn the pool yourself - you can let Windows take care of the spawning and management of the threads in the pool。</p>
<p>IOCP其实也属于一种同步对象，就像Windows里的Event对象一样。IOCP用于同步IO操作。在异步IO操作中，提交了一个异步操作后，某些时候就需要得知操作的结果，也就是同步一下。</p>
<p>&nbsp;<br><a href="http://www.codeproject.com/KB/IP/iocp_server_client.aspx">http://www.codeproject.com/KB/IP/iocp_server_client.aspx</a><br>A server application is fairly meaningless if it cannot service multiple clients at the same time, usually asynchronous I/O calls and multithreading is used for this purpose. By definition, an asynchronous I/O call returns immediately, leaving the I/O call pending. At some point of time, the result of the I/O asynchronous call must be synchronized with the main thread. This can be done in different ways. The synchronization can be performed by:<br>&#8226;Using events - A signal is set as soon as the asynchronous call is finished. The disadvantage of this approach is that the thread has to check or wait for the event to be set. &#8226;Using the GetOverlappedResult function - This approach has the same disadvantage as the approach above. &#8226;Using Asynchronous Procedure Calls (or APC) - There are several disadvantages associated with this approach. First, the APC is always called in the context of the calling thread, and second, in order to be able to execute the APCs, the calling thread has to be suspended in the so called alterable wait state. &#8226;Using IOCP - The disadvantage of this approach is that there are many practical thorny programming problems that must be solved. Coding IOCP can be a bit of a hassle.&nbsp; <br>这里我要特别强调一下异步IO和非阻塞IO的区别，异步IO就是把IO提交给系统，让系统替你做，做完了再用某种方式通知你；非阻塞IO就是你要通过某种方式不定时地向系统询问你是否可以开始做某个IO，当可以开始后，还是要自己来完成IO。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 不可以通过接受数据是否为0来判断客户端是否断开，只有当调用closesocket时才可以通过这个方法判断，如果是意外退出（断电，网络故障），则判断不出。这个时候可以采用定时发送数据（心跳信息）来确认。</p>
<p>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建IOCP程序，一般的步骤：</p>
<p>1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建一个单句柄数据结构体，该结构体里一般都包含一个套接字数据。因为IOCP实际上会有固定的几个线程（工作线程），这些线程在IOCP结果队列里查询IO操作结果。这些结果不止是在一个套接字上进行的操作（读或写），而是包括了所有与该IOCP对象关联起来的套接字上的操作结果。因此，为了区分某次操作结果属于哪个套接字，就需要这个单句柄数据结构里包含这个套接字句柄。</p>
<p>2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建一个以OVERLAPPED为首个元素的数据结构体。该结构体实际上对应着一个IO操作（例如WSASend）。对于 WSASend, WSARecv以及查询操作结果函数都需要一个OVERLAPPED参数（一般是指针），通常情况下我们需要更多的数据，因此定义的这个结构体里通常包含了更多的数据（例如WSABUF，它可以用来容纳WSARecv接受到的数据）。之所以要把OVERLAPPED 作为这个结构体的第一个元素，是为了在使用查询函数GetQueuedCompletionStatus后，可以通过该函数返回的OVERLAPPED类型的指针得到我们这里定义的结构体对象地址，从而获取更多的数据。</p>
<p>3.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 每一次接受到新的连接时(accept)，都将这个新的套接字与完成端口相关联。并且创建一个单句柄对象（也就是完成键）。每一个套接字都有一个关联的单句柄对象。而每一个IO操作都有一个关联的OVERLAPPED相关的数据结构（上一步定义的结构体）。</p>
<p>4.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以在任何时候提交异步IO请求，例如WSASend, WSARecv。这里需要为OVERLAPPED相关的结构体指定操作类型。一个典型的结构体为（即第二步定义的结构体）：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct IOContext<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /// 很多函数需要此参数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED ol;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /// 存放接受数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char buf[MAX_BUF];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ///<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSABUF wsabuf;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /// 操作类型，提交IO操作时指定该值，在查询操作结果时，可以重新获取到该值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int op_type;<br>};<br>在创建该结构体的变量时，为op_type指定一个值。然后将此结构体的地址给WSASend之类的函数。在工作者线程中执行查询时，实际上得到了该结构体的地址（结构体变量），那么，就可以获取op_type的值。<br>注意：查询结构只能获取IO操作的字节数(以及IO操作结果数据)，不能知道IO操作的类型。所以IO操作的类型实际上是在这里用户自己指定的。<br>当执行WSASend时，设置op_type为SEND（自己定义的常量），执行WSARecv时，指定READ。然后在查询结果时，可以根据op_type知道这个操作结果是什么类型。如果是SEND，那么就表示之前提交的WSASend操作。<br>IOCP是一个异步操作机制，之所以是异步，就是因为可以随时提交IO操作。提交之后具体的操作由系统为你完成。完成后就需要某种机制来得知操作结果。IOCP设置的这个结果队列就是一种机制。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;<br>5.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以通过PostQueuedCompletionStatus手动地往结果队列里放置一个操作结果。通常这个函数都用于让工作者线程退出。例如：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PostQueuedCompletionStatus( cp_handle, 0, NULL, NULL );<br>然后在工作者线程里：</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = GetQueuedCompletionStatus( cp_handle, &amp;transfer_bytes, (PULONG_PTR) &amp;hc, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPOVERLAPPED*)&amp;ic, INFINITE );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( ic == NULL )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf( "ic == NULL\n" );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用PostQueuedCompletionStatus传递过来的数据，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这里约定ic==NULL时退出<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>6.&nbsp;&nbsp;&nbsp;&nbsp; 如果不提交任何IO操作，那么结果队列里很有可能一直都是空的。那么GetQueued这个查询函数就会一直得不到数据。<br>&nbsp;<br>7.&nbsp;&nbsp;&nbsp;&nbsp; 纵观IOCP程序，一个比较复杂的地方在于资源的释放。在接收到一个新的连接时，会为这个连接创建单句柄数据，执行IO操作的话，还要创建OVERLAPPED相关结构体变量。这些变量的地址都会在工作者线程中通过GetQueued..函数获取，并在工作者线程中使用。一个比较直接的做法是在工作者线程中释放这些资源。<br>&nbsp;<br>&nbsp;<br>&nbsp;<br>推荐些文章：</p>
<p>几种socket模型的代码：<a href="http://blog.csdn.net/mlite/archive/2006/04/30/699340.aspx">http://blog.csdn.net/mlite/archive/2006/04/30/699340.aspx</a> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 又一个简单的IOCP代码：<a href="http://www.go321.cn/html/app/cpp/20070526/30207.html">http://www.go321.cn/html/app/cpp/20070526/30207.html</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另一个例子，那幅图有点意义：<a href="http://www.3800hk.com/Article/cxsj/vc/wllbcvc/2005-08-25/Article_54111.html">http://www.3800hk.com/Article/cxsj/vc/wllbcvc/2005-08-25/Article_54111.html</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; codeproject上的文章：<a href="http://www.codeproject.com/KB/IP/iocp_server_client.aspx">http://www.codeproject.com/KB/IP/iocp_server_client.aspx</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; codeproject上的另一篇：<a href="http://www.codeproject.com/KB/IP/jbsocketserver1.aspx">http://www.codeproject.com/KB/IP/jbsocketserver1.aspx</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MSDN上的，前部分由点意义：<a href="http://msdn.microsoft.com/msdnmag/issues/1000/winsock/">http://msdn.microsoft.com/msdnmag/issues/1000/winsock/</a><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其实完成端口的例子在细节上有很多方法，例如accept, AcceptEx之类，对于accept的处理尤其多。这些无疑又给初学者带来了迷惑。我觉得只要把握住几个要点就行了：异步操作，结果队列，数据的传送（提交操作时传进去，查询时取出来），工作者线程</p>
<img src ="http://www.cppblog.com/yehao/aggbug/144909.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yehao/" target="_blank">厚积薄发</a> 2011-04-24 18:59 <a href="http://www.cppblog.com/yehao/articles/144909.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>