﻿<?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++博客-Dark Angle-随笔分类-net work</title><link>http://www.cppblog.com/niewenlong/category/4930.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 23 Aug 2008 02:20:28 GMT</lastBuildDate><pubDate>Sat, 23 Aug 2008 02:20:28 GMT</pubDate><ttl>60</ttl><item><title>如何成为盛大的尊贵帐号“白金账号”</title><link>http://www.cppblog.com/niewenlong/archive/2008/03/13/44343.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Thu, 13 Mar 2008 02:54:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2008/03/13/44343.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/44343.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2008/03/13/44343.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/44343.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/44343.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: <br><br>如何成为盛大的尊贵帐号“白金账号”<br><br>盛大推广员帐号：wsrynwl<br>盛大通行证注册：http://register.sdo.com/sdo/reg/inputinfo.asp<br><br> <br><br>盛大白金帐号注册地址： http://tg.sdo.com/691821554             http://tg.sdo.com/691821554<br><br>注册盛大游戏通行证时，请记得填写推广员帐号：wsrynwl 当游戏角色达到相应等级时，均可获得丰厚的游戏奖励品。<br><br><br>新老玩家也是可以通过绑定推广员帐号：wsrynwl来获得奖励的！必须满足以下条件：<br><br>从未持有过点券（元宝）”指游戏帐号从未充过值，从未和其他玩家交易过点券并且从未从任何渠道获得赠送的点券。<br><br>新老用户绑定推广员帐号地址：http://178.sdo.com/web/ConsumerBindUser.aspx<br>&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2008/03/13/44343.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/44343.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2008-03-13 10:54 <a href="http://www.cppblog.com/niewenlong/archive/2008/03/13/44343.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>百分之百解决ARP欺骗、聚生网管、P2P终结者导致频繁掉线不能上网的问题</title><link>http://www.cppblog.com/niewenlong/archive/2008/02/27/43309.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Tue, 26 Feb 2008 19:37:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2008/02/27/43309.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/43309.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2008/02/27/43309.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/43309.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/43309.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: <br>近一两年来P2P终结者、聚生网管等等的ARP欺骗软件烦恼了不少局域网共享上网的用户，多数情况是内网用户被限速或无故断网。如果同一局域网内有两个或以上用户装了这些软件的话，网管就够头痛的了。。。ARP欺骗软件，你装别人也装，就形成所谓的恶性循环。<br><br>在网上也看到不少地方不上论坛在讨论解决之道，但始终都没有好的方法出来，今天不小心在电脑故障网上看到个好方法，肯定能够很好的解决，我稍微解释一下，介绍下精华吧，也当是给自己留个备份了，^_^ 究竟是什么方法能够很好地解决这个问题呢？那就是PPPOE SERVER (内网用户也像ADSL一样要拨号，通过路由检测用户名和密码后才获得IP上网，各用户之间互不影响。这样就加强了内网的管理和稳定，下面是我改良了的初级教程，如果有什么不正确的话也希望您能指正，错漏之处敬请原谅。原文来自电脑故障网论坛故障解决版块的版主dngz同志。在此表示感谢，另外图片我照搬过来了，请别介意 :-)&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2008/02/27/43309.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/43309.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2008-02-27 03:37 <a href="http://www.cppblog.com/niewenlong/archive/2008/02/27/43309.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ARP防范</title><link>http://www.cppblog.com/niewenlong/archive/2008/02/27/43305.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Tue, 26 Feb 2008 17:04:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2008/02/27/43305.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/43305.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2008/02/27/43305.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/43305.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/43305.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 方法一:<br>目前发现，唯一可以称做终极的解决ARP病毒（恶意软件除外）的方法就是：<br>删除system32npptools.dll，我这里删除了一个月了，从来没中过ARP病毒，也无任何不良反映，ARP病毒缺少了npptools.dll这个文件根本不能运行，目前所发现的ARP病毒通通提示npptools.dll出错，无法运行<br>暂时还没发现可以自动生成npptools.dll的病毒，npptools.dll本身就40多K，病毒如果还要生成自己的运行库的话，不是几十K的大小就可以办到的，再大一些的就不是病毒了<br>当然，还是要做ARP -S绑定，只绑定本机自身跟路由即可，可以在“一定程度上”减少ARP程序的破坏<br><br>另外，防止arp攻击的软件有 anti arp sniffer ，最新版为3.5，在百度上搜寻即可！<br>另外，据闻，目前唯一能防止arp攻击的软件防火墙是lns ：look n stop 最新版是sp3版。需要自定arp规则。<br>&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2008/02/27/43305.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/43305.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2008-02-27 01:04 <a href="http://www.cppblog.com/niewenlong/archive/2008/02/27/43305.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解I/O Completion Port</title><link>http://www.cppblog.com/niewenlong/archive/2007/10/09/33806.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Tue, 09 Oct 2007 03:59:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/10/09/33806.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/33806.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/10/09/33806.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/33806.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/33806.html</trackback:ping><description><![CDATA[<font size=2>　　欢迎阅读此篇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/niewenlong/aggbug/33806.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-10-09 11:59 <a href="http://www.cppblog.com/niewenlong/archive/2007/10/09/33806.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>细说WinSock IOCP</title><link>http://www.cppblog.com/niewenlong/archive/2007/10/09/33805.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Tue, 09 Oct 2007 03:57:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/10/09/33805.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/33805.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/10/09/33805.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/33805.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/33805.html</trackback:ping><description><![CDATA[<p>
<table style="TABLE-LAYOUT: fixed; WORD-WRAP: break-word" height="100%" cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td vAlign=top><font size=2><font face=Arial><span class=bold><span class=smalltxt>第&nbsp;1&nbsp;楼：细说WinSock IOCP<br><br></span></span><br></font></font>1.Socket Modes",什么是"Socket I/O Models"? <br>Socket Modes : Determines how winsock functions behave when called with a socket. <br>Socket I/O Models : Describes how an application manages and processes I/O on a socket. <br><br>2.那么Winsock提供了哪几种"Socket Modes"给程序员用呢? <br>　　a.Blocking Mode(阻塞式的) <br>　　b.Noblocking Mode(非阻塞式的) <br><br>3.详细解释一下阻塞和非阻塞. <br>我们那最常用的send和recv两个函数来说吧... <br>比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:对于阻塞模式的socket send函数将不返回直到系统缓冲区有足够的空间把你要发送的数据Copy过去以后才返回,而对于阻塞的socket来说send会立即返回WSAEWOULDDBLOCK告诉调用者说:"发送操作被阻塞了!!!你想办法处理吧..." <br>对于recv函数,同样道理,该函数的内部工作机制其实是在等待TCP/IP协议栈的接收缓冲区通知它说:嗨,你的数据来了.对于阻塞模式的socket来说如果TCP/IP协议栈的接收缓冲区没有通知一个结果给它它就一直不返回:耗费着系统资源....对于阻塞模式的socket该函数会马上返回,然后告诉你:WSAEWOULDDBLOCK. <br><br>3.对于阻塞模式(Blocking Mode)我采取开线程的方式不就搞定了吗,而且还不用我去判断基本的收/发什么时候完成,多省心? <br>　　没错,这位大虾说的开线程的方式具体的做法如下:我们将应用划分成一个读线程(Reading Thread)一个工作线程(Working Thread),两个线程*同步对象(Synchronization Object,可以是Event,Mutex...)来共享一个数据缓冲区,读线程(Reading Thread)负责从网 <br>络读取数据并将其放到共享缓冲区中,在适当的时机(比如说读线程将工作线程需要的数据接收下来之后)触发一个事件,通知/唤醒工作线程(Working Thread),下面是上述文字的简单代码实现: </td>
        </tr>
    </tbody>
</table>
</p>
<p>CRITICAL_SECTION g_data; <br>HANDLE　　　　　　　　　　g_hEvent; <br>TCHAR　　　　　　　　　　　g_szBuffer[MAX_BUFFER_SIZE]; <br>int　　　　　　　　　　　　　　　g_nBytes; </p>
<p>void ReadThread(void) <br>{ <br>&nbsp;　　 int nTotle = nRead = nLeft = nBytes = 0; </p>
<p>&nbsp;&nbsp;&nbsp; 　　 while(!done) <br>&nbsp;&nbsp;&nbsp; 　　{ <br>&nbsp;&nbsp;&nbsp;&nbsp;　　　 nTotal = 0; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　 nLeft = NUM_BYTES_REQUIRED; //要读多少的Bytes </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　 while( nTotal != NUM_BYTE_REQUIRED) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　 // 因为涉及线程和全局变量所以说需要用到关键代码段(Critical Section) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　 EnterCriticalSection(&amp;g_data); </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　 nRead = recv(sock,&amp;(g_szBuffer[MAX_BUFFER_SIZE] - nBytes),nLeft); </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　 if( -1 == nread ) <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; 　　　　　　printf("socket %d error.\n",WSAGetLasError()); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　　ExitThread(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　 } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　nTotal += nRead; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　nLeft -= nRead; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;　　　　　nBytes += nRead; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　　　　LeaveCriticalSelection(&amp;g_data); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　 } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　// 从网络接收完指定的(NUM_BYTES_REQUIRED)Bytes之后通知WorkThread启动 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　　 SetEvent(g_hEvent); <br>&nbsp;&nbsp;&nbsp;　 } <br>} </p>
<p><br>void WorkThread(void) <br>{ <br>&nbsp;　　WaitForSingleObject(g_hEvent); </p>
<p>&nbsp;&nbsp; 　　EnterCriticalSection(&amp;g_data); </p>
<p><br>&nbsp;&nbsp;　　// 主要的处理工作... <br>&nbsp;&nbsp;　　DoSomeWorkOnData(g_szBuffer); </p>
<p>&nbsp;&nbsp;&nbsp; 　 g_nBytes -= NUM_BYTE_REQUIRED; </p>
<p>&nbsp;&nbsp;&nbsp;　 LeaveCriticalSelection(&amp;g_data); <br>} </p>
<p>　　这种方式的一个缺点是:应用程序很难同时通过多个建立好连接的socket通信.当然话说回来我们可以为每一个连接好的socket都开一个Reading Thread and WorkThread,但是呢这会带来很大的开销,而且扩展性很差,试想:如果用这样的模型建立一个服务器端来一个连接开至少两个线程...天..要是业务负担很大/连接的socket很多的话怎么办??所以说别多想了... <br>&nbsp;&nbsp; 这样的方式是很不适合做服务器端的.(注意:我说的服务器端是指C/S中的S端逻辑). </p>
<p>&nbsp;&nbsp; 从编写代码的角度解读一下IO中最复杂的IOCP模型 </p>
<p>&nbsp;&nbsp; 下面的一段代码是Microsoft Win32 SDK里面的例子,这是一个Echo服务器程序: </p>
<p>&nbsp; // <br>&nbsp; // 这就不说了,为了使用Winsock2必须包含的头文件... <br>&nbsp; // <br>#include <br>#include <br>#include </p>
<p>#define PORT 5150 // 这个服务器选择5150端口 <br>#define DATA_BUFSIZE 8192 // 把Buffer定义为8K是一个比较被Microsoft推荐的大小 </p>
<p>&nbsp; // <br>&nbsp; // 这个结构体很重要,请一定看清楚,到后面用到的时候别糊涂了 <br>&nbsp; // 特别注意:把Overlapped放最开头是有原因的,你应该明确的是 <br>&nbsp; // 如果有: <br>&nbsp; // PER_IO_OPERATION_DATA PerIoData; <br>&nbsp; // &amp;PerIoData-&gt;Overlapped 和 &amp;PerIoData是一样的/等价的/ <br>&nbsp; // 指向同一块内存区域的. <br>&nbsp; // <br>&nbsp; typedef struct <br>&nbsp; { <br>&nbsp;&nbsp; OVERLAPPED Overlapped; <br>&nbsp;&nbsp; WSABUF DataBuf; <br>&nbsp;&nbsp; CHAR Buffer[DATA_BUFSIZE]; <br>&nbsp;&nbsp; DWORD BytesSEND; // 下面两个参数是用来记录每个connected socket上面的收发的Bytes的 <br>&nbsp;&nbsp; DWORD BytesRECV; <br>&nbsp; } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA; </p>
<p>&nbsp; // 别把这个结构体和上面的结构体混淆了,名字很像的...其实它在这个程序中就相当于是那个和Client端连接的socket <br>&nbsp; typedef struct <br>&nbsp; { <br>&nbsp;&nbsp; SOCKET Socket; <br>&nbsp; } PER_HANDLE_DATA, * LPPER_HANDLE_DATA; </p>
<p><br>&nbsp; DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID); </p>
<p>&nbsp; void main(void) <br>&nbsp; { <br>&nbsp;&nbsp; SOCKADDR_IN InternetAddr; <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; // 一个listen和一个Accept socket是服务器程序的老套路了... <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; SOCKET Listen; <br>&nbsp;&nbsp; SOCKET Accept; </p>
<p>&nbsp;&nbsp; // <br>&nbsp;&nbsp; // IO完成端口映射到程序就是一个HANDLE <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; HANDLE CompletionPort; <br>&nbsp;&nbsp; SYSTEM_INFO SystemInfo; </p>
<p>&nbsp;&nbsp; // <br>&nbsp;&nbsp; // 看清楚,这是指针...不怕你笑话我们处还真有分不清和指针相关的一切概念的人,比如下面的代码: <br>&nbsp;&nbsp; // 我写了一个DLL,里面有一个这样的函数: <br>&nbsp;&nbsp; // BOOL GetPlayingPosition(DWORD * dwPos); <br>&nbsp;&nbsp; // 结果大虾调用的时候是这样的.... <br>&nbsp;&nbsp; // DWORD * p; <br>&nbsp;&nbsp; // GetPlayingPostion(p); <br>&nbsp;&nbsp; // 它是调用爽了...我呢...:-( <br>&nbsp;&nbsp; // 当然了,也不好定义说它就是分不清指针,也许人家就不会写程序呢..对吧.. <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; LPPER_HANDLE_DATA PerHandleData; <br>&nbsp;&nbsp; LPPER_IO_OPERATION_DATA PerIoData; </p>
<p>&nbsp;&nbsp; int i; <br>&nbsp;&nbsp; DWORD RecvBytes; <br>&nbsp;&nbsp; DWORD Flags; <br>&nbsp;&nbsp; DWORD ThreadID; <br>&nbsp;&nbsp; WSADATA wsaData; <br>&nbsp;&nbsp; DWORD Ret; </p>
<p>&nbsp;&nbsp; // 就像CoInitialize()一样的把Winsock 2.2服务启动起来... <br>&nbsp;&nbsp; if ((Ret = WSAStartup(0x0202, &amp;wsaData)) != 0) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; printf("WSAStartup failed with error %d\n", Ret); <br>&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; // CreateIoCompletionPort有两个不同的用途:1.创建完成端口;2.把已经创建好的完成端口和一个Socket关联/绑定起来. <br>&nbsp;&nbsp; // 下面这句话是它的第一个用途:创建完成端口 <br>&nbsp;&nbsp; if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; // <br>&nbsp;&nbsp; // 下面这一些代码说得是:根据该服务器跑的机器上有几个CPU建立2*CPU数量的Thread. <br>&nbsp;&nbsp; // 这涉及到完成端口工作线程应该创建多少个的问题,在早期(MSDN 2000)Microsoft说 <br>&nbsp;&nbsp; // 是WorkThreadNums = 2 * CPU + 2,现在的MSDN 2003说是WorkThreadNums = 2 * CPU; <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; GetSystemInfo(&amp;SystemInfo); </p>
<p>&nbsp;&nbsp; for(i = 0; i &lt; SystemInfo.dwNumberOfProcessors * 2; i++) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; HANDLE ThreadHandle; </p>
<p>&nbsp;&nbsp;&nbsp; // 创建一个线程并把上面创建的完成端口传给线程 <br>&nbsp;&nbsp;&nbsp; if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort, <br>&nbsp;&nbsp;&nbsp;&nbsp; 0, &amp;ThreadID)) == NULL) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("CreateThread() failed with error %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; // 这个风格我喜欢,创建完Thread就把人家的Handle给release,省得要是线程异常退出搞得资源泄漏之类的麻烦事情. <br>&nbsp;&nbsp;&nbsp; CloseHandle(ThreadHandle); <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; // 开始创建监听Socket... <br>&nbsp;&nbsp; if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; printf("WSASocket() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; InternetAddr.sin_family = AF_INET; <br>&nbsp;&nbsp; InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); <br>&nbsp;&nbsp; InternetAddr.sin_port = htons(PORT); </p>
<p>&nbsp;&nbsp; // 把监听socket和绑定TCP/IP协议栈做一个绑定... <br>&nbsp;&nbsp; if (bind(Listen, (PSOCKADDR) &amp;InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; printf("bind() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; // 设置一次能接受5个连接 <br>&nbsp;&nbsp; if (listen(Listen, 5) == SOCKET_ERROR) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; printf("listen() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp; // <br>&nbsp;&nbsp; // 下面是服务器程序标准的循环了... <br>&nbsp;&nbsp; // <br>&nbsp;&nbsp; while(TRUE) <br>&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp; if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("WSAAccept() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // 接收到一个连接就new一个新的socket并承揽下连接进来的socket的接下来的接待/处理工作 <br>&nbsp;&nbsp;&nbsp; // 为什么不用new呢?我想是因为用Windows提供的内存管理函数,这样的话在业务量大的情况下 <br>&nbsp;&nbsp;&nbsp; // 出现内存匮乏的情况下不至于直接就异常了,怎么着也就是该次的内存分配失败... <br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("GlobalAlloc() failed with error %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; // Associate the accepted socket with the original completion port. </p>
<p>&nbsp;&nbsp;&nbsp; printf("Socket number %d connected\n", Accept); <br>&nbsp;&nbsp;&nbsp; PerHandleData-&gt;Socket = Accept; </p>
<p>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // 把accept socket和IO完成端口关联/绑定起来,并把accept socket传给IOCP机制这样的话 <br>&nbsp;&nbsp;&nbsp; // 当调用GetQueuedCompletionStatus的时候就可以得到该值. <br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData, <br>&nbsp;&nbsp;&nbsp;&nbsp; 0) == NULL) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("CreateIoCompletionPort failed with error %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; // 下面这个结构体也是会被传到工作线程中去的,怎么传呢?注意看下面红色部分 <br>&nbsp;&nbsp;&nbsp; if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("GlobalAlloc() failed with error %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), sizeof(OVERLAPPED)); <br>&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesSEND = 0; <br>&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesRECV = 0; <br>&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.len = DATA_BUFSIZE; <br>&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.buf = PerIoData-&gt;Buffer; </p>
<p>&nbsp;&nbsp;&nbsp; Flags = 0; </p>
<p>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // 在accept socket上面发起IO请求:WSARecv()以此激发IOCP工作机制.. <br>&nbsp;&nbsp;&nbsp; // 注意WSARecv的第五个参数,也就是说我这时候告诉WSARecv的不仅仅是 <br>&nbsp;&nbsp;&nbsp; // PerIoData-&gt;Overlapped的地址同时也是PerIoData的地址... <br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; if (WSARecv(Accept, &amp;(PerIoData-&gt;DataBuf), 1, &amp;RecvBytes, &amp;Flags, <br>&nbsp;&nbsp;&nbsp;&nbsp; &amp;(PerIoData-&gt;Overlapped), NULL) == SOCKET_ERROR) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; if (WSAGetLastError() != ERROR_IO_PENDING) <br>&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("WSARecv() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return; <br>&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp; } <br>&nbsp; } </p>
<p>&nbsp; // <br>&nbsp; // 工作线程 <br>&nbsp; // <br>&nbsp; DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID) <br>&nbsp; { <br>&nbsp;&nbsp; HANDLE CompletionPort = (HANDLE) CompletionPortID; <br>&nbsp;&nbsp; DWORD BytesTransferred; <br>&nbsp;&nbsp; LPOVERLAPPED Overlapped; <br>&nbsp;&nbsp; LPPER_HANDLE_DATA PerHandleData; <br>&nbsp;&nbsp; LPPER_IO_OPERATION_DATA PerIoData; <br>&nbsp;&nbsp; DWORD SendBytes, RecvBytes; <br>&nbsp;&nbsp; DWORD Flags; </p>
<p>&nbsp;&nbsp; while(TRUE) <br>&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // 这个函数解释清楚了整个机制也就明白了... <br>&nbsp;&nbsp;&nbsp; // 首先我们看CompletionPort应该没有问题,是从创建线程的主线程传过来的. <br>&nbsp;&nbsp;&nbsp; // BytesTransferred指明了这次IO传输了多少Bytes... <br>&nbsp;&nbsp;&nbsp; // PerHandleData是CreateIoCompletionPort的时候穿过来的,MSDN里面叫CompletionKey. <br>&nbsp;&nbsp;&nbsp; // 请注意PerIoData这个参数,本来按照MSDN的解释如下: <br>&nbsp;&nbsp;&nbsp; // "Pointer to a variable that receives the address of the OVERLAPPED structure that was <br>&nbsp;&nbsp;&nbsp; // specified when the completed I/O operation was started." <br>&nbsp;&nbsp;&nbsp; // 其实在这这个lpOverlapped传过来的就是上面WSARecv传入的overlapped的地址... <br>&nbsp;&nbsp;&nbsp; // 这下明白了吧:主线程借着overlapped的名声传了一个结构体指针过来..:-) <br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; if (GetQueuedCompletionStatus(CompletionPort, &amp;BytesTransferred, <br>&nbsp;&nbsp;&nbsp;&nbsp; (LPDWORD)&amp;PerHandleData, (LPOVERLAPPED *) &amp;PerIoData, INFINITE) == 0) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp; return 0; <br>&nbsp;&nbsp;&nbsp; } </p>
<p><br>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // First check to see if an error has occured on the socket and if so <br>&nbsp;&nbsp;&nbsp; // then close the socket and cleanup the SOCKET_INFORMATION structure <br>&nbsp;&nbsp;&nbsp; // associated with the socket. <br>&nbsp;&nbsp;&nbsp; // </p>
<p>&nbsp;&nbsp;&nbsp; if (BytesTransferred == 0) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("Closing socket %d\n", PerHandleData-&gt;Socket); </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; if (closesocket(PerHandleData-&gt;Socket) == SOCKET_ERROR) <br>&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("closesocket() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0; <br>&nbsp;&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; GlobalFree(PerHandleData); <br>&nbsp;&nbsp;&nbsp;&nbsp; GlobalFree(PerIoData); <br>&nbsp;&nbsp;&nbsp;&nbsp; continue; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp; // Check to see if the BytesRECV field equals zero. If this is so, then <br>&nbsp;&nbsp;&nbsp; // this means a WSARecv call just completed so update the BytesRECV field <br>&nbsp;&nbsp;&nbsp; // with the BytesTransferred value from the completed WSARecv() call. <br>&nbsp;&nbsp;&nbsp; // </p>
<p>&nbsp;&nbsp;&nbsp; if (PerIoData-&gt;BytesRECV == 0) <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesRECV = BytesTransferred; <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesSEND = 0; <br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; else <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesSEND += BytesTransferred; <br>&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp; if (PerIoData-&gt;BytesRECV &gt; PerIoData-&gt;BytesSEND) <br>&nbsp;&nbsp;&nbsp; { </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp;&nbsp; // Post another WSASend() request. <br>&nbsp;&nbsp;&nbsp;&nbsp; // Since WSASend() is not gauranteed to send all of the bytes requested, <br>&nbsp;&nbsp;&nbsp;&nbsp; // continue posting WSASend() calls until all received bytes are sent. <br>&nbsp;&nbsp;&nbsp;&nbsp; // <br>&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), sizeof(OVERLAPPED)); </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.buf = PerIoData-&gt;Buffer + PerIoData-&gt;BytesSEND; <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.len = PerIoData-&gt;BytesRECV - PerIoData-&gt;BytesSEND; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; if (WSASend(PerHandleData-&gt;Socket, &amp;(PerIoData-&gt;DataBuf), 1, &amp;SendBytes, 0, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;(PerIoData-&gt;Overlapped), NULL) == SOCKET_ERROR) <br>&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (WSAGetLastError() != ERROR_IO_PENDING) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("WSASend() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; else <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;BytesRECV = 0; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; // Now that there are no more bytes to send post another WSARecv() request. <br>&nbsp;&nbsp;&nbsp;&nbsp; Flags = 0; <br>&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), sizeof(OVERLAPPED)); </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.len = DATA_BUFSIZE; <br>&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuf.buf = PerIoData-&gt;Buffer; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; if (WSARecv(PerHandleData-&gt;Socket, &amp;(PerIoData-&gt;DataBuf), 1, &amp;RecvBytes, &amp;Flags, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;(PerIoData-&gt;Overlapped), NULL) == SOCKET_ERROR) <br>&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (WSAGetLastError() != ERROR_IO_PENDING) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("WSARecv() failed with error %d\n", WSAGetLastError()); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp; } <br>&nbsp; }<br></p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/33805.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-10-09 11:57 <a href="http://www.cppblog.com/niewenlong/archive/2007/10/09/33805.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境下的Socket编程</title><link>http://www.cppblog.com/niewenlong/archive/2007/09/26/32913.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Wed, 26 Sep 2007 06:41:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/09/26/32913.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/32913.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/09/26/32913.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/32913.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/32913.html</trackback:ping><description><![CDATA[<span class=pg id=xydwtext>什么是Socket<br>　　Socket接口是TCP/IP网络的API，Socket接口定义了许多函数或例程，程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程，必须理解Socket接口。<br>　　Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话，就很容易了解Socket了。网络的 Socket数据传输是一种特殊的I/O，Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket()，该函数返回一个整型的Socket描述符，随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种：流式Socket （SOCK_STREAM）和数据报式Socket（SOCK_DGRAM）。流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据报式Socket是一种无连接的Socket，对应于无连接的UDP服务应用。<br><br>Socket建立<br>　　为了建立Socket，程序可以调用Socket函数，该函数返回一个类似于文件描述符的句柄。socket函数原型为：<br>　　int socket(int domain, int type, int protocol);<br>　　domain指明所使用的协议族，通常为PF_INET，表示互联网协议族（TCP/IP协议族）；type参数指定socket的类型： SOCK_STREAM 或SOCK_DGRAM，Socket接口还定义了原始Socket（SOCK_RAW），允许程序使用低层协议；protocol通常赋值"0"。 Socket()调用返回一个整型socket描述符，你可以在后面的调用使用它。<br>　　Socket描述符是一个指向内部数据结构的指针，它指向描述符表入口。调用Socket函数时，socket执行体将建立一个Socket，实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。<br>　　两个网络程序之间的一个网络连接包括五种信息：通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。<br><br>Socket配置<br>　　通过socket调用返回一个socket描述符后，在使用socket进行网络传输以前，必须配置该socket。面向连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用 bind函数来配置本地信息。<br>Bind函数将socket与本机上的一个端口相关联，随后你就可以在该端口监听服务请求。Bind函数原型为：<br>　　int bind(int sockfd,struct sockaddr *my_addr, int addrlen);<br>　　Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针；addrlen常被设置为sizeof(struct sockaddr)。<br>　　struct sockaddr结构类型是用来保存socket信息的：<br>　　struct sockaddr {<br>　　 unsigned short sa_family; /* 地址族， AF_xxx */<br>char sa_data[14]; /* 14 字节的协议地址 */<br>};<br>　　sa_family一般为AF_INET，代表Internet（TCP/IP）地址族；sa_data则包含该socket的IP地址和端口号。<br>　　另外还有一种结构类型：<br>　　struct sockaddr_in {<br>　　 short int sin_family; /* 地址族 */<br>　　 unsigned short int sin_port; /* 端口号 */<br>　　 struct in_addr sin_addr; /* IP地址 */<br>　　 unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */<br>　　};<br>　　这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度，可以用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换，这意味着如果一个函数所需参数类型是sockaddr时，你可以在函数调用的时候将一个指向 sockaddr_in的指针转换为指向sockaddr的指针；或者相反。<br>　　使用bind函数时，可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号：<br>　　my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */<br>　　my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */<br>通过将my_addr.sin_port置为0，函数会自动为你选择一个未占用的端口来使用。同样，通过将my_addr.sin_addr.s_addr置为INADDR_ANY，系统会自动填入本机IP地址。<br>注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序；而sin_addr则不需要转换。<br>　　计算机数据存储有两种字节优先顺序：高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输，所以对于在内部是以低位字节优先方式存储数据的机器，在Internet上传输数据时就需要进行转换，否则就会出现数据不一致。<br>　　下面是几个字节顺序转换函数：<br>&#183;htonl()：把32位值从主机字节序转换成网络字节序<br>&#183;htons()：把16位值从主机字节序转换成网络字节序<br>&#183;ntohl()：把32位值从网络字节序转换成主机字节序<br>&#183;ntohs()：把16位值从网络字节序转换成主机字节序<br>　　Bind()函数在成功被调用时返回0；出现错误时返回"-1"并将errno置为相应的错误号。需要注意的是，在调用bind函数时一般不要将端口号置为小于1024的值，因为1到1024是保留端口号，你可以选择大于1024中的任何一个没有被占用的端口号。<br><br>连接建立<br>　　面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接，其函数原型为：<br>　　int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);<br>Sockfd 是socket函数返回的socket描述符；serv_addr是包含远端主机IP地址和端口号的指针；addrlen是远端地质结构的长度。 Connect函数在出现错误时返回-1，并且设置errno为相应的错误码。进行客户端程序设计无须调用bind()，因为这种情况下只需知道目的机器的IP地址，而客户通过哪个端口与服务器建立连接并不需要关心，socket执行体为你的程序自动选择一个未被占用的端口，并通知你的程序数据什么时候到打断口。<br>　　Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接，它只是被动的在协议端口监听客户的请求。<br>　　Listen函数使socket处于被动的监听模式，并为该socket建立一个输入数据队列，将到达的服务请求保存在此队列中，直到程序处理它们。<br>　　int listen(int sockfd， int backlog);<br>Sockfd 是Socket系统调用返回的socket 描述符；backlog指定在请求队列中允许的最大请求数，进入的连接请求将在队列中等待accept()它们（参考下文）。Backlog对队列中等待服务的请求的数目进行了限制，大多数系统缺省值为20。如果一个服务请求到来时，输入队列已满，该socket将拒绝连接请求，客户将收到一个出错信息。<br>当出现错误时listen函数返回-1，并置相应的errno错误码。<br>　　accept()函数让服务器接收客户的连接请求。在建立好输入队列后，服务器就调用accept函数，然后睡眠并等待客户的连接请求。<br>　　int accept(int sockfd, void *addr, int *addrlen);<br>　　sockfd是被监听的socket描述符，addr通常是一个指向sockaddr_in变量的指针，该变量用来存放提出连接请求服务的主机的信息（某台主机从某个端口发出该请求）；addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。<br>　　首先，当accept函数监视的 socket收到连接请求时，socket执行体将建立一个新的socket，执行体将这个新socket和请求连接进程的地址联系起来，收到服务请求的初始socket仍可以继续在以前的 socket上监听，同时可以在新的socket描述符上进行数据传输操作。<br><br>数据传输<br>　　Send()和recv()这两个函数用于面向连接的socket上进行数据传输。<br>　　Send()函数原型为：<br>　　int send(int sockfd, const void *msg, int len, int flags);<br>Sockfd是你想用来传输数据的socket描述符；msg是一个指向要发送数据的指针；Len是以字节为单位的数据的长度；flags一般情况下置为0（关于该参数的用法可参照man手册）。<br>　　Send()函数返回实际上发送出的字节数，可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时，应该对这种情况进行处理。<br>char *msg = "Hello!";<br>int len, bytes_sent;<br>&#8230;&#8230;<br>len = strlen(msg);<br>bytes_sent = send(sockfd, msg,len,0);<br>&#8230;&#8230;<br>　　recv()函数原型为：<br>　　int recv(int sockfd,void *buf,int len,unsigned int flags);<br>　　Sockfd是接受数据的socket描述符；buf 是存放接收数据的缓冲区；len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数，当出现错误时，返回-1并置相应的errno值。<br>Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接，所以在发送数据时应指明目的地址。<br>sendto()函数原型为：<br>　　int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);<br>　　该函数比send()函数多了两个参数，to表示目地机的IP地址和端口号信息，而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。<br>　　Recvfrom()函数原型为：<br>　　int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);<br>　　from是一个struct sockaddr类型的变量，该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时，fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1，并置相应的errno。<br>如果你对数据报socket调用了connect()函数时，你也可以利用send()和recv()进行数据传输，但该socket仍然是数据报socket，并且利用传输层的UDP服务。但在发送或接收数据报时，内核会自动为之加上目地和源地址信息。<br><br>结束传输<br>　　当所有的数据操作结束以后，你可以调用close()函数来释放该socket，从而停止在该socket上的任何数据操作：<br>close(sockfd);<br>　　你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输，而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据，直至读入所有数据。<br>　　int shutdown(int sockfd,int how);<br>　　Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式：<br>　　&#183;0-------不允许继续接收数据<br>　　&#183;1-------不允许继续发送数据<br>&#183;2-------不允许继续发送和接收数据，<br>&#183;均为允许则调用close ()<br>　　shutdown在操作成功时返回0，在出现错误时返回-1并置相应errno。<br><br>面向连接的Socket实例<br>　　代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件，在客户端运行客户软件，客户端就会收到该字符串。<br>　　该服务器软件代码如下：<br>#include &lt;stdio.h&gt;<br>#include &lt;stdlib.h&gt;<br>#include &lt;errno.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;sys/types.h&gt;<br>#include &lt;netinet/in.h&gt;<br>#include &lt;sys/socket.h&gt;<br>#include &lt;sys/wait.h&gt;<br>#define SERVPORT 3333 /*服务器监听端口号 */<br>#define BACKLOG 10 /* 最大同时连接请求数 */<br>main()<br>{<br>int sockfd,client_fd; /*sock_fd：监听socket；client_fd：数据传输socket */<br>　struct sockaddr_in my_addr; /* 本机地址信息 */<br>　struct sockaddr_in remote_addr; /* 客户端地址信息 */<br>if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {<br>　　perror("socket创建出错！"); exit(1);<br>}<br>my_addr.sin_family=AF_INET;<br>　my_addr.sin_port=htons(SERVPORT);<br>　my_addr.sin_addr.s_addr = INADDR_ANY;<br>bzero(&amp;(my_addr.sin_zero),8);<br>　if (bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)) \<br>　　 == -1) {<br>perror("bind出错！");<br>exit(1);<br>}<br>　if (listen(sockfd, BACKLOG) == -1) {<br>perror("listen出错！");<br>exit(1);<br>}<br>while(1) {<br>　　sin_size = sizeof(struct sockaddr_in);<br>　　if ((client_fd = accept(sockfd, (struct sockaddr *)&amp;remote_addr, \<br>　　&amp;sin_size)) == -1) {<br>perror("accept出错");<br>continue;<br>}<br>　　printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr));<br>　 if (!fork()) { /* 子进程代码段 */<br>　　 if (send(client_fd, "Hello, you are connected!\n", 26, 0) == -1)<br>　　 perror("send出错！");<br>close(client_fd);<br>exit(0);<br>}<br>　　close(client_fd);<br>　　}<br>　}<br>}<br>　　服务器的工作流程是这样的：首先调用socket函数创建一个Socket，然后调用bind函数将其与本机地址以及一个本地端口号绑定，然后调用 listen在相应的socket上监听，当accpet接收到一个连接服务请求时，将生成一个新的socket。服务器显示该客户机的IP地址，并通过新的socket向客户端发送字符串"Hello，you are connected!"。最后关闭该socket。<br>　　代码实例中的fork()函数生成一个子进程来处理数据传输部分，fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分，它与if语句后面的父进程代码部分是并发执行的。<br><br>客户端程序代码如下：<br>#include&lt;stdio.h&gt;<br>#include &lt;stdlib.h&gt;<br>#include &lt;errno.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;netdb.h&gt;<br>#include &lt;sys/types.h&gt;<br>#include &lt;netinet/in.h&gt;<br>#include &lt;sys/socket.h&gt;<br>#define SERVPORT 3333<br>#define MAXDATASIZE 100 /*每次最大数据传输量 */<br>main(int argc, char *argv[]){<br>　int sockfd, recvbytes;<br>　char buf[MAXDATASIZE];<br>　struct hostent *host;<br>　struct sockaddr_in serv_addr;<br>　if (argc &lt; 2) {<br>fprintf(stderr,"Please enter the server's hostname!\n");<br>exit(1);<br>}<br>　if((host=gethostbyname(argv[1]))==NULL) {<br>herror("gethostbyname出错！");<br>exit(1);<br>}<br>　if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){<br>perror("socket创建出错！");<br>exit(1);<br>}<br>　serv_addr.sin_family=AF_INET;<br>　serv_addr.sin_port=htons(SERVPORT);<br>　serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr);<br>　bzero(&amp;(serv_addr.sin_zero),8);<br>　if (connect(sockfd, (struct sockaddr *)&amp;serv_addr, \<br>　　 sizeof(struct sockaddr)) == -1) {<br>perror("connect出错！");<br>exit(1);<br>}<br>　if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {<br>perror("recv出错！");<br>exit(1);<br>}<br>　buf[recvbytes] = '\0';<br>　printf("Received: %s",buf);<br>　close(sockfd);<br>}<br>　　客户端程序首先通过服务器域名获得服务器的IP地址，然后创建一个socket，调用connect函数与服务器建立连接，连接成功之后接收从服务器发送过来的数据，最后关闭socket。<br>　　函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写，所以为了方便，人们常常用域名来表示主机，这就需要进行域名和IP地址的转换。函数原型为：<br>　　struct hostent *gethostbyname(const char *name);<br>　　函数返回为hosten的结构类型，它的定义如下：<br>　　struct hostent {<br>　 char *h_name; /* 主机的官方域名 */<br>　　 char **h_aliases; /* 一个以NULL结尾的主机别名数组 */<br>　　 int h_addrtype; /* 返回的地址类型，在Internet环境下为AF-INET */<br>　　int h_length; /* 地址的字节长度 */<br>　　 char **h_addr_list; /* 一个以0结尾的数组，包含该主机的所有地址*/<br>　　};<br>　　#define h_addr h_addr_list[0] /*在h-addr-list中的第一个地址*/<br>　　当 gethostname()调用成功时，返回指向struct hosten的指针，当调用失败时返回-1。当调用gethostbyname时，你不能使用perror()函数来输出错误信息，而应该使用herror()函数来输出。<br><br>　　无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的，两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接，而且在发送接收数据时，需要指定远端机的地址。<br><br>阻塞和非阻塞<br>　　阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如，程序执行一个读数据的函数调用时，在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时，而没有客户连接服务请求到来，服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞（blocking）。而非阻塞操作则可以立即完成。比如，如果你希望服务器仅仅注意检查是否有客户在等待连接，有就接受连接，否则就继续做其他事情，则可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。<br>　　#include &lt;unistd.h&gt;<br>　　#include &lt;fcntl.h&gt;<br>　　&#8230;&#8230;<br>sockfd = socket(AF_INET,SOCK_STREAM,0);<br>fcntl(sockfd,F_SETFL,O_NONBLOCK)；<br>&#8230;&#8230;<br>　　通过设置socket为非阻塞方式，可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时，函数将立即返回，返回值为-1，并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式，从而降低性能，浪费系统资源。而调用 select()会有效地解决这个问题，它允许你把进程本身挂起来，而同时使系统内核监听所要求的一组文件描述符的任何活动，只要确认在任何被监控的文件描述符上出现活动，select()调用将返回指示该文件描述符已准备好的信息，从而实现了为进程选出随机的变化，而不必由进程本身对输入进行测试而浪费 CPU开销。Select函数原型为:<br>int select(int numfds,fd_set *readfds,fd_set *writefds，<br>fd_set *exceptfds,struct timeval *timeout);<br>　　其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从标准输入和某个socket描述符读取数据，你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中；numfds的值是需要检查的号码最高的文件描述符加1，这个例子中numfds的值应为sockfd+1；当select返回时，readfds将被修改，指示某个文件描述符已经准备被读取，你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试，它提供了一组宏：<br>　　FD_ZERO(fd_set *set)----清除一个文件描述符集；<br>　　FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中；<br>　　FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除；<br>　　FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。<br>　　Timeout参数是一个指向struct timeval类型的指针，它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为：<br>　　struct timeval {<br>　　 int tv_sec; /* seconds */<br>　　 int tv_usec; /* microseconds */<br>};<br><br>POP3客户端实例<br>　　下面的代码实例基于POP3的客户协议，与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中，程序通过一个do-while循环依次发送这些命令。<br>#include&lt;stdio.h&gt;<br>#include &lt;stdlib.h&gt;<br>#include &lt;errno.h&gt;<br>#include &lt;string.h&gt;<br>#include &lt;netdb.h&gt;<br>#include &lt;sys/types.h&gt;<br>#include &lt;netinet/in.h&gt;<br>#include &lt;sys/socket.h&gt;<br>#define POP3SERVPORT 110<br>#define MAXDATASIZE 4096<br><br>main(int argc, char *argv[]){<br>int sockfd;<br>struct hostent *host;<br>struct sockaddr_in serv_addr;<br>char *POPMessage[]={<br>"USER userid\r\n",<br>"PASS password\r\n",<br>"STAT\r\n",<br>"LIST\r\n",<br>"RETR 1\r\n",<br>"DELE 1\r\n",<br>"QUIT\r\n",<br>NULL<br>};<br>int iLength;<br>int iMsg=0;<br>int iEnd=0;<br>char buf[MAXDATASIZE];<br><br>if((host=gethostbyname("your.server"))==NULL) {<br>perror("gethostbyname error");<br>exit(1);<br>}<br>if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){<br>perror("socket error");<br>exit(1);<br>}<br>serv_addr.sin_family=AF_INET;<br>serv_addr.sin_port=htons(POP3SERVPORT);<br>serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr);<br>bzero(&amp;(serv_addr.sin_zero),8);<br>if (connect(sockfd, (struct sockaddr *)&amp;serv_addr,sizeof(struct sockaddr))==-1){<br>perror("connect error");<br>exit(1);<br>}<br><br>do {<br>send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);<br>printf("have sent: %s",POPMessage[iMsg]);<br><br>iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0);<br>iEnd+=iLength;<br>buf[iEnd]='\0';<br>printf("received: %s,%d\n",buf,iMsg);<br><br>iMsg++;<br>} while (POPMessage[iMsg]);<br><br>close(sockfd);<br>}</span>
<div class=invisible id=reference>文章引用自：<a href="" target=_blank></a> </div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/32913.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-09-26 14:41 <a href="http://www.cppblog.com/niewenlong/archive/2007/09/26/32913.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux 下Socket编程基础</title><link>http://www.cppblog.com/niewenlong/archive/2007/09/26/32912.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Wed, 26 Sep 2007 06:40:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/09/26/32912.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/32912.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/09/26/32912.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/32912.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/32912.html</trackback:ping><description><![CDATA[Linux的兴起可以说是Internet创造的一个奇迹。Linux作为一个完全开放其原代码的免费的自由软件，兼容了各种UNIX标准（如POSIX、UNIX System V 和 BSD UNIX 等）的多用户、多任务的具有复杂内核的操作系统。在中国，随着Internet的普及，一批主要以高等院校的学生和ISP的技术人员组成的Linux爱好者队伍已经蓬勃成长起来。越来越多的编程爱好者也逐渐酷爱上这个优秀的自由软件。本文介绍了Linux下Socket的基本概念和函数调用。
<p>2、 什么是Socket</p>
<p>Socket（套接字）是通过标准的UNIX文件描述符和其它程序通讯的一个方法。每一个套接字都用一个半相关描述：{协议，本地地址、本地端口}来表示；一个完整的套接字则用一个相关描述：{协议，本地地址、本地端口、远程地址、远程端口}，每一个套接字都有一个本地的由操作系统分配的唯一的套接字号。</p>
<p>3、 Socket的三种类型</p>
<p>（1） 流式Socket（SOCK_STREAM）</p>
<p>流式套接字提供可靠的、面向连接的通信流；它使用TCP协议，从而保证了数据传输的正确性和顺序的。</p>
<p>（2） 数据报Socket（SOCK_DGRAM）</p>
<p>数据报套接字定义了一种无连接的服务，数据通过相互独立的报文进行传输，是无序的，并且不保证可靠、无差错。它使用数据报协议UDP</p>
<p>（3） 原始Socket</p>
<p>原始套接字允许对底层协议如IP或ICMP直接访问，它功能强大但使用较为不便，主要用于一些协议的开发。</p>
<p>4、 利用套接字发送数据</p>
<p>1、 对于流式套接字用系统调用send（）来发送数据。</p>
<p>2、对于数据报套接字，则需要自己先加一个信息头，然后调用sendto（）函数把数据发送出去。</p>
<p>5、 Linux中Socket的数据结构</p>
<p>（1） struct sockaddr { //用于存储套接字地址</p>
<p>unsigned short sa_family；//地址类型</p>
<p>char sa_data[14]； //14字节的协议地址</p>
<p>}；</p>
<p>（2） struct sockaddr_in{ //in 代表internet</p>
<p>short int sin_family； //internet协议族</p>
<p>unsigned short int sin_port；//端口号，必须是网络字节顺序</p>
<p>struct in_addr sin_addr；//internet地址，必须是网络字节顺序</p>
<p>unsigned char sin_zero；//添0（和struct sockaddr一样大小</p>
<p>}；</p>
<p>（3） struct in_addr{</p>
<p>unsigned long s_addr；</p>
<p>}；</p>
<p>6、 网络字节顺序及其转换函数</p>
<p>（1） 网络字节顺序</p>
<p>每一台机器内部对变量的字节存储顺序不同，而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器，一定要对数据进行转换，从程序的可移植性要求来讲，就算本机的内部字节表示顺序与网络字节顺序相同也应该在传输数据以前先调用数据转换函数，以便程序移植到其它机器上后能正确执行。真正转换还是不转换是由系统函数自己来决定的。</p>
<p>（2） 有关的转换函数</p>
<p>* unsigned short int htons（unsigned short int hostshort）：</p>
<p>主机字节顺序转换成网络字节顺序，对无符号短型进行操作4bytes</p>
<p>* unsigned long int htonl（unsigned long int hostlong）：</p>
<p>主机字节顺序转换成网络字节顺序，对无符号长型进行操作8bytes</p>
<p>* unsigned short int ntohs（unsigned short int netshort）：</p>
<p>网络字节顺序转换成主机字节顺序，对无符号短型进行操作4bytes</p>
<p>* unsigned long int ntohl（unsigned long int netlong）：</p>
<p>网络字节顺序转换成主机字节顺序，对无符号长型进行操作8bytes</p>
<p>注：以上函数原型定义在netinet/in.h里</p>
<p>7、 IP地址转换</p>
<p>有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换</p>
<p>（1） unsigned long int inet_addr(const char * cp)：该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型，如：struct sockaddr_in ina</p>
<p>ina.sin_addr.s_addr=inet_addr("202.206.17.101")</p>
<p>该函数成功时：返回转换结果；失败时返回常量INADDR_NONE，该常量=-1，二进制的无符号整数-1相当于255.255.255.255，这是一个广播地址，所以在程序中调用iner_addr（）时，一定要人为地对调用失败进行处理。由于该函数不能处理广播地址，所以在程序中应该使用函数inet_aton（）。</p>
<p>（2）int inet_aton（const char * cp,struct in_addr * inp）：此函数将字符串形式的IP地址转换成二进制形式的IP地址；成功时返回1，否则返回0，转换后的IP地址存储在参数inp中。</p>
<p>（3） char * inet_ntoa（struct in-addr in）：将32位二进制形式的IP地址转换为数字点形式的IP地址，结果在函数返回值中返回，返回的是一个指向字符串的指针。</p>
<p>8、 字节处理函数</p>
<p>Socket地址是多字节数据，不是以空字符结尾的，这和C语言中的字符串是不同的。Linux提供了两组函数来处理多字节数据，一组以b（byte）开头，是和BSD系统兼容的函数，另一组以mem（内存）开头，是ANSI C提供的函数。</p>
<p>以b开头的函数有：</p>
<p>（1） void bzero（void * s,int n）：将参数s指定的内存的前n个字节设置为0，通常它用来将套接字地址清0。</p>
<p>（2） void bcopy（const void * src，void * dest，int n）：从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域。</p>
<p>（3） int bcmp（const void * s1，const void * s2，int n）：比较参数s1指定的内存区域和参数s2指定的内存区域的前n个字节内容，如果相同则返回0，否则返回非0。</p>
<p>注：以上函数的原型定义在strings.h中。</p>
<p>以mem开头的函数有：</p>
<p>（1） void * memset（void * s，int c，size_t n）：将参数s指定的内存区域的前n个字节设置为参数c的内容。</p>
<p>（2） void * memcpy（void * dest，const void * src，size_t n）：功能同bcopy（），区别：函数bcopy（）能处理参数src和参数dest所指定的区域有重叠的情况，memcpy（）则不能。</p>
<p>（4） int memcmp（const void * s1，const void * s2，size_t n）：比较参数s1和参数s2指定区域的前n个字节内容，如果相同则返回0，否则返回非0。</p>
<p>注：以上函数的原型定义在string.h中。</p>
<p>9、 基本套接字函数</p>
<p>（1） socket（）</p>
<p>#include&lt;sys/types.h&gt;</p>
<p>#include&lt;sys/socket.h&gt;</p>
<p>int socket(int domain，int type，int protocol)</p>
<p>参数domain指定要创建的套接字的协议族，可以是如下值：</p>
<p>AF_UNIX //UNIX域协议族，本机的进程间通讯时使用</p>
<p>AF_INET //Internet协议族（TCP/IP）</p>
<p>AF_ISO //ISO协议族</p>
<p>参数type指定套接字类型，可以是如下值：</p>
<p>SOCK_STREAM //流套接字，面向连接的和可靠的通信类型</p>
<p>SOCK_DGRAM //数据报套接字，非面向连接的和不可靠的通信类型</p>
<p>SOCK_RAW //原始套接字，只对Internet协议有效，可以用来直接访问IP协议</p>
<p>参数protocol通常设置成0，表示使用默认协议，如Internet协议族的流套接字使用TCP协议，而数据报套接字使用UDP协议。当套接字是原始套接字类型时，需要指定参数protocol，因为原始套接字对多种协议有效，如ICMP和IGMP等。</p>
<p>Linux系统中创建一个套接字的操作主要是：在内核中创建一个套接字数据结构，然后返回一个套接字描述符标识这个套接字数据结构。这个套接字数据结构包含连接的各种信息，如对方地址、TCP状态以及发送和接收缓冲区等等，TCP协议根据这个套接字数据结构的内容来控制这条连接。</p>
<p>（2） 函数connect（）</p>
<p>#include&lt;sys/types.h&gt;</p>
<p>#include&lt;sys/socket.h&gt;</p>
<p>int connect（int sockfd，struct sockaddr * servaddr，int addrlen）</p>
<p>参数sockfd是函数socket返回的套接字描述符；参数servaddr指定远程服务器的套接字地址，包括服务器的IP地址和端口号；参数addrlen指定这个套接字地址的长度。成功时返回0，否则返回-1，并设置全局变量为以下任何一种错误类型：ETIMEOUT、ECONNREFUSED、EHOSTUNREACH或ENETUNREACH。</p>
<p>在调用函数connect之前，客户机需要指定服务器进程的套接字地址。客户机一般不需要指定自己的套接字地址（IP地址和端口号），系统会自动从1024至5000的端口号范围内为它选择一个未用的端口号，然后以这个端口号和本机的IP地址填充这个套接字地址。</p>
<p>客户机调用函数connect来主动建立连接。这个函数将启动TCP协议的3次握手过程。在建立连接之后或发生错误时函数返回。连接过程可能出现的错误情况有：</p>
<p>（1）如果客户机TCP协议没有接收到对它的SYN数据段的确认，函数以错误返回，错误类型为ETIMEOUT。通常TCP协议在发送SYN数据段失败之后，会多次发送SYN数据段，在所有的发送都高中失败之后，函数以错误返回。</p>
<p>注：SYN（synchronize）位：请求连接。TCP用这种数据段向对方TCP协议请求建立连接。在这个数据段中，TCP协议将它选择的初始序列号通知对方，并且与对方协议协商最大数据段大小。SYN数据段的序列号为初始序列号，这个SYN数据段能够被确认。当协议接收到对这个数据段的确认之后，建立TCP连接。</p>
<p>（2）如果远程TCP协议返回一个RST数据段，函数立即以错误返回，错误类型为ECONNREFUSED。当远程机器在SYN数据段指定的目的端口号处没有服务进程在等待连接时，远程机器的TCP协议将发送一个RST数据段，向客户机报告这个错误。客户机的TCP协议在接收到RST数据段后不再继续发送SYN数据段，函数立即以错误返回。</p>
<p>注：RST（reset）位：表示请求重置连接。当TCP协议接收到一个不能处理的数据段时，向对方TCP协议发送这种数据段，表示这个数据段所标识的连接出现了某种错误，请求TCP协议将这个连接清除。有3种情况可能导致TCP协议发送RST数据段：（1）SYN数据段指定的目的端口处没有接收进程在等待；（2）TCP协议想放弃一个已经存在的连接；（3）TCP接收到一个数据段，但是这个数据段所标识的连接不存在。接收到RST数据段的TCP协议立即将这条连接非正常地断开，并向应用程序报告错误。</p>
<p>（3）如果客户机的SYN数据段导致某个路由器产生&#8220;目的地不可到达&#8221;类型的ICMP消息，函数以错误返回，错误类型为EHOSTUNREACH或ENETUNREACH。通常TCP协议在接收到这个ICMP消息之后，记录这个消息，然后继续几次发送SYN数据段，在所有的发送都告失败之后，TCP协议检查这个ICMP消息，函数以错误返回。</p>
<p>注：ICMP：Internet 消息控制协议。Internet的运行主要是由Internet的路由器来控制，路由器完成IP数据包的发送和接收，如果发送数据包时发生错误，路由器使用ICMP协议来报告这些错误。ICMP数据包是封装在IP数据包的数据部分中进行传输的，其格式如下：</p>
<p>类型</p>
<p>码</p>
<p>校验和</p>
<p>数据</p>
<p>0 8 16 24 31</p>
<p>类型：指出ICMP数据包的类型。</p>
<p>代码：提供ICMP数据包的进一步信息。</p>
<p>校验和：提供了对整个ICMP数据包内容的校验和。</p>
<p>ICMP数据包主要有以下类型：</p>
<p>（1）目的地不可到达：A、目的主机未运行；B、目的地址不存在；C、路由表中没有目的地址对应的条目，因而路由器无法找到去往目的主机的路由。</p>
<p>（2）超时：路由器将接收到的IP数据包的生存时间（TTL）域减1，如果这个域的值变为0，路由器丢弃这个IP数据包，并且发送这种ICMP消息。</p>
<p>（3） 参数出错：当IP数据包中有无效域时发送。</p>
<p>（4） 重定向：将一条新的路径通知主机。</p>
<p>（5） ECHO请求、ECHO回答：这两条消息用语测试目的主机是否可以到达。请求者向目的主机发送ECHO请求ICMP数据包，目的主机在接收到这个ICMP数据包之后，返回ECHO回答ICMP数据包。</p>
<p>（6）时戳请求、时戳回答：ICMP协议使用这两种消息从其他机器处获得其时钟的当前时间。</p>
<p>调用函数connect的过程中，当客户机TCP协议发送了SYN数据段的确认之后，TCP状态由CLOSED状态转为SYN_SENT状态，在接收到对SYN数据段的确认之后，TCP状态转换成ESTABLISHED状态，函数成功返回。如果调用函数connect失败，应该用close关闭这个套接字描述符，不能再次使用这个套接字描述符来调用函数connect。</p>
<p>注：TCP协议状态转换图：</p>
<p>被动OPEN CLOSE 主动OPEN</p>
<p>（建立TCB） （删除TCB） （建立TCB，</p>
<p>发送SYN）</p>
<p>接收SYN SEND</p>
<p>（发送SYN，ACK） （发送SYN）</p>
<p>接收SYN的ACK（无动作）</p>
<p>接收SYN的ACK 接收SYN，ACK</p>
<p>（无动作） （发送ACK）</p>
<p>CLOSE</p>
<p>（发送FIN） CLOSE 接收FIN</p>
<p>（发送FIN） （发送FIN）</p>
<p>接收FIN</p>
<p>接收FIN的ACK（无动作） （发送ACK） CLOSE（发送FIN）</p>
<p>接收FIN 接收FIN的ACK 接收FIN的ACK</p>
<p>（发送ACK） （无动作） （无动作）</p>
<p>2MSL超时（删除TCB）</p>
<p>（3） 函数bind（）</p>
<p>函数bind将本地地址与套接字绑定在一起，其定义如下：</p>
<p>#include&lt;sys/types.h&gt;</p>
<p>#include&lt;sys/socket.h&gt;</p>
<p>int bind(int sockfd,struct sockaddr * myaddr,int addrlen)；</p>
<p>参数sockfd是函数sockt返回的套接字描述符；参数myaddr是本地地址；参数addrlen是套接字地址结构的长度。执行成功时返回0，否则，返回-1，并设置全局变量errno为错误类型EADDRINUSER。</p>
<p>服务器和客户机都可以调用函数bind来绑定套接字地址，但一般是服务器调用函数bind来绑定自己的公认端口号。绑定操作一般有如下几种组合方式：</p>
<p>表1</p>
<p>程序类型</p>
<p>IP地址</p>
<p>端口号</p>
<p>说明</p>
<p>服务器</p>
<p>INADDR_ANY</p>
<p>非零值</p>
<p>指定服务器的公认端口号</p>
<p>服务器</p>
<p>本地IP地址</p>
<p>非零值</p>
<p>指定服务器的IP地址和公认端口号</p>
<p>客户机</p>
<p>INADDR_ANY</p>
<p>非零值</p>
<p>指定客户机的连接端口号</p>
<p>客户机</p>
<p>本地IP地址</p>
<p>非零值</p>
<p>指定客户机的IP地址连接端口号</p>
<p>客户机</p>
<p>本地IP地址</p>
<p>零</p>
<p>指定客户机的IP地址</p>
<p>分别说明如下：</p>
<p>（1）服务器指定套接字地址的公认端口号，不指定IP地址：即服务器调用bind时，设置套接字的IP地址为特殊的INADDE-ANY，表示它愿意接收来自任何网络设备接口的客户机连接。这是服务器最常用的绑定方式。</p>
<p>（2）服务器指定套接字地址的公认端口号和IP地址：服务器调用bind时，如果设置套接字的IP地址为某个本地IP地址，这表示这台机器只接收来自对应于这个IP地址的特定网络设备接口的客户机连接。当服务器有多块网卡时，可以用这种方式来限制服务器的接收范围。</p>
<p>（3）客户机指定套接字地址的连接端口号：一般情况下，客户机调用connect函数时不用指定自己的套接字地址的端口号。系统会自动为它选择一个未用的端口号，并且用本地的IP地址来填充套接字地址中的相应项。但有时客户机需要使用一个特定的端口号（比如保留端口号），而系统不会未客户机自动分配一个保留端口号，所以需要调用函数bind来和一个未用的保留端口号绑定。</p>
<p>（4）指定客户机的IP地址和连接端口号：表示客户机使用指定的网络设备接口和端口号进行通信。</p>
<p>（5）指定客户机的IP地址：表示客户机使用指定的网络设备接口和端口号进行通信，系统自动为客户机选一个未用的端口号。一般只有在主机有多个网络设备接口时使用。</p>
<p>我们一般不在客户机上使用固定的客户机端口号，除非是必须使用的情况。在客户机上使用固定的端口号有以下不利：</p>
<p>（1）服务器执行主动关闭操作：服务器最后进入TIME_WAIT状态。当客户机再次与这个服务器进行连接时，仍使用相同的客户机端口号，于是这个连接与前次连接的套接字对完全一样，但是一呢、为前次连接处于TIME_WAIT状态，并未消失，所以这次连接请求被拒绝，函connect以错误返回，错误类型为ECONNREFUSED</p>
<p>（2）客户机执行主动关闭操作：客户机最后进入TIME_WAIT状态。当马上再次执行这个客户机程序时，客户机将继续与这个固定客户机端口号绑定，但因为前次连接处于TIME_WAIT状态，并未消失，系统会发现这个端口号仍被占用，所以这次绑定操作失败，函数bind以错误返回，错误类型为EADDRINUSE。</p>
<p>（4） 函数listen（）</p>
<p>函数listen将一个套接字转换为征听套接字，定义如下；</p>
<p>#include&lt;sys/socket,h&gt;</p>
<p>int listen(int sockfd，int backlog)</p>
<p>参数sockfd指定要转换的套接字描述符；参数backlog设置请求队列的最大长度；执行成功时返回0，否则返回-1。函数listen功能有两个：</p>
<p>（1）将一个尚未连接的主动套接字（函数socket创建的可以用来进行主动连接但不能接受连接请求的套接字）转换成一个被动连接套接字。执行listen之后，服务器的TCP状态由CLOSED转为LISTEN状态。</p>
<p>（2） TCP协议将到达的连接请求队列，函数listen的第二个参数指定这个队列的最大长度。</p>
<p>注：参数backlog的作用：</p>
<p>TCP协议为每一个征听套接字维护两个队列：</p>
<p>（1）未完成连接队列：每个尚未完成3次握手操作的TCP连接在这个队列中占有一项。TCP希望仪在接收到一个客户机SYN数据段之后，在这个队列中创建一个新条目，然后发送对客户机SYN数据段的确认和自己的SYN数据段（ACK+SYN数据段），等待客户机对自己的SYN数据段的确认。此时，套接字处于SYN_RCVD状态。这个条目将保存在这个队列中，直到客户机返回对SYN数据段的确认或者连接超时。</p>
<p>（2）完成连接队列：每个已经完成3次握手操作，但尚未被应用程序接收（调用函数accept）的TCP连接在这个队列中占有一项。当一个在未完成连接队列中的连接接收到对SYN数据段的确认之后，完成3次握手操作，TCP协议将它从未完成连接队列移到完成连接队列中。此时，套接字处于ESTABLISHED状态。这个条目将保存在这个队列中，直到应用程序调用函数accept来接收它。</p>
<p>参数backlog指定某个征听套接字的完成连接队列的最大长度，表示这个套接字能够接收的最大数目的未接收连接。如果当一个客户机的SYN数据段到达时，征听套接字的完成队列已经满了，那么TCP协议将忽略这个SYN数据段。对于不能接收的SYN数据段，TCP协议不发送RST数据段，</p>
<p>（5） 函数accept（）</p>
<p>函数accept从征听套接字的完成队列中接收一个已经建立起来的TCP连接。如果完成连接队列为空，那么这个进程睡眠。</p>
<p>#include&lt;sys/socket.h&gt;</p>
<p>int accept(int sockfd，struct sockaddr * addr，int * addrlen)</p>
<p>参数sockfd指定征听套接字描述符；参数addr为指向一个Internet套接字地址结构的指针；参数addrlen为指向一个整型变量的指针。执行成功时，返回3个结果：函数返回值为一个新的套接字描述符，标识这个接收的连接；参数addr指向的结构变量中存储客户机地址；参数addrlen指向的整型变量中存储客户机地址的长度。失败时返回-1。</p>
<p>征听套接字专为接收客户机连接请求，完成3次握手操作而用的，所以TCP协议不能使用征听套接字描述符来标识这个连接，于是TCP协议创建一个新的套接字来标识这个要接收的连接，并将它的描述符发挥给应用程序。现在有两个套接字，一个是调用函数accept时使用的征听套接字，另一个是函数accept返回的连接套接字（connected socket）。一个服务器通常只需创建一个征听套接字，在服务器进程的整个活动期间，用它来接收所有客户机的连接请求，在服务器进程终止前关闭这个征听套接字；对于没一个接收的（accepted）连接，TCP协议都创建一个新的连接套接字来标识这个连接，服务器使用这个连接套接字与客户机进行通信操作，当服务器处理完这个客户机请求时，关闭这个连接套接字。</p>
<p>当函数accept阻塞等待已经建立的连接时，如果进程捕获到信号，函数将以错误返回，错误类型为EINTR。对于这种错误，一般重新调用函数accept来接收连接。</p>
<p>（6） 函数close（）</p>
<p>函数close关闭一个套接字描述符。定义如下：</p>
<p>#include&lt;unistd.h&gt;</p>
<p>int close(int sockfd)；</p>
<p>执行成功时返回0，否则返回-1。与操作文件描述符的close一样，函数close将套接字描述符的引用计数器减1，如果描述符的引用计数大于0，则表示还有进程引用这个描述符，函数close正常返回；如果为0，则启动清除套接字描述符的操作，函数close立即正常返回。</p>
<p>调用close之后，进程将不再能够访问这个套接字，但TCP协议将继续使用这个套接字，将尚未发送的数据传递到对方，然后发送FIN数据段，执行关闭操作，一直等到这个TCP连接完全关闭之后，TCP协议才删除该套接字。</p>
<p>（7） 函数read（）和write（）</p>
<p>用于从套接字读写数据。定义如下：</p>
<p>int read(int fd，char * buf，int len)</p>
<p>int write(int fd，char * buf，int len)</p>
<p>函数执行成功时，返回读或写的数据量的大小，失败时返回-1。</p>
<p>每个TCP套接字都有两个缓冲区：套接字发送缓冲区、套接字接收缓冲区，分别处理发送和接收任务。从网络读、写数据的操作是由TCP协议在内核中完成的：TCP协议将从网络上接收到的数据保存在相应套接字的接收缓冲区中，等待用户调用函数将它们从接收缓冲区拷贝到用户缓冲区；用户将要发送的数据拷贝到相应套接字的发送缓冲区中，然后由TCP协议按照一定的算法处理这些数据。</p>
<p>读写连接套接字的操作与读写文件的操作类似，也可以使用函数read和write。函数read完成将数据从套接字接收缓冲区拷贝到用户缓冲区：当套接字接收缓冲区有数据可读时，1：可读数据量大于函数read指定值，返回函数参数len指定的数据量；2：了度数据量小于函数read指定值，函数read不等待请求的所有数据都到达，而是立即返回实际读到的数据量；当无数据可读时，函数read将阻塞不返回，等待数据到达。</p>
<p>当TCP协议接收到FIN数据段，相当于给读操作一个文件结束符，此时read函数返回0，并且以后所有在这个套接字上的读操作均返回0，这和普通文件中遇到文件结束符是一样的。</p>
<p>当TCP协议接收到RST数据段，表示连接出现了某种错误，函数read将以错误返回，错误类型为ECONNERESET。并且以后所有在这个套接字上的读操作均返回错误。错误返回时返回值小于0。</p>
<p>函数write完成将数据从用户缓冲区拷贝到套接字发送缓冲区的任务：到套接字发送缓冲区有足够拷贝所有用户数据的空间时，函数write将数据拷贝到这个缓冲区中，并返回老辈的数量大小，如果可用空间小于write参数len指定的大小时，函数write将阻塞不返回，等待缓冲区有足够的空间。</p>
<p>当TCP协议接收到RST数据段（当对方已经关闭了这条连接之后，继续向这个套接字发送数据将导致对方TCP协议返回RST数据段），TCP协议接收到RST数据段时，函数write将以错误返回，错误类型为EINTR。以后可以继续在这个套接字上写数据。</p>
<p>（8） 函数getsockname（）和getpeername（）</p>
<p>函数getsockname返回套接字的本地地址；函数getpeername返回套接字对应的远程地址。</p>
<p>10、 结束语</p>
<p>网络程序设计全靠套接字接收和发送信息。上文主要讲述了Linux 下Socket的基本概念、Sockets API以及Socket所涉及到的TCP常识。</p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/32912.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-09-26 14:40 <a href="http://www.cppblog.com/niewenlong/archive/2007/09/26/32912.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>完成端口模型代码</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30239.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:57:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30239.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30239.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30239.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30239.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30239.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 最近要做一个网络方面的小东东，基于C/S模式的。都说IOCP可以使系统达到最佳的性能，因此我就比划了两下，献丑了。抄书开始。&nbsp;&nbsp;&nbsp; 从本质上说，完成端口模型要求创建一个windows完成端口对象，该对象通过指定数量的线程，对重叠I/O请求进行管理，以便为已经完成的重叠I/O请求提供服务。&nbsp;&nbsp;&nbsp; 首先要创建一个I/O完成端口对象，用它面向任...&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2007/08/17/30239.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/30239.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:57 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30239.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SPI</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30238.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:19:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30238.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30238.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30238.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30238.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30238.html</trackback:ping><description><![CDATA[随着WOSA模型的出现，在Ws2_32.dll和协议堆栈之间多了一层开放的接口，就是SPI。Winsock2&nbsp;SPI和Winsock2&nbsp;API在性质上是一样的，只是他们的服务对象不同，API提供的接口工作在应用层的上层，为应用程序提供接口，在Winsock之上，而SPI提供的接口工作在应用层的最底层，为核心的网络服务提供接口，在Winsock之下。如果按照OSI分层标准来划分，SPI应该是工作在会话层，API工作在应用层。如果有人非要象我一开始这样，非要分出个一二三四的话，这样的理解或许能让你得到一点满足，当然这种理解不是完全正确的，但是至少在层次的先后上，这样的理解应该是正确的。我们的工作逐渐进入了OSI的下层，要学习，就要多费一些口舌了。<br><br>SPI的上沿是Ws2_32.dll，而它的下沿是协议堆栈和名字空间，当然分层服务可以多层叠放，扩展上下沿。这里讲述向协议堆栈的服务。SPI包含两种服务，一种是基础服务，一种是分层服务。<br><br>SPI实例实际上就是一个动态库，开放了一个初始化函数：WSPStartup&nbsp;或者&nbsp;NSPStartup。其他函数的指针通过一个函数分配表在此初始化函数中指定给Ws2_32.dll。Ws2_32.dll在需要的时候将相应的服务/协议实现模块装载入内存，在不需要的时候卸载。<br><br>一般来说，当一个应用程序调用&nbsp;Winsock2&nbsp;API&nbsp;函数的时候，Winsock2&nbsp;都会调用相应的&nbsp;Winsock2&nbsp;SPI&nbsp;函数。如&nbsp;WSPAccept,&nbsp;WSPConnect,&nbsp;etc.&nbsp;下述特例不经过SPI:<br>htonl,htons,ntohs,ntohl,inet_addr,inet_ntoa,gethostname,getXbyY,WSAAsnyGetXByY,wsacANCELaSYNCrEQUEST,WSAEnumProtocols,WSAIsBlocking,WSASetBlocking,WSAUnhookBlocking,WSAGetLastError,WSASetLastError,WSACreateEvent,WSACloseEvent,WSASetEvent,WSAResetEvent,WSAWaitForMultipleEvents.SPI的函数原型定义在ws2spi.h中，前缀都有WSP,NSP,WPU,WSC,有兴趣可以浏览一下。相应的对应函数有30个。都会在WSPStartup的实现中得到这些函数指针。<br><br>要注意的是：WSPStartup并不是在WSAStartup调用时被调用的，一旦要WSPStartup的时候也就是说明需要用到服务提供者了，那么Ws2_32什么时候加载服务提供者呢？应用程序创建了套接字的时候，Ws2_32就根据套接字的地址家族，类型，协议信息等加载相应的提供者，这时候，WSPStartup就会被调用，服务提供者就开始调度它的传输函数等等内容了。<br><br>现在有个问题，既然我们知道Windows&nbsp;Sockets动态库和协议堆栈之间没有直接联系了，那么，如果我的应用程序想要调用我的SPI实例的扩展函数的时候那该怎么办呢？Ws2_32.dll不可能再扩展专门的函数调用，让他按照规范再去调用协议堆栈提供者的扩展函数。其实，Ws2_32.dll提供了一个函数来解决这个额外的扩展问题，那就是WSAIoctl函数，通过命令码&nbsp;SIO_GET_EXTENSION_FUNCTION_POINTER，输入缓冲区是扩展函数的标识符，输出参数就是该函数的指针了。那么，应用程序就可以直接跳过Ws2_32.dll而直接调用扩展函数。<br><br>在动手编写SPI实例之前，我们需要了解一下基础服务和分层服务的区别以及系统如何标志这两种服务，然后讲述如何在系统中安装一个自己的SPI实例，来扩展你的网络传输功能。以及如何卸载自己的SPI实例，来恢复系统原来默认的设置。最后再来讲述如何编写SPI的实例。<br><br>基础服务执行核心的网络传输协议功能，而分层服务在基础服务的基础上执行自定义的通讯控制，所有的真正的数据交换是通过基础服务提供者来实现的，分层服务提供者无需再去实现网络协议的功能。简单的网络封包的截取和管理可以在此进行。<br><br>需要提及套接字句柄。当调用下层SPI的时候，如果调用WSPSocket,WSPAccept,WSPJoinLeaf时，服务提供者必须返回一个套接字句柄，Winsock2允许在此句柄上直接调用&nbsp;ReadFile/WriteFile来读写数据。这个句柄有两种类型：IFS（可安装文件系统句柄）和&nbsp;Non&nbsp;IFS（不可安装的文件系统句柄）。微软的基础服务提供者都是IFS句柄，但是分层服务提供者可以是IFS，也可以是Non&nbsp;IFS。但是如果分层提供者是IFS提供者，就必须将下一级IFS提供者的句柄上传到上一级提供者或者WS2_32.dll，此时对套接字的ReadFile/WriteFile调用会跳过分层服务提供者的WSPSend/WSPRecv函数而直接通过基础服务提供者的功能进行数据读写，而且分层服务提供者将不能处理一个完成端口的重叠I/0操作，这些操作也将绕过分层提供者，而直接通过基础提供者完成数据的读写。所以，最好将分层服务提供者定义为Non&nbsp;IFS句柄的服务提供者，这样就可以对网络数据进行完整的监控。具体做法是在协议信息结构中将dwServiceFlags1标志的XP1_IFS_HANDLES标志去掉。这样，你的分层提供这就是一个Non&nbsp;IFS分层服务提供者，Non&nbsp;IFS服务提供者使用WSPCreateSocketHandle上调函数建立套接字句柄，Winsock2会把ReadFile/WriteFile的调用重定向到WSPSend和WSPReceive上去，但这无疑会对系统性能产生负面影响。<br><br>Windows系统为服务提供者维护了一个目录，这些信息保存在注册表中，要更改/安装服务提供者，就必须对此目录信息进行维护。系统提供了一些函数简化对此信息的访问，他们都是以WSC开头。<br><br>对于基础服务提供者的安装相当简单，只要准备一个WSAPROTOCOL_INFOW结构，用来代表基础服务提供者信息，正确填充合适的值，然后调用WSCInstallProvider函数就可以完成基础服务提供者的安装。但是这种方法只对新增加的基础服务提供者来讲是很方便的，但是我们往往要利用系统的基础服务提供者来实现基本的协议，比如说TCP协议的实现，所以在这种情况下，方便的方法是不通过WSCInstallProvider函数，而是我们自己修改目录条目信息，对于基础提供者，只要把基础提供者的动态库路径信息改成我们自己虚拟的基础服务提供者路径就可以了。这样安装的后续工作必须是把所有调用传给原来的基础服务提供者，所以在我们虚拟的服务提供者程序中，必须能够检索到原来的服务提供者路径信息。记住，安装之前千万要备份原来的目录信息。否则，一旦发生错误会引起网络无法访问。<br><br>基础服务提供者安装代码实例如下所示：<br><br>void&nbsp;CBSPinstallDlg::OnBtnInstall()&nbsp;<br>{<br>//&nbsp;Update&nbsp;m_strFileName&nbsp;which&nbsp;is&nbsp;our&nbsp;mimic&nbsp;Base&nbsp;Provider&nbsp;filename<br>UpdateData(TRUE);<br>if(0==BackupBSP())&nbsp;InstallBSP();<br>}<br><br>int&nbsp;CBSPinstallDlg::EnumRegisterKey(HKEY&nbsp;hKey,TCHAR*&nbsp;pszKeyName,TCHAR**&nbsp;ppBuf,DWORD*&nbsp;pdwBufSize)<br>{<br>HKEY&nbsp;hSubKey=NULL;<br>if(RegOpenKeyEx(hKey,pszKeyName,0,KEY_ALL_ACCESS,&amp;hSubKey)!=ERROR_SUCCESS)&nbsp;<br>return&nbsp;-1;<br>TCHAR&nbsp;szKey[20]={'\0'};<br>if(hKey==HKEY_CURRENT_CONFIG)&nbsp;strcpy(szKey,"HKEY_LOCAL_MACHINE"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else<br>if(hKey==HKEY_CURRENT_USER)&nbsp;strcpy(szKey,"HKEY_CURRENT_USER"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else<br>if(hKey==HKEY_LOCAL_MACHINE)&nbsp;strcpy(szKey,"HKEY_LOCAL_MACHINE"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else<br>if(hKey==HKEY_USERS)&nbsp;strcpy(szKey,"HKEY_USERS"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else<br>if(hKey==HKEY_PERFORMANCE_DATA)&nbsp;strcpy(szKey,"HKEY_PERFORMANCE_DATA"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else<br>if(hKey==HKEY_DYN_DATA)&nbsp;strcpy(szKey,"HKEY_DYN_DATA"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;&nbsp;else&nbsp;<br>return&nbsp;-1;<br><br>int&nbsp;len=strlen(szKey)+strlen(pszKeyName)+5;<br>TCHAR*&nbsp;pszItem=new&nbsp;TCHAR[len+1];<br>memset(pszItem,0,len+1);<br>sprintf(pszItem,"[%s\\%s]\r\n",szKey,pszKeyName);<br><br>TCHAR*&nbsp;pBuf=*ppBuf;<br>DWORD&nbsp;dwBufSize=*pdwBufSize;<br>TCHAR*&nbsp;pTmp=new&nbsp;TCHAR[dwBufSize+len];<br>memset(pTmp,0,dwBufSize+len);<br>memmove(pTmp,pBuf,dwBufSize);<br>memmove(pTmp+dwBufSize,pszItem,len);<br>delete[]&nbsp;pszItem;&nbsp;pszItem=NULL;<br>delete[]&nbsp;pBuf;&nbsp;pBuf=NULL;<br>dwBufSize+=len;<br>*ppBuf=pTmp;<br>*pdwBufSize=dwBufSize;<br><br>//&nbsp;"Num_Catalog_Entries"=dword:00000013<br>//&nbsp;"Next_Catalog_Entry_ID"=dword:0000043e<br>//&nbsp;"Serial_Access_Num"=dword:00000014<br>DWORD&nbsp;cbClass=0;<br>DWORD&nbsp;cSubKeys=0;<br>DWORD&nbsp;cbMaxSubKeyLen=0;<br>DWORD&nbsp;cbMaxClassLen=0;<br>DWORD&nbsp;cValues=0;<br>DWORD&nbsp;cbMaxValueNameLen=0;<br>DWORD&nbsp;cbMaxValueLen=0;<br>int&nbsp;nRet=RegQueryInfoKey(hSubKey,<br>NULL,<br>&amp;cbClass,<br>NULL,<br>&amp;cSubKeys,<br>&amp;cbMaxSubKeyLen,<br>&amp;cbMaxClassLen,<br>&amp;cValues,<br>&amp;cbMaxValueNameLen,<br>&amp;cbMaxValueLen,<br>NULL,<br>NULL);<br>if(nRet!=ERROR_SUCCESS)&nbsp;<br>return&nbsp;-1;<br>for(DWORD&nbsp;dwIndex=0;dwIndex&lt;cValues;dwIndex++)<br>{<br>DWORD&nbsp;dwItemNameSize=cbMaxValueNameLen+1;<br>TCHAR*&nbsp;pszItemName=new&nbsp;TCHAR[dwItemNameSize];<br>memset(pszItemName,0,dwItemNameSize);<br>DWORD&nbsp;dwDataSize=cbMaxValueLen+1;<br>BYTE*&nbsp;pbyData=new&nbsp;BYTE[dwDataSize];<br>memset(pbyData,0,dwDataSize);<br>DWORD&nbsp;dwType=0;<br>nRet=RegEnumValue(hSubKey,dwIndex,pszItemName,&amp;dwItemNameSize,NULL,&amp;dwType,pbyData,&amp;dwDataSize);<br>if(nRet!=ERROR_SUCCESS)<br>{<br>delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>return&nbsp;-1;<br>}<br>TCHAR*&nbsp;pBuf=*ppBuf;<br>DWORD&nbsp;dwBufSize=*pdwBufSize;<br>if(dwDataSize&gt;0)<br>{<br>TCHAR*&nbsp;szTmp=new&nbsp;TCHAR[dwDataSize*4+266];<br>memset(szTmp,0,dwDataSize*4+266);<br>szTmp[0]='"';<br>int&nbsp;nPos=1;<br>memmove(szTmp+nPos,pszItemName,strlen(pszItemName));<br>nPos+=strlen(pszItemName);<br>szTmp[nPos]='"';nPos++;<br>szTmp[nPos]='=';nPos++;<br>switch(dwType)<br>{<br>case&nbsp;REG_SZ:<br>szTmp[nPos]='"';nPos++;<br>memmove(szTmp,pbyData,dwDataSize);<br>nPos+=dwDataSize;<br>szTmp[nPos]='"';nPos++;<br>szTmp[nPos]='\r';nPos++;<br>szTmp[nPos]='\n';nPos++;<br>break;<br>case&nbsp;REG_DWORD:<br>{<br>char&nbsp;sz[20]="dword:\0";<br>sprintf(sz,"%s%08x",sz,*(DWORD*)pbyData);<br>memmove(szTmp+nPos,sz,strlen(sz));<br>nPos+=strlen(sz);<br>}<br>break;<br>case&nbsp;REG_BINARY:<br>{<br>char&nbsp;sz[20]="hex:\0";<br>memmove(szTmp+nPos,sz,strlen(sz));<br>nPos+=strlen(sz);<br>int&nbsp;j=0;<br>for(UINT&nbsp;i=0;i&lt;dwDataSize;i++)<br>{<br>if((&nbsp;(j==0&nbsp;&amp;&amp;&nbsp;nPos%78==0)&nbsp;||&nbsp;\<br>&nbsp;(&nbsp;j&gt;0&nbsp;&amp;&amp;&nbsp;(nPos-81-(j-1)*80)%77==0)&nbsp;\<br>&nbsp;&nbsp;&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;&amp;&amp;&nbsp;i&lt;dwDataSize-1)<br>{<br>j++;<br>szTmp[nPos]='\\';nPos++;<br>szTmp[nPos]='\r';nPos++;<br>szTmp[nPos]='\n';nPos++;<br>if(j&gt;0)<br>{<br>szTmp[nPos]='&nbsp;';nPos++;<br>szTmp[nPos]='&nbsp;';nPos++;<br>}<br>}<br>if(i&lt;dwDataSize-1)<br>{<br>TCHAR&nbsp;szAppend[20]={'\0'};<br>sprintf(szAppend,"%02x,",pbyData[i]);<br>strcat(szTmp,szAppend);<br>nPos+=3;<br>}<br>else<br>{<br>TCHAR&nbsp;szAppend[20]={'\0'};<br>sprintf(szAppend,"%02x",pbyData[i]);<br>strcat(szTmp,szAppend);<br>nPos+=2;<br>}<br>}<br>}<br>default:<br>break;<br>}<br>delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>szTmp[nPos]='\r';nPos++;<br>szTmp[nPos]='\n';nPos++;<br>TCHAR*&nbsp;pTmp=new&nbsp;TCHAR[dwBufSize+nPos];<br>memset(pTmp,0,dwBufSize+nPos);<br>memmove(pTmp,pBuf,dwBufSize);<br>memmove(pTmp+dwBufSize,szTmp,nPos);<br>delete[]&nbsp;szTmp;szTmp=NULL;<br>delete[]&nbsp;pBuf;pBuf=NULL;<br>dwBufSize+=nPos;<br>*ppBuf=pTmp;<br>*pdwBufSize=dwBufSize;<br>}<br>if(pszItemName)&nbsp;delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>if(pbyData)&nbsp;delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>}<br><br>{<br>TCHAR*&nbsp;pBuf=*ppBuf;<br>DWORD&nbsp;dwBufSize=*pdwBufSize;<br>TCHAR*&nbsp;pTmp=new&nbsp;TCHAR[dwBufSize+2];<br>memset(pTmp,0,dwBufSize+2);<br>memmove(pTmp,pBuf,dwBufSize);<br>pTmp[dwBufSize]='\r';<br>pTmp[dwBufSize+1]='\n';<br>*ppBuf=pTmp;<br>*pdwBufSize=dwBufSize+2;<br>delete[]&nbsp;pBuf;&nbsp;pBuf=NULL;<br>}<br><br>DWORD&nbsp;dwSubKeyLen=cbMaxSubKeyLen+1;<br>//&nbsp;[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries]<br>for(dwIndex=0;dwIndex&lt;cSubKeys;dwIndex++)<br>{ <br>TCHAR*&nbsp;szKeyName=new&nbsp;TCHAR[dwSubKeyLen];<br>memset(szKeyName,0,dwSubKeyLen);<br>int&nbsp;nRet=RegEnumKey(hSubKey,dwIndex,szKeyName,dwSubKeyLen);<br>if(nRet!=ERROR_SUCCESS)<br>{<br>delete[]&nbsp;szKeyName;&nbsp;szKeyName=NULL;<br>return&nbsp;-1;<br>}<br>TCHAR&nbsp;szKeyNameAnother[266]={'\0'};<br>wsprintf(szKeyNameAnother,"%s\\%s",pszKeyName,szKeyName);<br>delete[]&nbsp;szKeyName;&nbsp;szKeyName=NULL;<br>nRet=EnumRegisterKey(HKEY_LOCAL_MACHINE,szKeyNameAnother,ppBuf,pdwBufSize);<br>if(nRet==-1)&nbsp;return&nbsp;-1;<br>}<br><br>RegCloseKey(hSubKey);<br>return&nbsp;0;<br>}<br><br>int&nbsp;CBSPinstallDlg::BackupBSP()<br>{<br>TCHAR&nbsp;szSysDir[266]={'\0'};<br>GetWindowsDirectory(szSysDir,266);<br>TCHAR&nbsp;szBackupFile[266]={'\0'};<br>sprintf(szBackupFile,"%s\\MinBSP.reg",szSysDir);<br>CFileStatus&nbsp;status;<br>if(CFile::GetStatus(szBackupFile,status))<br>{<br>if(IDNO==::MessageBox(NULL,"你已经安装了基础服务提供者，是不是还要继续安装？","确认",MB_YESNO))<br>{<br>PostQuitMessage(0);<br>return&nbsp;-1;<br>}<br>}<br><br>if(m_strFileName.GetLength()==0)<br>{<br>AfxMessageBox("请指定基础服务提供者。"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>m_edtFileName.SetFocus();<br>m_edtFileName.SetSel(0,-1);<br>return&nbsp;-1;<br>}<br><br>//&nbsp;开始安装<br>//&nbsp;首先备份原来的服务提供者目录条目信息<br>CFile&nbsp;f;<br>if(f.Open(szBackupFile,CFile::modeCreate|CFile::modeReadWrite))<br>{<br>TCHAR&nbsp;szTmp[266]="Windows&nbsp;Registry&nbsp;Editor&nbsp;Version&nbsp;5.00\r\n\r\n\0";<br>DWORD&nbsp;dwBufSize=strlen(szTmp);<br>TCHAR*&nbsp;lpBuf=new&nbsp;TCHAR[dwBufSize];<br>memset(lpBuf,0,dwBufSize);<br>memmove(lpBuf,szTmp,dwBufSize);<br><br>TCHAR&nbsp;szKeyName[1024]="SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters\\Protocol_Catalog9\0";<br>int&nbsp;nRet=EnumRegisterKey(HKEY_LOCAL_MACHINE,szKeyName,&amp;lpBuf,&amp;dwBufSize);<br>if(nRet==-1)&nbsp;{f.Close();delete[]&nbsp;lpBuf;lpBuf=NULL;return&nbsp;-1;}<br><br>nRet=dwBufSize*2+2;<br>WCHAR*&nbsp;pwszResult=new&nbsp;WCHAR[nRet];<br>memset(pwszResult,0,nRet);<br>nRet=MultiByteToWideChar(CP_ACP,0,lpBuf,dwBufSize,pwszResult,nRet);<br>BYTE&nbsp;by=0xFF;<br>f.Write(&amp;by,1);<br>by=0xFE;<br>f.Write(&amp;by,1);<br>f.Write(pwszResult,nRet*2);<br>f.Close();<br>delete[]&nbsp;pwszResult;&nbsp;pwszResult=NULL;<br>delete[]&nbsp;lpBuf;lpBuf=NULL;<br>}<br>return&nbsp;0;<br>}<br><br>void&nbsp;CBSPinstallDlg::InstallBSP()<br>{<br>//&nbsp;备份完成<br>//&nbsp;接下来，更改系统原来的基础服务提供者目录条目信息为我的基础提供者信息<br>//&nbsp;枚举协议<br>LPWSAPROTOCOL_INFOW&nbsp;lpProtocolInfo=NULL;<br>DWORD&nbsp;dwProtocolInfoSize=0;<br>int&nbsp;nErrorCode=0;<br>int&nbsp;nRet=WSCEnumProtocols(NULL,lpProtocolInfo,&amp;dwProtocolInfoSize,&amp;nErrorCode);<br>if(nRet==SOCKET_ERROR&nbsp;&amp;&amp;&nbsp;nErrorCode!=WSAENOBUFS)<br>{<br>TRACE1("WSCEnumProtocols()&nbsp;returns&nbsp;error:&nbsp;%d\n",nErrorCode);<br>return;<br>}<br>lpProtocolInfo=(LPWSAPROTOCOL_INFOW)&nbsp;new&nbsp;BYTE[dwProtocolInfoSize];<br>memset(lpProtocolInfo,0,dwProtocolInfoSize);<br>__try<br>{<br>//&nbsp;取得协议信息<br>int&nbsp;nTotalProtocols=WSCEnumProtocols(NULL,lpProtocolInfo,&amp;dwProtocolInfoSize,&amp;nErrorCode);<br>if(nTotalProtocols==SOCKET_ERROR)&nbsp;return;<br>//&nbsp;查找TCP/IP协议信息<br>TCHAR&nbsp;szTCPIP[20]="[TCP/IP]\0";<br>TCHAR&nbsp;szTCP95[20]=".TCP\0";<br>TCHAR&nbsp;szProtocol[MAX_PATH]={'\0'};<br>TCHAR&nbsp;szKeyName[]="SYSTEM\\CurrentControlSet\\Services\\WinSock2\\Parameters\\Protocol_Catalog9\\Catalog_Entries\0";<br>HKEY&nbsp;hSubKey=NULL;<br>if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,szKeyName,0,KEY_ALL_ACCESS,&amp;hSubKey)!=ERROR_SUCCESS)&nbsp;return;<br>for(int&nbsp;i=0;i&lt;nTotalProtocols;i++)<br>{<br>memset(szProtocol,0,MAX_PATH);<br>WCHAR*&nbsp;pwsz=(lpProtocolInfo+i)-&gt;szProtocol;<br>BSTR&nbsp;bstr=SysAllocString(pwsz);<br>WideCharToMultiByte(CP_ACP,0,bstr,-1,szProtocol,255,NULL,NULL);<br>SysFreeString(bstr);<br>//&nbsp;if(strstr(szProtocol,szTCPIP)!=NULL&nbsp;||&nbsp;strstr(szProtocol,szTCP95)!=NULL)&nbsp;break;<br>if((lpProtocolInfo+i)-&gt;ProtocolChain.ChainLen==1)<br>{<br>//&nbsp;更改基础服务提供者信息<br>TCHAR&nbsp;szSubKeyName[MAX_PATH]={'\0'};<br>int&nbsp;nRet=RegEnumKey(hSubKey,i,szSubKeyName,MAX_PATH);<br>if(nRet!=ERROR_SUCCESS)&nbsp;return;<br>HKEY&nbsp;hSubKeySub=NULL;<br>DWORD&nbsp;dwSubKeyLen=strlen(szKeyName);<br>TCHAR*&nbsp;pszKeyName=new&nbsp;TCHAR[dwSubKeyLen+strlen(szSubKeyName)+2];<br>memset(pszKeyName,0,dwSubKeyLen+strlen(szSubKeyName)+2);<br>wsprintf(pszKeyName,"%s\\%s",szKeyName,szSubKeyName);<br>if(RegOpenKeyEx(HKEY_LOCAL_MACHINE,pszKeyName,0,KEY_ALL_ACCESS,&amp;hSubKeySub)!=ERROR_SUCCESS)<br>{<br>if(pszKeyName)&nbsp;delete[]&nbsp;pszKeyName;&nbsp;pszKeyName=NULL;<br>return;<br>}<br>DWORD&nbsp;cbClass=0;<br>DWORD&nbsp;cSubKeys=0;<br>DWORD&nbsp;cbMaxSubKeyLen=0;<br>DWORD&nbsp;cbMaxClassLen=0;<br>DWORD&nbsp;cValues=0;<br>DWORD&nbsp;cbMaxValueNameLen=0;<br>DWORD&nbsp;cbMaxValueLen=0;<br>nRet=RegQueryInfoKey(hSubKeySub,<br>NULL,<br>&amp;cbClass,<br>NULL,<br>&amp;cSubKeys,<br>&amp;cbMaxSubKeyLen,<br>&amp;cbMaxClassLen,<br>&amp;cValues,<br>&amp;cbMaxValueNameLen,<br>&amp;cbMaxValueLen,<br>NULL,<br>NULL);<br>if(nRet!=ERROR_SUCCESS)&nbsp;<br>{<br>cbMaxValueNameLen=MAX_PATH;<br>cbMaxValueLen=MAX_PATH+sizeof(WSAPROTOCOL_INFOW)+2;<br>}<br>DWORD&nbsp;dwItemNameSize=cbMaxValueNameLen+1;<br>TCHAR*&nbsp;pszItemName=new&nbsp;TCHAR[dwItemNameSize];<br>memset(pszItemName,0,dwItemNameSize);<br>DWORD&nbsp;dwDataSize=cbMaxValueLen+1;<br>BYTE*&nbsp;pbyData=new&nbsp;BYTE[dwDataSize];<br>memset(pbyData,0,dwDataSize);<br>DWORD&nbsp;dwType=0;<br>nRet=RegEnumValue(hSubKeySub,0,pszItemName,&amp;dwItemNameSize,NULL,&amp;dwType,pbyData,&amp;dwDataSize);<br>if(nRet!=ERROR_SUCCESS)<br>{<br>if(pszKeyName)&nbsp;delete[]&nbsp;pszKeyName;&nbsp;pszKeyName=NULL;<br>if(pszItemName)&nbsp;delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>if(pbyData)&nbsp;delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>return;<br>}<br>memset(pbyData,0,MAX_PATH);<br>TCHAR&nbsp;szDllPath[MAX_PATH+2];<br>wsprintf(szDllPath,"%%SystemRoot%%\\System32\\%s",m_strFileName);<br>int&nbsp;nLen=strlen(szDllPath);<br>memmove(pbyData,szDllPath,nLen);<br>nRet=RegSetValueEx(hSubKeySub,pszItemName,NULL,dwType,pbyData,dwDataSize);<br>if(nRet!=ERROR_SUCCESS)<br>{<br>if(pszKeyName)&nbsp;delete[]&nbsp;pszKeyName;&nbsp;pszKeyName=NULL;<br>if(pszItemName)&nbsp;delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>if(pbyData)&nbsp;delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>return;<br>}<br>if(pszKeyName)&nbsp;delete[]&nbsp;pszKeyName;&nbsp;pszKeyName=NULL;<br>if(pszItemName)&nbsp;delete[]&nbsp;pszItemName;&nbsp;pszItemName=NULL;<br>if(pbyData)&nbsp;delete[]&nbsp;pbyData;&nbsp;pbyData=NULL;<br>}<br>}<br><br>if(i&lt;nTotalProtocols)&nbsp;TRACE1("TCP/IP&nbsp;协议信息在目录条目中的第&nbsp;%d&nbsp;项。\n",i+1);<br>else&nbsp;TRACE("无法根据协议描述判断TCP/IP协议信息。\n"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>}<br>__finally<br>{<br>if(lpProtocolInfo)&nbsp;delete[]&nbsp;lpProtocolInfo;<br>lpProtocolInfo=NULL;<br>}<br>}<br><br>而对于分层服务提供者而言，安装要复杂一点。他需要建立两个WSAPROTOCOL_INFOW结构，一个代表分层提供者（ChainLen==0）,另一个代表协议链（ChainLen&gt;1），利用该协议链把分层服务提供者和基础服务提供者连接起来。<br>分层服务提供者安装的具体的步骤，概括起来大致分为五步：<br>第一，初始化两个协议信息结构，初始化可以借助于WSCEnumProtocols函数取回的信息来完成；<br>第二，用WSCInstallProvider调用来安装你的分层服务提供者；<br>第三，在第二步成功的基础上，列举所有的目录条目，获得新安装的分层提供者的目录编号。并利用这个目录信息来设置一个协议链的目录信息，通过这个协议链，将你的分层提供者和基础服务提供者（或者是它下一层的分层协议提供者）连接起来。<br>第四，调用WSCInstallProvider安装这个协议链。<br>第五，分层服务提供者安装成功后，Windows会在服务提供者目录的最后加入这个新的条目。但是我们为了让我们的分层服务提供者成为系统默认的TCP服务提供者的话，就必须对目录进行重新排列，让新安装的协议链目录条目放在最上面。这些排序过程通过调用WSCWriteProviderOrder完成，该函数将所有的服务提供者重新排序。需要包含&nbsp;&lt;sporder.h&gt;头文件和sporder.lib库。<br><br>以下是单纯针对TCP协议（其他协议处理方法雷同）的分层服务提供者示例：<br><br>int&nbsp;CLSPinstallDlg::EnumProtocols(LPWSAPROTOCOL_INFOW*&nbsp;lppInfo,DWORD&amp;&nbsp;dwProtocolInfoSize,int&amp;&nbsp;nTotalProtocols)<br>{<br>LPWSAPROTOCOL_INFOW&nbsp;lpProtocolInfo=*lppInfo;<br>if(lpProtocolInfo!=NULL)&nbsp;delete[]&nbsp;lpProtocolInfo;&nbsp;lpProtocolInfo=NULL;<br>int&nbsp;nErrorCode=0;<br>int&nbsp;nRet=WSCEnumProtocols(NULL,lpProtocolInfo,&amp;dwProtocolInfoSize,&amp;nErrorCode);<br>if(nRet==SOCKET_ERROR&amp;&amp;nErrorCode!=WSAENOBUFS)&nbsp;return&nbsp;nErrorCode;<br>lpProtocolInfo&nbsp;=&nbsp;(LPWSAPROTOCOL_INFOW)new&nbsp;BYTE[dwProtocolInfoSize];<br>ASSERT(lpProtocolInfo!=NULL);<br>nTotalProtocols&nbsp;=&nbsp;WSCEnumProtocols(NULL,lpProtocolInfo,&amp;dwProtocolInfoSize,&amp;nErrorCode);<br>if(nTotalProtocols==&nbsp;SOCKET_ERROR)&nbsp;return&nbsp;-1;<br>*lppInfo=lpProtocolInfo;<br><br>return&nbsp;0;<br>}<br><br>void&nbsp;CLSPinstallDlg::InstallLSP()<br>{<br>LPWSAPROTOCOL_INFOW&nbsp;lpProtocolInfo=NULL;<br>DWORD&nbsp;dwProtocolInfoSize=0;<br>int&nbsp;nTotalProtocols=0;<br>_try<br>{<br>//&nbsp;第一步，初始化两个协议信息结构，初始化可以借助于WSCEnumProtocols函数取回的信息来完成；<br>int&nbsp;nRet=EnumProtocols(&amp;lpProtocolInfo,dwProtocolInfoSize,nTotalProtocols);<br>if(nRet!=0)<br>{<br>TRACE1("WSCEnumProtocols()&nbsp;returns&nbsp;error:&nbsp;%d\n",nRet);<br>return;<br>}<br>DWORD&nbsp;dwLayeredCatalogId,dwTcpCatalogId;<br>WSAPROTOCOL_INFOW&nbsp;stuLayeredInfo,stuTcpChainInfo;<br>for(int&nbsp;i=0;i&lt;nTotalProtocols;i++)<br>{<br>if(lpProtocolInfo[i].iAddressFamily&nbsp;==&nbsp;AF_INET&nbsp;&amp;&amp;&nbsp;lpProtocolInfo[i].iProtocol&nbsp;==&nbsp;IPPROTO_TCP)<br>{<br>dwTcpCatalogId&nbsp;=&nbsp;lpProtocolInfo[i].dwCatalogEntryId;<br>memmove(&amp;stuTcpChainInfo,&nbsp;&amp;lpProtocolInfo[i],&nbsp;sizeof(WSAPROTOCOL_INFOW));<br>memmove(&amp;stuLayeredInfo,&nbsp;&amp;lpProtocolInfo[i],&nbsp;sizeof(WSAPROTOCOL_INFOW));<br>stuTcpChainInfo.dwServiceFlags1&nbsp;=&nbsp;lpProtocolInfo[i].dwServiceFlags1&nbsp;&amp;&nbsp;~XP1_IFS_HANDLES;&nbsp;<br>break;<br>}<br>}<br><br>//&nbsp;第二步，用WSCInstallProvider调用来安装你的分层服务提供者；<br>wcscpy(stuLayeredInfo.szProtocol,&nbsp;L"Mini&nbsp;Layered&nbsp;Provider"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>stuLayeredInfo.ProtocolChain.ChainLen&nbsp;=&nbsp;LAYERED_PROTOCOL;<br>WCHAR&nbsp;wszDllPath[MAX_PATH*2+2]={'\0'};<br>swprintf(wszDllPath,L"%%SystemRoot%%\\System32\\%s",m_strFileName);<br>int&nbsp;nErrorCode=0;<br>nRet=WSCInstallProvider(&amp;guidMiniProvider,wszDllPath,&amp;stuLayeredInfo,1,&amp;nErrorCode);<br>if(nRet==SOCKET_ERROR)<br>{<br>printf("WSCInstallProvider&nbsp;failed&nbsp;%d\n",&nbsp;nErrorCode);<br>return;<br>}<br><br>//&nbsp;第三步，在第二步成功的基础上，列举所有的目录条目，获得新安装的分层提供者的目录编号。<br>//&nbsp;并利用这个目录信息来设置一个协议链的目录信息，通过这个协议链，将分层提供者和基础服务提供者<br>//&nbsp;（或者是它下一层的分层协议提供者）连接起来。<br>nRet=EnumProtocols(&amp;lpProtocolInfo,dwProtocolInfoSize,nTotalProtocols);<br>if(nRet!=0)<br>{<br>TRACE1("WSCEnumProtocols()&nbsp;returns&nbsp;error:&nbsp;%d\n",nRet);<br>return;<br>}<br>for&nbsp;(i=0;i&lt;nTotalProtocols;i++)<br>{<br>if(memcmp(&amp;lpProtocolInfo[i].ProviderId,&nbsp;&amp;guidMiniProvider,sizeof&nbsp;(GUID))==0)<br>{<br>dwLayeredCatalogId&nbsp;=&nbsp;lpProtocolInfo[i].dwCatalogEntryId;<br>break;<br>}<br>}<br>WCHAR&nbsp;wszChainName[MAX_PATH*2+2]={'\0'};<br>swprintf(wszChainName,&nbsp;L"Mini&nbsp;Layered&nbsp;TCP&nbsp;[%s]",&nbsp;stuTcpChainInfo.szProtocol);<br>wcscpy(stuTcpChainInfo.szProtocol,wszChainName);<br>if&nbsp;(stuTcpChainInfo.ProtocolChain.ChainLen==BASE_PROTOCOL)<br>{<br>stuTcpChainInfo.ProtocolChain.ChainEntries[1]&nbsp;=&nbsp;dwTcpCatalogId;<br>}<br>else<br>{<br>for&nbsp;(i=stuTcpChainInfo.ProtocolChain.ChainLen;i&gt;0;i--)<br>{<br>stuTcpChainInfo.ProtocolChain.ChainEntries[i+1]&nbsp;=&nbsp;stuTcpChainInfo.ProtocolChain.ChainEntries[i];<br>}<br>}<br>stuTcpChainInfo.ProtocolChain.ChainLen++;<br>stuTcpChainInfo.ProtocolChain.ChainEntries[0]&nbsp;=&nbsp;dwLayeredCatalogId;<br><br>//&nbsp;第四步，调用WSCInstallProvider安装这个协议链<br>nRet=WSCInstallProvider(&amp;guidMiniProviderChain,wszDllPath,&amp;stuTcpChainInfo,1,&amp;nErrorCode);<br>if(nRet==SOCKET_ERROR)<br>{<br>printf("WSCInstallProvider&nbsp;for&nbsp;protocol&nbsp;chain&nbsp;failed&nbsp;%d\n",&nbsp;nErrorCode);<br>return;<br>}<br><br>//&nbsp;第五步，安装成功后将所有的服务提供者重新排序。<br>LPDWORD&nbsp;lpCatalogEntries=(LPDWORD)new&nbsp;BYTE[nTotalProtocols*sizeof(DWORD)];<br>ASSERT(lpCatalogEntries!=NULL);<br>memset(lpCatalogEntries,0,nTotalProtocols*sizeof(DWORD));<br>DWORD&nbsp;dwIndex=0;<br>for&nbsp;(i=0;i&lt;nTotalProtocols;i++)<br>{<br>if(memcmp(&amp;lpProtocolInfo[i].ProviderId,&nbsp;&amp;guidMiniProvider,&nbsp;sizeof&nbsp;(GUID))==0&nbsp;||&nbsp;\<br>&nbsp;&nbsp;&nbsp;memcmp&nbsp;(&amp;lpProtocolInfo[i].ProviderId,&nbsp;&amp;guidMiniProviderChain,&nbsp;sizeof&nbsp;(GUID))==0)<br>lpCatalogEntries[dwIndex++]&nbsp;=&nbsp;lpProtocolInfo[i].dwCatalogEntryId;<br>}<br>for&nbsp;(i=0;i&lt;nTotalProtocols;i++)<br>{<br>if(memcmp&nbsp;(&amp;lpProtocolInfo[i].ProviderId,&nbsp;&amp;guidMiniProvider,&nbsp;sizeof&nbsp;(GUID))!=0&nbsp;&amp;&amp;&nbsp;\<br>&nbsp;&nbsp;&nbsp;memcmp&nbsp;(&amp;lpProtocolInfo[i].ProviderId,&nbsp;&amp;guidMiniProviderChain,&nbsp;sizeof&nbsp;(GUID))!=0)<br>lpCatalogEntries[dwIndex++]=lpProtocolInfo[i].dwCatalogEntryId;<br>}<br>nRet=WSCWriteProviderOrder(lpCatalogEntries,nTotalProtocols);<br>delete[]&nbsp;lpCatalogEntries;&nbsp;lpCatalogEntries=NULL;&nbsp;<br>if(nRet!=ERROR_SUCCESS)<br>{<br>printf("WSCWriteProviderOrder&nbsp;failed&nbsp;%d\n",&nbsp;nErrorCode);<br>return;<br>}<br>}<br>__finally<br>{<br>if(lpProtocolInfo)&nbsp;delete[]&nbsp;lpProtocolInfo;&nbsp;lpProtocolInfo=NULL;<br>}<br>}<br><br>好了，到目前，我们安装了基础服务提供者和分层服务提供者，到了该卸载他们的时候了，对于基础服务提供者，简单地恢复原来的目录信息就可以了，对于分层服务提供者，调用WSCDeinstallProvider卸载函数可以简单的卸载，但是对于分层服务提供者，一定要维护好协议链，把所有和你的分层服务提供者的协议链相关的信息都清除掉，恢复安装之前的协议链状态，免得发生协议链断开的现象发生，如果断开了，就会影响网络的功能。Windows&nbsp;SPI在这方面存在缺陷，如果很多人装了很多个分层服务提供者，那怎么办呢？会很麻烦的。<br><br>接下来，我们最后来看看怎么样实现一个服务提供者的最基本的实例，我们可以试图在这里对网络封包进行截取。<br><br>以下是基础服务提供者实例，对于分层服务提供者，麻烦！我懒得写了：<br><br>int&nbsp;WSPAPI&nbsp;WSPStartup(<br>&nbsp;&nbsp;&nbsp;&nbsp;WORD&nbsp;wVersion,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSPDATA&nbsp;lpWSPData,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSAPROTOCOL_INFOW&nbsp;lpProtocolInfo,<br>&nbsp;&nbsp;&nbsp;&nbsp;WSPUPCALLTABLE&nbsp;UpCallTable,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSPPROC_TABLE&nbsp;lpProcTable)<br>{<br><br>int&nbsp;nTotalProtocols=0;<br>&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;nProviderPathLen=MAX_PATH;<br>&nbsp;&nbsp;&nbsp;&nbsp;TCHAR&nbsp;szLibraryPath[MAX_PATH]={'\0'};<br>&nbsp;&nbsp;&nbsp;&nbsp;TCHAR&nbsp;szProviderPath[MAX_PATH]={'\0'};;<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSAPROTOCOL_INFOW&nbsp;lpOrigProtocolInfo=NULL;<br>theApp.m_hLibOrigBase=NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSPSTARTUP&nbsp;WSPStartupFunc&nbsp;=&nbsp;NULL;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;EnterCriticalSection(&amp;theApp.m_CriticalSection);<br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(!theApp.m_nEntityCount)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>CFile&nbsp;f;<br>TCHAR&nbsp;szFileName[MAX_PATH]={'\0'};<br>GetWindowsDirectory(szFileName,MAX_PATH);<br>sprintf(szFileName,"%s\\%s",szFileName,OLD_BSP_CATALOG);<br>if(!f.Open(szFileName,CFile::modeRead))&nbsp;return&nbsp;WSAEPROVIDERFAILEDINIT;<br>DWORD&nbsp;dwLen=f.GetLength();<br>DWORD&nbsp;dwSize=MAX_PATH+sizeof(WSAPROTOCOL_INFOW);<br>nTotalProtocols=dwLen/dwSize;<br>lpOrigProtocolInfo=new&nbsp;WSAPROTOCOL_INFOW[nTotalProtocols];<br>for(int&nbsp;i=0;i&lt;nTotalProtocols;i++)<br>{<br>f.Seek(dwSize*i+MAX_PATH,0);<br>f.Read(&amp;lpOrigProtocolInfo[i],sizeof(WSAPROTOCOL_INFOW));<br>if(lpProtocolInfo-&gt;ProviderId==lpOrigProtocolInfo-&gt;ProviderId)<br>{<br>f.Seek(dwSize*i,0);<br>f.Read(szProviderPath,MAX_PATH);<br>break;<br>}<br>}<br>f.Close();<br>delete[]&nbsp;lpOrigProtocolInfo;&nbsp;lpOrigProtocolInfo=NULL;<br>if(!ExpandEnvironmentStrings(szProviderPath,szLibraryPath,MAX_PATH))&nbsp;return&nbsp;WSAEPROVIDERFAILEDINIT;<br><br>theApp.m_hLibOrigBase=LoadLibrary(szLibraryPath);<br>if(theApp.m_hLibOrigBase==NULL)&nbsp;return&nbsp;WSAEPROVIDERFAILEDINIT;<br><br>WSPStartupFunc=(LPWSPSTARTUP)GetProcAddress(theApp.m_hLibOrigBase,"WSPStartup"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>if(WSPStartupFunc==NULL)&nbsp;return&nbsp;WSAEPROVIDERFAILEDINIT;<br><br>int&nbsp;nRet=(*WSPStartupFunc)(wVersion,lpWSPData,lpProtocolInfo,UpCallTable,lpProcTable);<br>if(nRet!=ERROR_SUCCESS)&nbsp;return&nbsp;nRet;<br><br>memmove(&amp;theApp.m_NextProcTable,&nbsp;lpProcTable,&nbsp;sizeof(WSPPROC_TABLE));<br><br>lpProcTable-&gt;lpWSPAccept&nbsp;=&nbsp;WSPAccept;<br>lpProcTable-&gt;lpWSPAddressToString&nbsp;=&nbsp;WSPAddressToString;<br>lpProcTable-&gt;lpWSPAsyncSelect&nbsp;=&nbsp;WSPAsyncSelect;<br>lpProcTable-&gt;lpWSPBind&nbsp;=&nbsp;WSPBind;<br>lpProcTable-&gt;lpWSPCancelBlockingCall&nbsp;=&nbsp;WSPCancelBlockingCall;<br>lpProcTable-&gt;lpWSPCleanup&nbsp;=&nbsp;WSPCleanup;<br>lpProcTable-&gt;lpWSPCloseSocket&nbsp;=&nbsp;WSPCloseSocket;<br>lpProcTable-&gt;lpWSPConnect&nbsp;=&nbsp;WSPConnect;<br>lpProcTable-&gt;lpWSPDuplicateSocket&nbsp;=&nbsp;WSPDuplicateSocket;<br>lpProcTable-&gt;lpWSPEnumNetworkEvents&nbsp;=&nbsp;WSPEnumNetworkEvents;<br>lpProcTable-&gt;lpWSPEventSelect&nbsp;=&nbsp;WSPEventSelect;<br>lpProcTable-&gt;lpWSPGetOverlappedResult&nbsp;=&nbsp;WSPGetOverlappedResult;<br>lpProcTable-&gt;lpWSPGetPeerName&nbsp;=&nbsp;WSPGetPeerName;<br>lpProcTable-&gt;lpWSPGetSockOpt&nbsp;=&nbsp;WSPGetSockOpt;<br>lpProcTable-&gt;lpWSPGetSockName&nbsp;=&nbsp;WSPGetSockName;<br>lpProcTable-&gt;lpWSPGetQOSByName&nbsp;=&nbsp;WSPGetQOSByName;<br>lpProcTable-&gt;lpWSPIoctl&nbsp;=&nbsp;WSPIoctl;<br>lpProcTable-&gt;lpWSPJoinLeaf&nbsp;=&nbsp;WSPJoinLeaf;<br>lpProcTable-&gt;lpWSPListen&nbsp;=&nbsp;WSPListen;<br>lpProcTable-&gt;lpWSPRecv&nbsp;=&nbsp;WSPRecv;<br>lpProcTable-&gt;lpWSPRecvDisconnect&nbsp;=&nbsp;WSPRecvDisconnect;<br>lpProcTable-&gt;lpWSPRecvFrom&nbsp;=&nbsp;WSPRecvFrom;<br>lpProcTable-&gt;lpWSPSelect&nbsp;=&nbsp;WSPSelect;<br>lpProcTable-&gt;lpWSPSend&nbsp;=&nbsp;WSPSend;<br>lpProcTable-&gt;lpWSPSendDisconnect&nbsp;=&nbsp;WSPSendDisconnect;<br>lpProcTable-&gt;lpWSPSendTo&nbsp;=&nbsp;WSPSendTo;<br>lpProcTable-&gt;lpWSPSetSockOpt&nbsp;=&nbsp;WSPSetSockOpt;<br>lpProcTable-&gt;lpWSPShutdown&nbsp;=&nbsp;WSPShutdown;<br>lpProcTable-&gt;lpWSPSocket&nbsp;=&nbsp;WSPSocket;<br>lpProcTable-&gt;lpWSPStringToAddress&nbsp;=&nbsp;WSPStringToAddress;<br><br>theApp.m_lpWSPData&nbsp;=&nbsp;lpWSPData;<br>theApp.m_lpProcTable&nbsp;=&nbsp;lpProcTable;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>else<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpWSPData&nbsp;=&nbsp;theApp.m_lpWSPData;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpProcTable&nbsp;=&nbsp;theApp.m_lpProcTable;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;theApp.m_nEntityCount++;<br>&nbsp;&nbsp;&nbsp;&nbsp;LeaveCriticalSection(&amp;theApp.m_CriticalSection);<br><br>&nbsp;&nbsp;&nbsp;return&nbsp;0;<br>}<br><br>int&nbsp;WSPAPI&nbsp;WSPCleanup&nbsp;(LPINT lpErrno)<br>{<br>int&nbsp;nRet=theApp.m_NextProcTable.lpWSPCleanup(lpErrno);<br><br>EnterCriticalSection(&amp;theApp.m_CriticalSection);<br>theApp.m_nEntityCount--;<br>&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(theApp.m_nEntityCount&nbsp;==&nbsp;0)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FreeLibrary(theApp.m_hLibOrigBase);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;theApp.m_hLibOrigBase&nbsp;=&nbsp;NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;LeaveCriticalSection(&amp;theApp.m_CriticalSection);<br><br>return&nbsp;nRet;<br>}<br><br>对于不同的I/0操作模式，需要进行不同的处理<br>对于WSARecv,WSARecvFrom,WSASend,WSASendTo的重叠调用，保存重叠信息，重点是完成例程<br>//&nbsp;客户程序如果用完成例程，那么将不能直接监视到收发的数据，为了解决这个问题<br>//&nbsp;我们可以在服务提供者内提供我们自己的完成例程截获数据，在我们截获了数据之后，<br>//&nbsp;再把控制权交还给客户机的完成例程。<br>//<br>//&nbsp;首先，保存重叠操作参数信息的结构<br>typedef&nbsp;struct&nbsp;tagOVERLAPPED_RECORDER<br>{<br>DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dwType;&nbsp;//&nbsp;WSASend,WSASendTo,WSARecv,WSARecvFrom<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;用来标志监视的数据操作方式<br>&nbsp;&nbsp;&nbsp;&nbsp;SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;LPWSABUF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpBuffers,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dwBufferCount,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;LPDWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpNumberOfBytesSent,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dwFlags,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;const&nbsp;SOCKADDR_IN&nbsp;FAR&nbsp;&nbsp;*lpFromOrTo,&nbsp;&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;int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;iFromOrTolen,&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;LPWSAOVERLAPPED&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpOverlapped,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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>LPWSAOVERLAPPED_COMPLETION_ROUTINE&nbsp;&nbsp;&nbsp;lpCompletionRoutine,<br>&nbsp;&nbsp;&nbsp;&nbsp;LPWSATHREADID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpThreadId,&nbsp;&nbsp;&nbsp;&nbsp;&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;LPINT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lpErrno&nbsp;&nbsp;<br>}<br>OVERLAPPED_RECORDER,&nbsp;*POVERLAPPED_RECORDER,&nbsp;*LPOVERLAPPED_RECORDER;<br><br>//&nbsp;其次替换重叠操作的完成例程为自定义的例程<br>//&nbsp;例程完成后，控制权交给客户机的完成例程，我们可以删除上面保存的重叠参数信息结构了<br>//&nbsp;完成例程原型如下:<br>void&nbsp;CALLBACK&nbsp;CompletionRoutine&nbsp;(<br>&nbsp;IN&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dwError,&nbsp;<br>&nbsp;IN&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cbTransferred,&nbsp;<br>&nbsp;IN&nbsp;&nbsp;&nbsp;&nbsp;LPWSAOVERLAPPED&nbsp;&nbsp;&nbsp;lpOverlapped,&nbsp;<br>&nbsp;IN&nbsp;&nbsp;&nbsp;&nbsp;DWORD&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dwFlags&nbsp;<br>);<br><br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30238.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:19 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30238.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Multicasting</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30237.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:18:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30237.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30237.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30237.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30237.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30237.html</trackback:ping><description><![CDATA[Multicasting<br><br>多播通讯让网络客户充分享受了广播的便捷而又不用再担心网络会被风暴阻塞，所有这一切都归功于&nbsp;IGMP&nbsp;协议的实施。<br>多播地址是一个D类的IP地址（224.0.0.0-239.255.255.255),其中有一些是为特殊用途而分配的，详细分配情况请参考&nbsp;RFC1700。<br>这里只讲述IP多播。IP多播是无根多播，下面只罗列简单的程序实现，详细的规范请参考相应的文档。<br><br>#define&nbsp;MCASTADDR "234.5.6.7"<br>#define&nbsp;MCASTPORT 65500<br>#define&nbsp;COMMBUFSIZE 1024<br><br>//&nbsp;使用Winsock2<br>if(WSAStartup(MAKEWORD(2,2),&amp;wsd)!=0)&nbsp;return&nbsp;FALSE;<br>//&nbsp;创建多播套接字<br>SOCKET&nbsp;sock=WSASocket(AF_INET,<br>SOCK_DGRAM,<br>0,<br>NULL,<br>0,<br>WSA_FLAG_MULTIPOINT_C_LEAF|<br>WSA_FLAG_MULTIPOINT_D_LEAF|<br>WSA_FLAG_OVERLAPPED);<br>//&nbsp;绑定套接字<br>SOCKADDR_IN&nbsp;saiLocal;<br>saiLocal.sin_family=AF_INET;<br>saiLocal.sin_port=htons(MCASTPORT);<br>saiLocal.sin_addr.S_un.S_addr=INADDR_ANY;<br>if(bind(sock,(SOCKADDR*)&amp;saiLocal,sizeof(saiLocal))==SOCKET_ERROR)&nbsp;return&nbsp;FALSE;<br>//&nbsp;设置套接字选项<br>unsigned&nbsp;long&nbsp;ulNoneBlockingIO=1;<br>if(SOCKET_ERROR==ioctlsocket(sock, FIONBIO,&amp;ulNoneBlockingIO))&nbsp;return&nbsp;FALSE;<br>//&nbsp;Set&nbsp;TTL&nbsp;to&nbsp;8<br>int&nbsp;nOpt=8;<br>if(setsockopt(sock,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&amp;nOpt,sizeof(nOpt))==SOCKET_ERROR)&nbsp;return&nbsp;FALSE;<br>//&nbsp;Set&nbsp;Loopback<br>if(m_bLoopBack)<br>{<br>BOOL&nbsp;bOpt=0;<br>if(setsockopt(sock,IPPROTO_IP,IP_MULTICAST_LOOP,(char*)&amp;bOpt,sizeof(bOpt))==SOCKET_ERROR)&nbsp;return&nbsp;FALSE;<br>}<br>//&nbsp;加入多播组<br>m_saiRemote.sin_family=AF_INET;<br>m_saiRemote.sin_port=htons(MCASTPORT);<br>m_saiRemote.sin_addr.S_un.S_addr=inet_addr(MCASTADDR);<br>DWORD&nbsp;dwFlags=(m_bSender)?JL_SENDER_ONLY:JL_RECEIVER_ONLY;<br>SOCKET&nbsp;sockM=WSAJoinLeaf(sock,(SOCKADDR*)&amp;m_saiRemote,sizeof(m_saiRemote),NULL,NULL,NULL, NULL,dwFlags);<br>if(sockM==INVALID_SOCKET)<br>{<br>closesocket(sock);<br>WSACleanup();<br>return&nbsp;FALSE;<br>}<br>//&nbsp;发送<br>int&nbsp;nRet=sendto(sock,(char*)pBuf,len,0,(SOCKADDR*)&amp;m_saiRemote,sizeof(m_saiRemote));<br>//&nbsp;接收<br>SOCKADDR_IN&nbsp;sai_From;<br>int&nbsp;len&nbsp;=&nbsp;sizeof(sai_From);<br>BYTE&nbsp;buf[COMMBUFSIZE]={'\0'};<br>int&nbsp;nRetRecv=recvfrom(sock,(char*)buf,COMMBUFSIZE,0,(SOCKADDR*)&amp;sai_From,&amp;len);<br>//&nbsp;结束<br><br>//&nbsp;附上一个配置函数<br>#include&nbsp;"Winsock2.h"<br>#include&nbsp;"Ws2tcpip.h"<br><br>#include&nbsp;"iprtrmib.h"<br>#include&nbsp;"ipexport.h"<br>#include&nbsp;"iptypes.h"<br>#include&nbsp;"iphlpapi.h"<br><br>#pragma&nbsp;comment(lib,"ws2_32.lib"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>#pragma&nbsp;comment(lib,"iphlpapi.lib"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br><br>typedef&nbsp;struct&nbsp;tagNIC<br>{<br>int&nbsp;nNum; //&nbsp;多少块网卡<br>unsigned&nbsp;char&nbsp;szHostName[150]; //&nbsp;主机名<br>unsigned&nbsp;char&nbsp;szAddr[7*10]; //&nbsp;每块网卡地址以&nbsp;0为间隔255.255.255.255.<br>unsigned&nbsp;char&nbsp;szMac[7*10]; //&nbsp;每块网卡MAC地址0为间隔0x000x040x760x3F0x5F0xA9.<br>}<br>NICADDR,*LPNICADDR;<br><br>typedef&nbsp;struct&nbsp;tagConfigParam<br>{<br>BOOL&nbsp;&nbsp;bSender; //&nbsp;[IN]&nbsp;是发送还是接收<br>char*&nbsp;pszMcastAddr; //&nbsp;[IN]&nbsp;多播组地址<br>WORD&nbsp;&nbsp;wMcastPort; //&nbsp;[IN]&nbsp;多播端口<br>NICADDR&nbsp;stuAddr; //&nbsp;[OUT]本地网卡地址信息，函数回填<br>}<br>CONFIGPARAM,*LPCONFIGPARAM;<br><br>void&nbsp;FuncConfig(CONFIGPARAM*&nbsp;pParam)<br>{<br>m_bSender=pParam-&gt;bSender;<br>if(pParam-&gt;pszMcastAddr!=NULL)<br>m_dwMulticastGroup=inet_addr(pParam-&gt;pszMcastAddr);<br>if(pParam-&gt;wMcastPort!=0)<br>m_dwPort=pParam-&gt;wMcastPort;<br>memset(&amp;(pParam-&gt;stuAddr),0,sizeof(NICADDR));<br>char&nbsp;name[MAX_PATH]={'\0'};<br>if(0==gethostname(name,MAX_PATH))<br>{<br>if(strlen(name)&gt;0)&nbsp;strcpy((char*)pParam-&gt;stuAddr.szHostName,name);<br>HOSTENT*&nbsp;HostEnt;<br>HostEnt=gethostbyname(name);<br>int&nbsp;n=0;<br>int&nbsp;m=0;<br>BOOL&nbsp;bNoConnected=TRUE;<br>if(HostEnt!=NULL)<br>{<br>IP_ADAPTER_INFO&nbsp;AdapterInfo[16];<br>PIP_ADAPTER_INFO&nbsp;pAdapterInfo;<br>DWORD&nbsp;dwBufLen=sizeof(AdapterInfo);<br><br>DWORD&nbsp;dwRet=GetAdaptersInfo(<br>AdapterInfo,<br>&amp;dwBufLen);<br>if(dwRet==ERROR_SUCCESS)&nbsp;pAdapterInfo=AdapterInfo;<br><br>for(int&nbsp;i=0;HostEnt-&gt;h_addr_list[i]!=NULL;i++)<br>{<br>in_addr&nbsp;ia;<br>memset(&amp;ia,0,sizeof(ia));<br>ia.S_un.S_addr&nbsp;=&nbsp;*((unsigned&nbsp;long*)HostEnt-&gt;h_addr_list[i]);<br>char*&nbsp;pszIP&nbsp;=&nbsp;inet_ntoa(ia);<br>int&nbsp;nLen=strlen(pszIP);<br>memmove(pParam-&gt;stuAddr.szAddr+n,pszIP,nLen);<br>n+=strlen(pszIP);<br>n++;<br><br>if(dwRet==ERROR_SUCCESS)<br>{<br>if(pAdapterInfo)<br>{<br>memmove(pParam-&gt;stuAddr.szMac+m,pAdapterInfo-&gt;Address,6);<br>pAdapterInfo=pAdapterInfo-&gt;Next;<br>m+=7;<br>}<br>}<br><br>pParam-&gt;stuAddr.nNum++;<br>}<br>}<br>}<br>}<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30237.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:18 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30237.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NAT</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30236.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:15:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30236.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30236.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30236.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30236.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30236.html</trackback:ping><description><![CDATA[今天碰到了NAT的问题，来到了这里。尽管注册过程繁琐，我还是坚持到底了。这个问题解决了，一定写下心得，让需要帮助的人能够受益。<img onclick="window.open('/images/em/2/1.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/em/2/1.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br><br>NAT<br><br>I&nbsp;saw&nbsp;a&nbsp;post&nbsp;on&nbsp;a&nbsp;Delphi&nbsp;BBS,&nbsp;one&nbsp;is&nbsp;asking:"&nbsp;How&nbsp;do&nbsp;I&nbsp;develop&nbsp;a&nbsp;software&nbsp;acting&nbsp;like&nbsp;QQ,&nbsp;It&nbsp;can&nbsp;punching&nbsp;through&nbsp;the&nbsp;Gateway&nbsp;so&nbsp;that&nbsp;two&nbsp;different&nbsp;private&nbsp;net's&nbsp;users&nbsp;can&nbsp;exchange&nbsp;data&nbsp;through&nbsp;UDP&nbsp;trans?&nbsp;"<br><br>I&nbsp;was&nbsp;confused&nbsp;by&nbsp;the&nbsp;same&nbsp;problem&nbsp;at&nbsp;that&nbsp;time.&nbsp;I&nbsp;was&nbsp;developing&nbsp;a&nbsp;C/S&nbsp;software,&nbsp;and&nbsp;the&nbsp;Client&nbsp;will&nbsp;be&nbsp;behind&nbsp;the&nbsp;Gateway.&nbsp;How&nbsp;do&nbsp;the&nbsp;Server&nbsp;send&nbsp;data&nbsp;to&nbsp;the&nbsp;client&nbsp;behiding&nbsp;the&nbsp;Gateway?<br><br>I&nbsp;knew&nbsp;NAT&nbsp;then.&nbsp;Cone&nbsp;NAT&nbsp;and&nbsp;Symentric&nbsp;NAT.<br><br>In&nbsp;a&nbsp;few&nbsp;hours,&nbsp;I&nbsp;knew&nbsp;that&nbsp;It's&nbsp;easy&nbsp;to&nbsp;resolve&nbsp;the&nbsp;problem.&nbsp;<br><br>According&nbsp;to&nbsp;my&nbsp;comprehension.&nbsp;NAT&nbsp;does&nbsp;so:<br><br>NAT&nbsp;will&nbsp;use&nbsp;the&nbsp;same&nbsp;public&nbsp;IP&nbsp;for&nbsp;all&nbsp;private&nbsp;net&nbsp;users&nbsp;and&nbsp;will&nbsp;assign(binding&nbsp;or&nbsp;temporary.Here&nbsp;done&nbsp;in&nbsp;Cone&nbsp;NAT)&nbsp;different&nbsp;Ports&nbsp;for&nbsp;different&nbsp;sessions.<br>All&nbsp;the&nbsp;applications&nbsp;outside&nbsp;the&nbsp;private&nbsp;net&nbsp;dont&nbsp;know&nbsp;the&nbsp;private&nbsp;net&nbsp;users&nbsp;but&nbsp;they&nbsp;know&nbsp;this&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;theses&nbsp;different&nbsp;Ports.<br>Meanwhile&nbsp;the&nbsp;NAT&nbsp;knows&nbsp;whom&nbsp;each&nbsp;port&nbsp;identified.&nbsp;<br><br>That's&nbsp;all.<br><br>A&nbsp;little&nbsp;complex&nbsp;for&nbsp;UDP.&nbsp;it&nbsp;has&nbsp;no&nbsp;connection.&nbsp;How&nbsp;the&nbsp;public&nbsp;IPs&nbsp;send&nbsp;UDP&nbsp;to&nbsp;the&nbsp;private&nbsp;net&nbsp;users?&nbsp;they&nbsp;dont&nbsp;konw&nbsp;them&nbsp;at&nbsp;all,&nbsp;they&nbsp;can&nbsp;only&nbsp;konw&nbsp;the&nbsp;NAT's&nbsp;public&nbsp;IP.&nbsp;It&nbsp;seems&nbsp;this&nbsp;is&nbsp;the&nbsp;focus&nbsp;point.<br>Chaos?&nbsp;But&nbsp;it's&nbsp;really&nbsp;easy&nbsp;if&nbsp;you&nbsp;know&nbsp;this&nbsp;truth:&nbsp;Only&nbsp;if&nbsp;a&nbsp;private&nbsp;client&nbsp;send&nbsp;UDP&nbsp;data&nbsp;to&nbsp;a&nbsp;public&nbsp;IP&nbsp;through&nbsp;the&nbsp;NAT,&nbsp;the&nbsp;NAT&nbsp;will&nbsp;identify&nbsp;the&nbsp;private&nbsp;client&nbsp;through&nbsp;assigning&nbsp;a&nbsp;random&nbsp;UDP&nbsp;port&nbsp;for&nbsp;this&nbsp;client&nbsp;in&nbsp;a&nbsp;certain&nbsp;period.&nbsp;Then&nbsp;in&nbsp;this&nbsp;period,&nbsp;if&nbsp;somebody&nbsp;outside&nbsp;the&nbsp;private&nbsp;net&nbsp;send&nbsp;data&nbsp;to&nbsp;this&nbsp;UDP&nbsp;port&nbsp;of&nbsp;the&nbsp;NAT's&nbsp;public&nbsp;IP,&nbsp;NAT&nbsp;will&nbsp;retransfer&nbsp;all&nbsp;the&nbsp;incoming&nbsp;data&nbsp;to&nbsp;the&nbsp;JUST&nbsp;user&nbsp;it&nbsp;identified&nbsp;according&nbsp;to&nbsp;this&nbsp;UDP&nbsp;port.<br><br>Done!<br><br>It's&nbsp;all&nbsp;for&nbsp;the&nbsp;so&nbsp;said&nbsp;"UDP&nbsp;punching&nbsp;hole".&nbsp;It's&nbsp;so&nbsp;easy,&nbsp;isn't&nbsp;it?<br><br>Of&nbsp;course,&nbsp;till&nbsp;now,&nbsp;we&nbsp;just&nbsp;resolve&nbsp;half&nbsp;problem&nbsp;of&nbsp;the&nbsp;question&nbsp;we&nbsp;mentioned&nbsp;most&nbsp;above.&nbsp;Now&nbsp;we&nbsp;know&nbsp;how&nbsp;the&nbsp;users&nbsp;ouside&nbsp;the&nbsp;private&nbsp;net&nbsp;send&nbsp;UDP&nbsp;data&nbsp;to&nbsp;the&nbsp;private&nbsp;net&nbsp;user.&nbsp;It&nbsp;all&nbsp;depends&nbsp;on&nbsp;such&nbsp;a&nbsp;resume:&nbsp;The&nbsp;outside&nbsp;user&nbsp;has&nbsp;konwn&nbsp;the&nbsp;NAT&nbsp;IP&nbsp;and&nbsp;the&nbsp;UDP&nbsp;port&nbsp;that&nbsp;NAT&nbsp;uses&nbsp;to&nbsp;identify&nbsp;the&nbsp;private&nbsp;net's&nbsp;client.&nbsp;How&nbsp;did&nbsp;he&nbsp;know&nbsp;the&nbsp;port?&nbsp;Of&nbsp;course&nbsp;the&nbsp;private&nbsp;net's&nbsp;user&nbsp;send&nbsp;UDP&nbsp;data&nbsp;first&nbsp;to&nbsp;tell&nbsp;him.&nbsp;WOWOWO&nbsp;wait!&nbsp;Here&nbsp;comes&nbsp;another&nbsp;problem?&nbsp;<br>No&nbsp;doult,&nbsp;If&nbsp;the&nbsp;outside&nbsp;user&nbsp;has&nbsp;a&nbsp;public&nbsp;IP,&nbsp;so&nbsp;the&nbsp;private&nbsp;net's&nbsp;client&nbsp;can&nbsp;easily&nbsp;send&nbsp;UDP&nbsp;data&nbsp;to&nbsp;the&nbsp;outside&nbsp;user,&nbsp;when&nbsp;the&nbsp;the&nbsp;user&nbsp;received&nbsp;the&nbsp;UDP&nbsp;data,&nbsp;surely&nbsp;it&nbsp;will&nbsp;know&nbsp;the&nbsp;UDP&nbsp;port&nbsp;the&nbsp;NAT&nbsp;assigned&nbsp;for&nbsp;the&nbsp;private&nbsp;net's&nbsp;client.<br>BUT&nbsp;WHAT&nbsp;if&nbsp;the&nbsp;outside&nbsp;user&nbsp;is&nbsp;also&nbsp;a&nbsp;private&nbsp;net's&nbsp;user?&nbsp;How&nbsp;did&nbsp;this&nbsp;private&nbsp;net's&nbsp;user&nbsp;send&nbsp;the&nbsp;first&nbsp;identity-comfirming&nbsp;UDP&nbsp;packet&nbsp;to&nbsp;another&nbsp;private&nbsp;net's&nbsp;user?&nbsp;It&nbsp;dont&nbsp;know&nbsp;THAT&nbsp;private&nbsp;net's&nbsp;NAT's&nbsp;IP&nbsp;and&nbsp;the&nbsp;UDP&nbsp;Port&nbsp;that&nbsp;NAT&nbsp;assigned&nbsp;to&nbsp;that&nbsp;private&nbsp;user.&nbsp;It&nbsp;seems&nbsp;like&nbsp;a&nbsp;circle,&nbsp;right?&nbsp;<br><br>I&nbsp;think&nbsp;you&nbsp;have&nbsp;dropped&nbsp;into&nbsp;a&nbsp;never&nbsp;ending&nbsp;circle:&nbsp;i&nbsp;want&nbsp;to&nbsp;touch&nbsp;you&nbsp;but&nbsp;you&nbsp;dont&nbsp;know&nbsp;me,&nbsp;and&nbsp;you&nbsp;want&nbsp;to&nbsp;touch&nbsp;me&nbsp;but&nbsp;i&nbsp;also&nbsp;dont&nbsp;know&nbsp;you.&nbsp;HOW&nbsp;SHOULD&nbsp;YOU&nbsp;DO&nbsp;NOW?<br><br>Always&nbsp;when&nbsp;people&nbsp;are&nbsp;in&nbsp;trouble,&nbsp;they&nbsp;prayed&nbsp;:&nbsp;"Oh&nbsp;God&nbsp;Bless."&nbsp;YUP,&nbsp;WHY&nbsp;NOT&nbsp;LET&nbsp;GOD&nbsp;HELP&nbsp;YOU?<br><br>If&nbsp;God&nbsp;can&nbsp;tell&nbsp;me&nbsp;your&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;your&nbsp;NAT's&nbsp;port&nbsp;that&nbsp;your&nbsp;NAT&nbsp;used&nbsp;to&nbsp;identify&nbsp;you,&nbsp;Any&nbsp;thing&nbsp;is&nbsp;resolved&nbsp;right?&nbsp;And&nbsp;the&nbsp;same,&nbsp;If&nbsp;God&nbsp;can&nbsp;tell&nbsp;you&nbsp;my&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;the&nbsp;UDP&nbsp;Port&nbsp;my&nbsp;NAT&nbsp;used&nbsp;to&nbsp;identify&nbsp;me,&nbsp;you&nbsp;can&nbsp;also&nbsp;touch&nbsp;me&nbsp;right?<br><br>But&nbsp;where&nbsp;is&nbsp;the&nbsp;God.&nbsp;My&nbsp;teach&nbsp;ever&nbsp;told&nbsp;me&nbsp;that&nbsp;God&nbsp;is&nbsp;created&nbsp;by&nbsp;people.&nbsp;I&nbsp;think&nbsp;maybe&nbsp;it's&nbsp;right&nbsp;now.<br><br>Let's&nbsp;Suppose,&nbsp;if&nbsp;A&nbsp;Server&nbsp;konws&nbsp;your&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;the&nbsp;UDP&nbsp;Port&nbsp;your&nbsp;NAT&nbsp;used&nbsp;to&nbsp;identify&nbsp;you.&nbsp;And&nbsp;IT&nbsp;also&nbsp;knows&nbsp;my&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;the&nbsp;UDP&nbsp;port&nbsp;that&nbsp;my&nbsp;NAT&nbsp;assigned&nbsp;for&nbsp;me.&nbsp;If&nbsp;it&nbsp;will&nbsp;tell&nbsp;you&nbsp;my&nbsp;NAT's&nbsp;infomation&nbsp;and&nbsp;also&nbsp;tell&nbsp;me&nbsp;your&nbsp;NAT's&nbsp;information.&nbsp;It&nbsp;will&nbsp;be&nbsp;the&nbsp;JUST&nbsp;GOD.&nbsp;right?<br><br>You&nbsp;cant&nbsp;touch&nbsp;me&nbsp;directly,&nbsp;but&nbsp;you&nbsp;can&nbsp;touch&nbsp;the&nbsp;GOD.&nbsp;How&nbsp;to&nbsp;do&nbsp;this,&nbsp;I&nbsp;has&nbsp;described&nbsp;at&nbsp;the&nbsp;beginning&nbsp;of&nbsp;the&nbsp;article.&nbsp;The&nbsp;only&nbsp;extra&nbsp;thing&nbsp;that&nbsp;the&nbsp;God&nbsp;will&nbsp;do&nbsp;is&nbsp;to&nbsp;tell&nbsp;you&nbsp;my&nbsp;infomation&nbsp;and&nbsp;tell&nbsp;me&nbsp;yours,&nbsp;then&nbsp;the&nbsp;Left&nbsp;things&nbsp;are&nbsp;ours,&nbsp;i&nbsp;know&nbsp;you&nbsp;now&nbsp;and&nbsp;you&nbsp;know&nbsp;me,&nbsp;too.&nbsp;i&nbsp;can&nbsp;send&nbsp;UDP&nbsp;data&nbsp;to&nbsp;your&nbsp;NAT's&nbsp;public&nbsp;IP&nbsp;and&nbsp;your&nbsp;NAT's&nbsp;UDP&nbsp;port&nbsp;it&nbsp;assigned&nbsp;for&nbsp;you,&nbsp;then&nbsp;you&nbsp;can&nbsp;received&nbsp;my&nbsp;data.&nbsp;So&nbsp;does&nbsp;you.&nbsp;God&nbsp;bless,&nbsp;the&nbsp;problem&nbsp;resolved.<br><br>Got&nbsp;it?<br><br>Demonstrate&nbsp;in&nbsp;Figures&nbsp;Below:<br><br>C/S&nbsp;Model:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Server(S)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:211.111.111.111<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:54326<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;|<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;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NAT<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:61.167.12.199<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1818&nbsp;for&nbsp;Private&nbsp;User1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1822&nbsp;for&nbsp;Private&nbsp;User(n)<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;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br>Private&nbsp;User1&nbsp;&nbsp;...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Private&nbsp;User(n)<br>IP:192.168.0.192&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:192.168.0.198<br>Port:&nbsp;2011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1037<br><br><br><br>Private&nbsp;Peer&nbsp;to&nbsp;Private&nbsp;Peer&nbsp;Model:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Server(S)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:211.111.111.111<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:54326<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;NAT1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;......&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NAT(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:61.167.12.199&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IP:202.192.112.122<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1818&nbsp;for&nbsp;Private&nbsp;User1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:60031&nbsp;for&nbsp;Private&nbsp;Peer1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1822&nbsp;for&nbsp;Private&nbsp;User(n)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:60033&nbsp;for&nbsp;Private&nbsp;Peer(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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;&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&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>Private&nbsp;User1&nbsp;&nbsp;...&nbsp;&nbsp;Private&nbsp;User(n)&nbsp;&nbsp;&nbsp;&nbsp;Private&nbsp;Peer1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Private&nbsp;Peer(n)<br>IP:192.168.0.192&nbsp;IP:192.168.0.198&nbsp;IP:192.198.198.22&nbsp;&nbsp;&nbsp;IP:192.198.198.60<br>Port:&nbsp;2011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1037&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1047&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Port:1077<br><br>(Surely&nbsp;it&nbsp;does&nbsp;work&nbsp;for&nbsp;multi-layer&nbsp;NATs.)<br><br>Here&nbsp;is&nbsp;a&nbsp;Code&nbsp;snippet:<br><br>#define&nbsp;NC_UDP_PORT&nbsp;54326<br><br>//&nbsp;Server:<br><br>SOCKET&nbsp; m_socUDP&nbsp;=&nbsp;WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);<br><br>SOCKADDR_IN&nbsp;saiLocal;<br>saiLocal.sin_family&nbsp;=&nbsp;AF_INET;<br>saiLocal.sin_port&nbsp;=&nbsp;htons(NC_UDP_PORT);<br>saiLocal.sin_addr.S_un.S_addr&nbsp;=&nbsp;inet_addr(pszLocalAddr);<br>if&nbsp;(bind(m_socUDP,(SOCKADDR&nbsp;*)&amp;saiLocal,sizeof(saiLocal))==SOCKET_ERROR)&nbsp;return&nbsp;NC_E_UDP_BIND|WSAGetLastError();<br><br>SOCKADDR_IN&nbsp;saiFrom;<br>int&nbsp;fromlen=sizeof(SOCKADDR_IN);<br>BYTE&nbsp;*buf=(BYTE&nbsp;*)malloc(len);<br>if(buf==NULL)&nbsp;return&nbsp;NULL;<br><br>len=recvfrom(m_socUDP,(char&nbsp;*)buf,len,MSG_PEEK,(SOCKADDR&nbsp;*)&amp;saiFrom,&amp;fromlen);<br>if(len&lt;0)&nbsp;{&nbsp;free(buf);&nbsp;return&nbsp;NULL;&nbsp;}<br><br>len=sendto(m_socUDP,(char&nbsp;*)buf,len,0,(SOCKADDR*)&amp;saiFrom,&amp;fromlen);<br><br>//&nbsp;Client:<br><br>SOCKADDR_IN&nbsp;saiLocal;<br>saiLocal.sin_port&nbsp;=&nbsp;htons(0);<br>saiLocal.sin_addr.S_un.S_addr&nbsp;=&nbsp;inet_addr(pszLocalAddr);<br>int&nbsp;len=sizeof(saiLocal);<br>if&nbsp;(bind(m_socUDP,&nbsp;(SOCKADDR&nbsp;*)&amp;saiLocal,&nbsp;len&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;==&nbsp;SOCKET_ERROR)&nbsp;return&nbsp;-1;<br><br>SOCKADDR_IN&nbsp;saiServer;<br>memset(&amp;saiServer,0,sizeof(saiServer));<br>saiServer.sin_family&nbsp;=&nbsp;AF_INET;<br>saiServer.sin_addr.S_un.S_addr&nbsp;=&nbsp;inet_addr(pszServerAddr);<br>saiServer.sin_port&nbsp;=&nbsp;htons(NC_UDP_PORT);<br>sendto(pThis-&gt;m_socUDP,(const&nbsp;char*)buf,len,0,(SOCKADDR*)&amp;saiServer,len);<br><br>SOCKADDR_IN&nbsp;saiFrom;<br>memset(&amp;saiFrom,0,len);<br>saiFrom.sin_family&nbsp;=&nbsp;AF_INET;<br>nRet&nbsp;=&nbsp;recvfrom(pThis-&gt;m_socUDP,buf,len,0,(SOCKADDR*)&amp;sai_From,&amp;len);<br><br>以上是&nbsp;UDP&nbsp;穿透网关的说明，至于TCP如何穿透网关，如下所示。<br><br>TCP在两方都在NAT之后的客户端很难建立连接，至少我不知道怎么连，可以用UDP代替，但是如果只有一方在NAT之后，可以尝试逆向连接，让服务器通知内网的用户去连另一个公网的客户，但是公网的客户却无法连接到内网的客户，这也只对一方有意义。<br><br>上文对于单向的NAT应该不成问题，但是对于对称的NAT，由于我们考虑了一个生存周期的问题，虽然对称的NAT会反复变换端口，但是在生存周期内应该不会变的，我这么认为，没有试过。<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30236.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:15 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30236.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOCKS5</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30235.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:14:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30235.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30235.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30235.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30235.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30235.html</trackback:ping><description><![CDATA[SOCKS5<br><br>Socks5版本的协议说明参考&nbsp;RFC1928,RFC1929<br>下面简单地罗列程序实现，不涉及详细的协议规范。首先对于TCP连接，然后再讨论UDP传输。至于通过socks5的多播通讯，有兴趣可以参考D.&nbsp;Chouinard的文章。<br><br>1、TCP:<br><br>//&nbsp;建立流套接字&nbsp;<br>SOCKET&nbsp;m_socTCP=socket(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP);<br><br>//&nbsp;连接到代理服务器<br>int&nbsp;nRet&nbsp;=&nbsp;connect(m_socTCP,(SOCKADDR*)&amp;m_saiProxy,sizeof(m_saiProxy));<br><br>//&nbsp;Step&nbsp;1:&nbsp;连接代理服务器成功后，马上开始和代理协商，协商报文如下,询问服务器，版本5，是需要验证(0x02)还是不需要验证(0x00)<br>　　+------+-------------------+------------+<br>　　|VER&nbsp;&nbsp;&nbsp;|&nbsp;Number&nbsp;of&nbsp;METHODS&nbsp;|&nbsp;METHODS&nbsp;&nbsp;&nbsp;&nbsp;|<br>　　+------+-------------------+------------+<br>　　|&nbsp;0x05&nbsp;|&nbsp;0x02&nbsp;(有两个方法)&nbsp;&nbsp;|&nbsp;0x00&nbsp;|&nbsp;0x02|<br>　　+------+-------------------+------------+<br>const&nbsp;char&nbsp;reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};<br>nRet&nbsp;=&nbsp;send(m_socTCP,reqNego,4,0);<br><br>//&nbsp;Setp&nbsp;2:&nbsp;代理服务器将返回两个字节的协商结果，接收协商结果<br>fd_set&nbsp;fdread;FD_ZERO(&amp;fdread);<br>FD_SET(m_socTCP,&amp;fdread);<br>//&nbsp;Last&nbsp;param&nbsp;set&nbsp;to&nbsp;NULL&nbsp;for&nbsp;blocking&nbsp;operation.&nbsp;(struct&nbsp;timeval*)<br>if((nRet=select(0,&amp;fdread,NULL,NULL,NULL))==SOCKET_ERROR){return&nbsp;NC_E_PROXY_SELECT_READ|WSAGetLastError();}<br>char&nbsp;resNego[2]={'\0'};<br>int&nbsp;nRcvd=0,nCount=0;<br>while(1)<br>{<br>if(FD_ISSET(m_socTCP,&amp;fdread))<br>{<br>//接收sock[0]发送来的数据<br>do{<br>nRet&nbsp;=&nbsp;recv(m_socTCP,&nbsp;(char*)resNego+nRcvd,&nbsp;2-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=2)&amp;&amp;(++nCount&lt;1000));<br>if(nRcvd==2)&nbsp;break;<br>}<br>if(nCount++&gt;=2000){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>}<br>if(resNego[0]!=0x05&nbsp;||&nbsp;(resNego[1]!=0x00&nbsp;&amp;&amp;&nbsp;resNego[1]!=0x02)){return&nbsp;NC_E_PROXY_PROTOCOL_VERSION|WSAGetLastError();};<br><br>//&nbsp;Step&nbsp;3:&nbsp;根据协商结果判断是否需要验证用户，如果是0x02，则需要提供验证，验证部分参考RFC1929<br>if(resNego[1]==0x02)<br>{<br>//&nbsp;需要密码验证<br>char&nbsp;reqAuth[513]={'\0'};<br>BYTE&nbsp;byLenUser&nbsp;=&nbsp;(BYTE)strlen(m_szProxyUserName);<br>BYTE&nbsp;byLenPswd&nbsp;=&nbsp;(BYTE)strlen(m_szProxyPassword);<br>reqAuth[0]=0x01;<br>reqAuth[1]=byLenUser;<br>sprintf(&amp;reqAuth[2],"%s",m_szProxyUserName);<br>reqAuth[2+byLenUser]=byLenPswd;<br>sprintf(&amp;reqAuth[3+byLenUser],"%s",m_szProxyPassword);<br>//Send&nbsp;authentication&nbsp;info<br>int&nbsp;len&nbsp;=&nbsp;(int)byLenUser&nbsp;+&nbsp;(int)byLenPswd&nbsp;+&nbsp;3;<br>nRet=send(m_socTCP,(const&nbsp;char*)reqAuth,len,0);<br>if&nbsp;(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_SEND|WSAGetLastError();}<br>//Now&nbsp;:&nbsp;Response&nbsp;to&nbsp;the&nbsp;auth&nbsp;request<br>char&nbsp;resAuth[2]={'\0'};<br>int&nbsp;nRcvd=0,nCount=0;<br>do{<br>nRet&nbsp;=&nbsp;recv(m_socTCP,resAuth+nRcvd,2-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=2)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>if&nbsp;(resAuth[1]!=0)&nbsp;return&nbsp;NC_E_PROXY_AUTHORIZE;<br>//&nbsp;密码验证通过了<br>}<br><br>//&nbsp;Step&nbsp;4:&nbsp;协商完成，开始发送连接远程服务器请求,请求报文格式如下：<br>　　+----+-----+-------+------+----------+----------+<br>　　|VER&nbsp;|&nbsp;CMD&nbsp;|　RSV　|&nbsp;ATYP&nbsp;|&nbsp;DST.ADDR&nbsp;|&nbsp;DST.PORT&nbsp;|<br>　　+----+-----+-------+------+----------+----------+<br>　　|&nbsp;1　|&nbsp;　1&nbsp;|&nbsp;0x00&nbsp;&nbsp;|&nbsp;　1　|&nbsp;Variable&nbsp;|　　&nbsp;2　　&nbsp;|<br>　　+----+-----+-------+------+----------+----------+<br>//&nbsp;CMD==0x01&nbsp;表示连接,&nbsp;ATYP==0x01表示采用IPV4格式地址，DST.ADDR是远程服务器地址，DST.PORT是远程服务器端口<br>//&nbsp;如果需要接受外来连接，则需要在连接完成之后，发送CMD==0x02绑定请求，代理将为此请求绑定一个套接字接受外部连接<br>char&nbsp;reqSubNego[10]={(char)0x05,(char)0x01,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};<br>*(unsigned&nbsp;long*)&amp;reqSubNego[4]&nbsp;=m_saiServerTCP.sin_addr.S_un.S_addr;<br>*(unsigned&nbsp;short*)&amp;reqSubNego[8]=m_saiServerTCP.sin_port;<br>nRet=send(m_socTCP,(const&nbsp;char*)reqSubNego,10,0);<br>if&nbsp;(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_SEND|WSAGetLastError();}<br><br>//&nbsp;Step&nbsp;5:&nbsp;接收对请求的响应，响应包格式如下<br>　　+----+-----+-------+------+----------+----------+<br>　　|VER&nbsp;|&nbsp;REP&nbsp;|　RSV　|&nbsp;ATYP&nbsp;|&nbsp;BND.ADDR&nbsp;|&nbsp;BND.PORT&nbsp;|<br>　　+----+-----+-------+------+----------+----------+<br>　　|&nbsp;1　|　1　|&nbsp;0x00&nbsp;&nbsp;|　1&nbsp;　&nbsp;|&nbsp;Variable&nbsp;|&nbsp;　　2&nbsp;　&nbsp;|<br>　　+----+-----+-------+------+----------+----------+<br>//&nbsp;VER&nbsp;必须是0x05,&nbsp;REP==0x00表示成功，ATYP==0x01表示地址是IPV4地址，BND.ADDR&nbsp;是代理为连接远程服务器绑定的地址，BND.PORT是这个套接字的端口<br>char&nbsp;resSubNego1[5]={'\0'};<br>if(FD_ISSET(m_socTCP,&amp;fdread))<br>{<br>int&nbsp;nRcvd=0,nCount=0;<br>do{<br>nRet&nbsp;=&nbsp;recv(m_socTCP,resSubNego1+nRcvd,5-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=5)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>if(resSubNego1[0]!=0x05||resSubNego1[1]!=0x00){return&nbsp;NC_E_PROXY_PROTOCOL_VERSION_SUB|WSAGetLastError();};<br><br>switch(resSubNego1[3])<br>{<br>case&nbsp;0x01:<br>{<br>//&nbsp;IP&nbsp;V4<br>char&nbsp;resSubNego2[6]={resSubNego1[4],0};<br>int&nbsp;nRet=-1;<br>if(FD_ISSET(m_socTCP,&amp;fdread))<br>{<br>int&nbsp;nRcvd=0,nCount=0;<br>do{<br>int&nbsp;nRet&nbsp;=&nbsp;recv(m_socTCP,&amp;resSubNego2[1]+nRcvd,5-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=5)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>}<br>//&nbsp;得到代理绑定地址<br>unsigned&nbsp;long&nbsp;&nbsp;ulBINDAddr&nbsp;=&nbsp;*(unsigned&nbsp;long*)&amp;resSubNego2; //&nbsp;SOCKS&nbsp;BIND&nbsp;ADDR<br>unsigned&nbsp;short&nbsp;usBINDPort&nbsp;=&nbsp;*(unsigned&nbsp;short*)&amp;resSubNego2[4]; //&nbsp;SOCKS&nbsp;BIND&nbsp;PORT<br>m_saiProxyBindTCP.sin_addr.S_un.S_addr=ulBINDAddr;<br>m_saiProxyBindTCP.sin_port=usBINDPort;<br>//&nbsp;得到本机绑定地址<br>int&nbsp;len&nbsp;=&nbsp;sizeof(m_saiHostTCP);<br>getsockname(m_socTCP,(SOCKADDR*)&amp;m_saiHostTCP,&amp;len);<br>}<br>break;<br>case&nbsp;0x03:<br>{<br>//&nbsp;Domain&nbsp;name<br>int&nbsp;nLen&nbsp;=&nbsp;resSubNego1[4]+2;<br>char*&nbsp;presSubNego2&nbsp;=&nbsp;new&nbsp;char[nLen];<br>if(FD_ISSET(m_socTCP,&amp;fdread))<br>{<br>int&nbsp;nRet=0,nRcvd=0,nCount=0;<br>do{<br>nRet&nbsp;=&nbsp;recv(m_socTCP,presSubNego2+nRcvd,nLen-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=nLen)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>}<br>unsigned&nbsp;short&nbsp;usBINDPort&nbsp;=&nbsp;*(unsigned&nbsp;short*)(presSubNego2+nLen-2); //&nbsp;BIND&nbsp;PORT;<br>//&nbsp;此时得到的是远程主机的Domain&nbsp;Name<br>delete[]&nbsp;presSubNego2;&nbsp;presSubNego2=NULL;<br>}<br>break;<br>case&nbsp;0x04:<br>{<br>//&nbsp;IPV6<br>AfxMessageBox("该版本不支持IPV6"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>}<br>break;<br>default:<br>break;<br>}<br><br>//&nbsp;至此，连接已经建立。在此套接字上可进行数据的收发。<br>}<br><br>2、UDP<br><br>SOCKS&nbsp;V5提供了对UDP的支持，它通过在代理服务器和内网主机之间建立一个UDP中继的TCP连接，来辅助进行UDP数据包的收发。此连接有一个有效期，在此有效生命周期内，必须往代理发送UDP数据报确认连接有效，来维持此连接的有效性，否则，代理服务器超时，将会自动释放此连接的资源。<br>下面简单地罗列程序实现，不涉及详细的协议规范。<br><br>//&nbsp;客户端为UDP中继建立一个相关的流套接字<br>SOCKET&nbsp;m_socClientTCP_UdpAssociate&nbsp;=&nbsp;socket(AF_INET,&nbsp;SOCK_STREAM,&nbsp;IPPROTO_TCP);<br><br>//&nbsp;连接代理服务器<br>int&nbsp;nRet=connect(m_socClientTCP_UdpAssociate,(SOCKADDR*)&amp;saiProxy,sizeof(saiProxy));<br><br>//&nbsp;连接成功，开始和代理服务器协商，首先发送版本标志，方法选择报文<br>const&nbsp;char&nbsp;reqNego[4]={(char)0x05,(char)0x02,(char)0x00,(char)0x02};<br>nRet&nbsp;=&nbsp;send(m_socClientTCP_UdpAssociate,reqNego,4,0);<br>if(&nbsp;nRet==SOCKET_ERROR&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>DWORD&nbsp;dwError&nbsp;=&nbsp;WSAGetLastError();<br>if&nbsp;(dwError!=WSAEWOULDBLOCK)&nbsp;return&nbsp;NCM_E_WM_CREATE_PROXYREQUESTFAILED;<br>}<br><br>//&nbsp;接收协商的响应<br>fd_set&nbsp;fdread;&nbsp;FD_ZERO(&amp;fdread);<br>FD_SET(m_socClientTCP_UdpAssociate,&amp;fdread);<br>if((nRet=select(0,&amp;fdread,NULL,NULL,NULL))==SOCKET_ERROR)&nbsp;return&nbsp;NCM_E_WM_CREATE_PROXYCONNECTFAILED;<br>char&nbsp;resNego[2]={0};<br>int&nbsp;nRcvd=0,nCount=0;<br>if(FD_ISSET(m_socClientTCP_UdpAssociate,&amp;fdread))<br>{<br>nRcvd&nbsp;=&nbsp;recv(m_socClientTCP_UdpAssociate,&nbsp;(char*)resNego+nRcvd,&nbsp;2,0);<br>if(nRcvd==SOCKET_ERROR)&nbsp;return&nbsp;NCM_E_WM_CREATE_PROXYCONNECTFAILED;<br>}<br>if(resNego[0]!=0x05&nbsp;||&nbsp;(resNego[1]!=0x00&nbsp;&amp;&amp;&nbsp;resNego[1]!=0x02))&nbsp;return&nbsp;NCM_E_WM_CREATE_PROXYCONNECTFAILED;<br><br>//&nbsp;看是否需要密码验证<br>if(resNego[1]==0x02)<br>{<br>//&nbsp;需要密码验证<br>char&nbsp;reqAuth[513];&nbsp;memset(reqAuth,0,513);<br>BYTE&nbsp;byLenUser&nbsp;=&nbsp;(BYTE)strlen(m_szProxyUserName);<br>BYTE&nbsp;byLenPswd&nbsp;=&nbsp;(BYTE)strlen(m_szProxyPassword);<br>reqAuth[0]=0x01;<br>reqAuth[1]=byLenUser;<br>sprintf(&amp;reqAuth[2],"%s",m_szProxyUserName);<br>reqAuth[2+byLenUser]=byLenPswd;<br>sprintf(&amp;reqAuth[3+byLenUser],"%s",m_szProxyPassword);<br>//Send&nbsp;authentication&nbsp;info<br>int&nbsp;len&nbsp;=&nbsp;(int)byLenUser&nbsp;+&nbsp;(int)byLenPswd&nbsp;+&nbsp;3;<br>int&nbsp;ret=send(m_socClientTCP_UdpAssociate,(const&nbsp;char*)reqAuth,len,0);<br>if&nbsp;(ret==SOCKET_ERROR)&nbsp;if&nbsp;(GetLastError()!=WSAEWOULDBLOCK)&nbsp;return&nbsp;NCM_E_WM_CREATE_PROXYREQUESTFAILED;<br><br>//Now&nbsp;:&nbsp;Response&nbsp;to&nbsp;the&nbsp;auth&nbsp;request<br>char&nbsp;resAuth[2]={'\0'};<br>int&nbsp;nRcvd=0,nCount=0;<br>do{<br>ret&nbsp;=&nbsp;recv(m_socClientTCP_UdpAssociate,resAuth+nRcvd,2-nRcvd,0);<br>if(ret==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=2)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE}<br>if&nbsp;(resAuth[1]!=0)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;NEM_E_WM_CREATE_PROXYAUTHFAILED;<br>//&nbsp;密码验证通过了<br>}<br><br>//&nbsp;开始发送向目标服务器的连接请求，其中DST.ADDR是目标服务器的地址，DST.PORT是目标服务器的UDP端口<br>char&nbsp;reqSubNego[10]={(char)0x05,(char)0x03,(char)0x00,(char)0x01,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00,(char)0x00};<br>*(unsigned&nbsp;long*)&amp;reqSubNego[4]&nbsp;=saiServerUDP.sin_addr.S_un.S_addr; //&nbsp;cmd:&nbsp;DEST.addr<br>*(unsigned&nbsp;short*)&amp;reqSubNego[8]=saiServerUDP.sin_port; //&nbsp;cmd:&nbsp;DEST.port&nbsp;in&nbsp;network&nbsp;octet&nbsp;order<br>nRet=send(m_socClientTCP_UdpAssociate,(const&nbsp;char*)reqSubNego,10,0);<br>if&nbsp;(nRet==SOCKET_ERROR)&nbsp;return&nbsp;NEM_E_WM_CREATE_PROXYREQFAILED;<br><br>//&nbsp;接收响应信息<br>int&nbsp;nRecvCount&nbsp;=&nbsp;0;<br>int&nbsp;nRecvBufferLen&nbsp;=&nbsp;10;<br>char&nbsp;szRecvBuf[10];<br>nRet&nbsp;=&nbsp;0;<br>if(FD_ISSET(m_socClientTCP_UdpAssociate,&amp;fdread))<br>{<br>int&nbsp;nRcvd=0,nCount=0;<br>do{<br>nRet&nbsp;=&nbsp;recv(m_socClientTCP_UdpAssociate,(char*)szRecvBuf+nRcvd,10-nRcvd,0);<br>if(nRet==SOCKET_ERROR){return&nbsp;NC_E_PROXY_RECEIVE|WSAGetLastError();}<br>nRcvd&nbsp;+=&nbsp;nRet;<br>}<br>while((nRcvd!=10)&amp;&amp;(++nCount&lt;1000));<br>if(nCount&gt;=1000){return&nbsp;NC_E_PROXY_RECEIVE;}<br>if&nbsp;(szRecvBuf[0]!=0x05||szRecvBuf[1]!=0x00){return&nbsp;NC_E_PROXY_PROTOCOL_VERSION_SUB;}<br>}<br>else<br>{<br>return&nbsp;NCM_E_WM_CREATE_PROXYREQUESTFAILED;<br>}<br><br>//&nbsp;代理服务器绑定udp地址BND.ADR，一般代理服务器都是多宿主机器，因此这个绑定地址是局域网地址，代理服务器绑定udp端口BND.PORT<br>//&nbsp;m_ulProxyUDPAddr 代理绑定地址<br>//&nbsp;m_usUDPAssociatePort 代理绑定端口<br>memmove(&amp;m_ulProxyUDPAddr,&amp;szRecvBuf[4],4);<br>memmove(&amp;m_usUDPAssociatePort,&amp;szRecvBuf[8],2);<br><br>m_bUDPAssociated&nbsp;=&nbsp;TRUE;<br>//&nbsp;至此，得到了代理绑定的地址和端口，客户端就可以通过它来收发UDP数据报了<br><br><br>//&nbsp;客户端收发实例<br>//&nbsp;首先创建数据报套接字，绑定套接字，得到本地数据报套接字地址，端口<br>SOCKET&nbsp;m_socClientUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);<br>SOCKADDR_IN&nbsp;saiLocal;<br>memset(&amp;saiLocal,0,sizeof(saiLocal));<br>saiLocal.sin_family&nbsp;=&nbsp;AF_INET;<br>saiLocal.sin_port&nbsp;=&nbsp;0;<br>saiLocal.sin_addr.S_un.S_addr&nbsp;=&nbsp;INADDR_ANY;<br>//&nbsp;绑定本地udp套接字地址，地址+端口由系统决定<br>if&nbsp;(bind(m_socClientUDP,&nbsp;(SOCKADDR&nbsp;*)&amp;saiLocal,&nbsp;sizeof(saiLocal)&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;==&nbsp;SOCKET_ERROR)<br>getsockname(m_socClientUDP,&nbsp;(sockaddr*)&amp;saiLocal,&nbsp;&amp;len);<br>//&nbsp;m_ulHostAddr 本机绑定地址<br>//&nbsp;m_usHostPortUdp 本机绑定端口<br>m_ulHostAddr&nbsp;=&nbsp;saiLocal.sin_addr.S_un.S_addr;<br>m_usHostPortUdp&nbsp;=&nbsp;saiLocal.sin_port;<br><br>//&nbsp;按照格式，发送数据<br>SOCKADDR_IN&nbsp;saiProxy;<br>memset(&amp;saiProxy,0,sizeof(saiProxy));<br>saiProxy.sin_family&nbsp;=&nbsp;AF_INET;<br>saiProxy.sin_addr.S_un.S_addr&nbsp;=&nbsp;m_ulProxyUDPAddr; //&nbsp;代理绑定的udp地址<br>saiProxy.sin_port&nbsp;=&nbsp;m_usUDPAssociatePort; //&nbsp;代理绑定的udp端口<br><br>//&nbsp;每个UDP包必须携带如下所述的头：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+----+------+------+----------+----------+----------+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|RSV&nbsp;|&nbsp;FRAG&nbsp;|&nbsp;ATYP&nbsp;|&nbsp;DST.ADDR&nbsp;|&nbsp;DST.PORT&nbsp;|&nbsp;&nbsp;&nbsp;DATA&nbsp;&nbsp;&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+----+------+------+----------+----------+----------+<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;2&nbsp;&nbsp;|&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;|&nbsp;Variable&nbsp;|&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;Variable&nbsp;|<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+----+------+------+----------+----------+----------+<br>//&nbsp;其中&nbsp;ATYP==0x01&nbsp;表示采用&nbsp;IPV4&nbsp;地址，那么头长度就是10，DST.ADDR并不是服务器地址，而是本机绑定地址，DST.PORT是本机绑定端口<br>char&nbsp;buf[10+2]={'0x00','0x00','0x00','0x01','0xff'};<br>memmove(&amp;buf[4],&amp;m_ulHostAddr,4);<br>*(unsigned&nbsp;short*)&amp;buf[8]=m_usHostPortUDP;<br>int&nbsp;nRet&nbsp;=&nbsp;sendto(m_socClientUDP,buf,12,0,(SOCKADDR*)&amp;saiProxy,sizeof(saiProxy));<br><br>//&nbsp;接收数据<br>int&nbsp;nLen&nbsp;=&nbsp;sizeof(saiProxy);<br>char&nbsp;buf=new&nbsp;char[MAX_UDPLEN];<br>nRet&nbsp;=&nbsp;recvfrom(m_socClientUDP,buf,MAX_UDPLEN,0,(SOCKADDR&nbsp;*)&amp;saiProxy,&amp;nLen);<br>if(nRet==SOCKET_ERROR)&nbsp;return&nbsp;SOCKET_ERROR;<br>BYTE&nbsp;flag=0xff;<br>if(memcmp(&amp;buf[0],&amp;flag,1)==0)&nbsp;return&nbsp;SOCKET_ERROR;<br>BYTE&nbsp;byProxyHead[20];<br>byProxyHead[0]=byProxyHead[1]=byProxyHead[2]=0;<br>byProxyHead[3]=1;<br>*(unsigned&nbsp;long*)&amp;byProxyHead[4]&nbsp;=&nbsp;saiServerUDP.sin_addr.S_un.S_addr;<br>*(unsigned&nbsp;short*)&amp;byProxyHead[8]&nbsp;=&nbsp;saiServerUDP.sin_port;<br>byProxyHead[10]=byProxyHead[11]=byProxyHead[12]=0;<br>byProxyHead[13]=1;<br>*(unsigned&nbsp;long*)&amp;byProxyHead[14]&nbsp;=&nbsp;m_ulHostAddr;<br>*(unsigned&nbsp;short*)&amp;byProxyHead[18]&nbsp;=&nbsp;m_usHostPortUdp;<br><br>int&nbsp;i=0;<br>BOOL&nbsp;bIsForMe=FALSE;<br>if(memcmp(&amp;byProxyHead[0],&amp;buf[i],4)==0)<br>{<br>unsigned&nbsp;long&nbsp;ulRetAddr&nbsp;=&nbsp;*(unsigned&nbsp;long*)&amp;buf[i+4];<br>if(ulRetAddr==m_ulHostAddr)<br>{<br>unsigned&nbsp;short&nbsp;usRetPort&nbsp;=&nbsp;ntohs((unsigned&nbsp;short)(*(unsigned&nbsp;short*)&amp;buf[i+8]));<br>if(usRetPort==m_usHostPortUdp)<br>{<br>bIsForMe=TRUE;<br>}<br>}<br>i+=10;<br>}<br>//&nbsp;客户端收发结束<br><br><br>//&nbsp;服务器端发送实例<br>//&nbsp;m_ulProxyUDPAddr 代理绑定地址<br>//&nbsp;m_usUDPAssociatePort 代理绑定端口<br>//&nbsp;m_ulHostAddr 本机绑定地址<br>//&nbsp;m_usHostPortUdp 本机绑定端口<br>//&nbsp;客户端必须把上面的m_ulProxyUDPAddr,m_usUDPAssociatePort,m_ulHostAddr,m_usHostPortUdp告知服务器，服务器通过这两个套接字进行数据的收发<br><br>//&nbsp;服务器创建数据报套接字<br>SOCKET&nbsp;m_socUDP=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,WSA_FLAG_OVERLAPPED);<br><br>//&nbsp;代理的udp中继地址<br>SOCKADDR_IN&nbsp;saiClient;<br>saiClient.sin_family&nbsp;=&nbsp;AF_INET;<br>saiClient.sin_addr.S_un.S_addr&nbsp;=&nbsp;m_ulProxyUDPAddr;<br>saiClient.sin_port&nbsp;=&nbsp;m_usUDPAssociatePort;<br>//&nbsp;如果远程目标在代理服务器后面，透过代理，指定远程目标的socks信息，参照RFC1928<br>char&nbsp;buffer[10]={'\0'};<br>buffer[0]=buffer[1]=buffer[2]=0;<br>buffer[3]=1; //&nbsp;No&nbsp;Fragment,&nbsp;It's&nbsp;a&nbsp;dependent&nbsp;udp,&nbsp;no&nbsp;need&nbsp;for&nbsp;socks&nbsp;server&nbsp;to&nbsp;arrange&nbsp;udp&nbsp;fragments<br><br>//&nbsp;目标机器的地址端口，透过代理后发向这里<br>*(int*)&amp;buffer[4]=m_ulHostAddr;<br>*(unsigned&nbsp;short*)&amp;buffer[8]=m_usHostPortUdp;<br>BYTE&nbsp;buf[DATA_BUFSIZE]={'\0'};<br>memmove(&amp;buf[10],&amp;szSendbuf[0],dwLength);<br>memmove(&amp;buf[0],&amp;buffer[0],10);<br>int&nbsp;nSent&nbsp;=&nbsp;sendto(m_socUDP,&nbsp;(const&nbsp;char*)&amp;buf[0],&nbsp;dwLength+10,&nbsp;0,&nbsp;(SOCKADDR*)&amp;saiClient,sizeof(saiClient));<br>//&nbsp;服务器端发送结束<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30235.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:14 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30235.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Completion Port</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30234.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 05:10:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30234.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30234.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30234.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30234.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30234.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 10pt; FONT-FAMILY: Arial">Windows2000之后的版本提供的完成端口的应用，这使得开发服务器端程序变得更简便了，我们不用再为每一个连接而去维护令人厌烦的线程池。Windows提供的完成端口封装了一切繁琐的工作，我们唯一要做的就是对完成端口的应用。<br><br>//&nbsp;创建监听套接字<br>SOCKET&nbsp;m_socListen&nbsp;=&nbsp;WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);<br>//&nbsp;设置异步连接标志<br>if&nbsp;(WSAAsyncSelect(m_socListen,&nbsp;m_hMainWnd,&nbsp;UM_SOCKETEVENT,&nbsp;FD_ACCEPT)==SOCKET_ERROR)&nbsp;return&nbsp;SOCKET_ERROR;<br>//&nbsp;初始化完成端口<br>IOCP_Initialize();<br>/*<br>IOCP_Initialize()<br>{<br>SYSTEM_INFO&nbsp;systeminfo;<br>DWORD&nbsp;dwThreadID;<br>m_hCompletionPort&nbsp;=&nbsp;CreateIoCompletionPort(INVALID_HANDLE_VALUE,&nbsp;NULL,&nbsp;0,&nbsp;0);<br>if(&nbsp;m_hCompletionPort&nbsp;==&nbsp;NULL&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>TRACE(_T("CreateIoCompletionPort&nbsp;failed&nbsp;with&nbsp;error:&nbsp;%d\n"<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>,&nbsp;GetLastError());<br>return&nbsp;-1;<br>}<br>m_bStopIOCP=FALSE;<br>m_hThreadIOCP=NULL;<br>GetSystemInfo(&nbsp;&amp;systeminfo&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>for(DWORD&nbsp;i=0;&nbsp;i&lt;systeminfo.dwNumberOfProcessors;&nbsp;i++)<br>{<br>m_hThreadIOCP&nbsp;=&nbsp;CreateThread(&nbsp;NULL, //&nbsp;Security<br>0, //&nbsp;Stack&nbsp;size&nbsp;-&nbsp;use&nbsp;default<br>(LPTHREAD_START_ROUTINE)IOCP_ThreadPoolFunc, //&nbsp;Thread&nbsp;function&nbsp;entry&nbsp;point<br>(void*)&nbsp;this, //&nbsp;Param&nbsp;for&nbsp;thread<br>0, //&nbsp;Init&nbsp;flag<br>&amp;dwThreadID); //&nbsp;Thread&nbsp;ID<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(m_hThreadIOCP!=NULL)&nbsp;<br>{<br>InterlockedIncrement((LPLONG)&amp;m_nWorkerCnt);<br>CloseHandle(m_hThreadIOCP);<br>}<br>}<br>}<br><br>IOCP_ThreadPoolFunc&nbsp;(LPVOID&nbsp;lpParam)&nbsp;&nbsp;&nbsp;&nbsp;<br>{<br>ASSERT(lpParam);<br>CIOCPServer*&nbsp;pThis&nbsp;=&nbsp;reinterpret_cast&lt;CIOCPServer*&gt;(lpParam);<br>HANDLE&nbsp;hCompletionPort&nbsp;=&nbsp;pThis-&gt;m_hCompletionPort;<br><br>DWORD&nbsp;dwBytesTransferred=0; //&nbsp;Bytes&nbsp;transferred&nbsp;in&nbsp;the&nbsp;Completion&nbsp;packet<br>DWORD&nbsp;dwIoType=0; //&nbsp;The&nbsp;operation&nbsp;type&nbsp;of&nbsp;completion&nbsp;packet<br>DWORD&nbsp;dwFlags=0;<br><br>LPPERHANDLEDATA&nbsp;lpPerHandleData;<br>LPPERIOOPERATIONDATA&nbsp;lpPerIoData;<br><br>while(!pThis-&gt;IsIOCPstopped())<br>{<br>BOOL&nbsp;bIoRet&nbsp;=&nbsp;FALSE;<br>if((bIoRet&nbsp;=&nbsp;GetQueuedCompletionStatus(<br>hCompletionPort,<br>&amp;dwBytesTransferred,<br>(LPDWORD)&nbsp;&amp;lpPerHandleData,<br>(LPOVERLAPPED*)&amp;lpPerIoData,<br>INFINITE))==0)<br>{<br>DWORD&nbsp;dwError=GetLastError();<br>if(dwError==ERROR_NETNAME_DELETED)<br>{<br>//&nbsp;服务器主动关闭套接字<br>if(lpPerIoData)&nbsp;{&nbsp;pThis-&gt;FreePerIoData(lpPerIoData);&nbsp;lpPerIoData=NULL;&nbsp;};<br>/*<br>FreePerIoData(PERIOOPERATIONDATA*&nbsp;lpPerIoData)<br>{<br>if(lpPerIoData-&gt;m_pOutBuf)<br>{<br>delete[]&nbsp;lpPerIoData-&gt;m_pOutBuf;<br>lpPerIoData-&gt;m_pOutBuf=NULL;<br>}<br>delete(lpPerIoData);<br>lpPerIoData=NULL; <br>}<br>*/<br>continue;<br>}<br><br>TRACE("GetQueuedCompletionStatus&nbsp;failed&nbsp;with&nbsp;error&nbsp;%d\n",&nbsp;dwError);<br>InterlockedDecrement(&amp;pThis-&gt;m_nWorkerCnt);<br>return&nbsp;0;<br>}<br><br>if(lpPerIoData==NULL&nbsp;&amp;&amp;&nbsp;lpPerHandleData==NULL)<br>{<br>//&nbsp;服务器关闭完成端口<br>InterlockedDecrement(&amp;pThis-&gt;m_nWorkerCnt);<br>return&nbsp;0;<br>}<br><br>//&nbsp;First&nbsp;check&nbsp;to&nbsp;see&nbsp;if&nbsp;an&nbsp;error&nbsp;has&nbsp;occured&nbsp;on&nbsp;the&nbsp;socket&nbsp;and&nbsp;if&nbsp;so<br>//&nbsp;then&nbsp;close&nbsp;the&nbsp;socket&nbsp;and&nbsp;cleanup&nbsp;the&nbsp;SOCKET_INFORMATION&nbsp;structure<br>//&nbsp;associated&nbsp;with&nbsp;the&nbsp;socket.<br>if&nbsp;((dwBytesTransferred==0)&nbsp;&amp;&amp;&nbsp;((lpPerIoData-&gt;m_ioType==IORead)||(lpPerIoData-&gt;m_ioType==IOWrite)))<br>{<br>pThis-&gt;RemoveStaleClient(lpPerHandleData-&gt;m_Socket,FALSE);<br>if(lpPerIoData)&nbsp;{&nbsp;pThis-&gt;FreePerIoData(lpPerIoData);&nbsp;lpPerIoData=NULL;<br>continue;<br>}<br><br>if(&nbsp;lpPerHandleData!=NULL&nbsp;&amp;&amp;&nbsp;lpPerIoData!=NULL&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>BOOL&nbsp;bRet&nbsp;=&nbsp;pThis-&gt;IOCP_ProcessIOMessage(lpPerHandleData,&nbsp;lpPerIoData,&nbsp;dwBytesTransferred);<br><br>//&nbsp;Before&nbsp;Every&nbsp;PostQueuedCompletionStatus()&nbsp;call&nbsp;we&nbsp;allocate&nbsp;a&nbsp;memory&nbsp;for&nbsp;PerIoData.<br>//&nbsp;And&nbsp;Before&nbsp;every&nbsp;WSASend()&nbsp;and&nbsp;WSARecv()&nbsp;call&nbsp;we&nbsp;also&nbsp;allocate&nbsp;such&nbsp;a&nbsp;block&nbsp;of&nbsp;memory.<br>//&nbsp;So&nbsp;After&nbsp;handling&nbsp;every&nbsp;QueuedCompletion&nbsp;Packet,&nbsp;we&nbsp;Free&nbsp;this&nbsp;block&nbsp;if&nbsp;it's&nbsp;not&nbsp;useful.<br><br>/*<br>enum&nbsp;IOType&nbsp;{ IOIdle,IORead,IOWrite,IOInitialize };<br>IOCP_ProcessIOMessage(PERHANDLEDATA*&nbsp;pContext,&nbsp;PERIOOPERATIONDATA*&nbsp;pPerIoData,&nbsp;DWORD&nbsp;dwSize&nbsp;=&nbsp;0)<br>{<br>int&nbsp;nRet&nbsp;=&nbsp;0;&nbsp;<br>IOType&nbsp;clientIO&nbsp;=&nbsp;pPerIoData-&gt;m_ioType;&nbsp;<br><br>if(&nbsp;clientIO&nbsp;==&nbsp;IOInitialize)<br>nRet&nbsp;=&nbsp;IOCP_OnClientInitializing(pContext,&nbsp;pPerIoData,&nbsp;dwSize);<br>else&nbsp;if(&nbsp;clientIO&nbsp;==&nbsp;IORead)<br>nRet&nbsp;=&nbsp;IOCP_OnClientReading(pContext,&nbsp;pPerIoData,&nbsp;dwSize);<br>else&nbsp;if(&nbsp;clientIO&nbsp;==&nbsp;IOWrite)<br>nRet&nbsp;=&nbsp;IOCP_OnClientWriting(pContext,&nbsp;pPerIoData,&nbsp;dwSize);<br>}<br><br>IOCP_OnClientInitializing(PERHANDLEDATA*&nbsp;lpContext,&nbsp;PERIOOPERATIONDATA*&nbsp;lpPerIoData,&nbsp;DWORD&nbsp;dwIoSize)<br>{<br>//&nbsp;launch&nbsp;another&nbsp;recv&nbsp;operation.<br>DWORD&nbsp;dwBytesTransferred=0;<br>PERIOOPERATIONDATA*&nbsp;&nbsp;lpPerIoDataResv&nbsp;=&nbsp;new&nbsp;PERIOOPERATIONDATA;<br>memset(lpPerIoDataResv,0,sizeof(PERIOOPERATIONDATA));<br>lpPerIoDataResv-&gt;m_ioType&nbsp;=&nbsp;IORead;<br>lpPerIoDataResv-&gt;m_wsaDataBuf.buf&nbsp;=&nbsp;lpPerIoDataResv-&gt;m_Buffer;<br>lpPerIoDataResv-&gt;m_wsaDataBuf.len&nbsp;=&nbsp;sizeof(lpPerIoDataResv-&gt;m_Buffer);<br><br>ULONG&nbsp;ulFlags&nbsp;=&nbsp;0;<br>int&nbsp;nRet&nbsp;=&nbsp;WSARecv( lpContext-&gt;m_Socket,<br>&amp;lpPerIoDataResv-&gt;m_wsaDataBuf,<br>1,<br>&amp;dwBytesTransferred,<br>&amp;ulFlags,<br>(LPWSAOVERLAPPED)&amp;lpPerIoDataResv-&gt;Overlapped,<br>NULL);<br><br>if&nbsp;(&nbsp;nRet&nbsp;==&nbsp;SOCKET_ERROR&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>DWORD&nbsp;dwError&nbsp;=&nbsp;WSAGetLastError();<br>if(&nbsp;dwError&nbsp;!=&nbsp;WSA_IO_PENDING)&nbsp;<br>{<br>RemoveStaleClient(&nbsp;lpContext-&gt;m_Socket,&nbsp;FALSE&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>return&nbsp;NC_E_IOCP_INITIALIZE|dwError;<br>}<br>}<br>}<br><br>IOCP_OnClientReading(PERHANDLEDATA*&nbsp;pContext,&nbsp;PERIOOPERATIONDATA*&nbsp;pPerIoData,&nbsp;DWORD&nbsp;dwSize)<br>{<br>//&nbsp;make&nbsp;sure&nbsp;to&nbsp;issue&nbsp;a&nbsp;read&nbsp;after&nbsp;this<br>//&nbsp;pContext-&gt;m_wsaInBuffer.buf/*==pContext-&gt;m_byInBuffer*/是该套接字的缓冲区，<br>//&nbsp;如果该缓冲区中有数据(pContext-&gt;m_wsaInBuffer.len!=0时)，<br>//&nbsp;则说明上次收到了一个请求包的一部分数据，把这次的数据继续放入<br>memmove(pContext-&gt;m_wsaInBuffer.buf+pContext-&gt;m_wsaInBuffer.len,pPerIoData-&gt;m_wsaDataBuf.buf,dwSize);<br>pContext-&gt;m_wsaInBuffer.len+=dwSize;<br>HandleRequest(pContext);<br><br>//&nbsp;launch&nbsp;another&nbsp;recv&nbsp;operation.<br>DWORD&nbsp;dwBytesTransferred=0;<br>PERIOOPERATIONDATA*&nbsp;&nbsp;lpPerIoDataResv&nbsp;=&nbsp;new&nbsp;PERIOOPERATIONDATA;<br>memset(lpPerIoDataResv,0,sizeof(PERIOOPERATIONDATA));<br>lpPerIoDataResv-&gt;m_ioType&nbsp;=&nbsp;IORead;<br>lpPerIoDataResv-&gt;m_wsaDataBuf.buf&nbsp;=&nbsp;lpPerIoDataResv-&gt;m_Buffer;<br>lpPerIoDataResv-&gt;m_wsaDataBuf.len&nbsp;=&nbsp;sizeof(lpPerIoDataResv-&gt;m_Buffer);<br><br>int&nbsp;nRet&nbsp;=&nbsp;WSARecv( pContext-&gt;m_Socket,<br>&amp;lpPerIoDataResv-&gt;m_wsaDataBuf,<br>1,<br>&amp;dwBytesTransferred,<br>&amp;ulFlags,<br>(LPWSAOVERLAPPED)&amp;lpPerIoDataResv-&gt;Overlapped,<br>NULL);<br><br>if&nbsp;(&nbsp;nRet&nbsp;==&nbsp;SOCKET_ERROR&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>DWORD&nbsp;dwError&nbsp;=&nbsp;WSAGetLastError();<br>if(&nbsp;dwError&nbsp;!=&nbsp;WSA_IO_PENDING&nbsp;&amp;&amp;&nbsp;dwError&nbsp;!=&nbsp;WSAENOTSOCK)&nbsp;<br>{<br>RemoveStaleClient(&nbsp;pContext-&gt;m_Socket,&nbsp;FALSE&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>return&nbsp;NC_E_IOCP_READ|dwError;<br>}<br>}<br>}<br><br>IOCP_OnClientWriting(PERHANDLEDATA*&nbsp;pContext,&nbsp;PERIOOPERATIONDATA*&nbsp;pPerIoData,&nbsp;DWORD&nbsp;dwSize)<br>{<br>//&nbsp;SendPacket调用异步发送数据，当数据发送完成后，将来此处<br>}<br><br>SendPacket(PERHANDLEDATA*&nbsp;pContext,BYTE*&nbsp;sendbuf,DWORD&nbsp;dwLength)<br>{<br>//&nbsp;发送数据代码断如下:<br>PERIOOPERATIONDATA*&nbsp;&nbsp;lpPerIoDataRPP&nbsp;=&nbsp;new&nbsp;PERIOOPERATIONDATA;<br>memset(lpPerIoDataRPP,0,sizeof(PERIOOPERATIONDATA));<br>lpPerIoDataRPP-&gt;m_ioType&nbsp;=&nbsp;IOWrite;<br>memset(lpPerIoDataRPP-&gt;m_Buffer,0,sizeof(lpPerIoDataRPP-&gt;m_Buffer));<br>lpPerIoDataRPP-&gt;m_pOutBuf=new&nbsp;BYTE[dwLength];<br>memmove(lpPerIoDataRPP-&gt;m_pOutBuf,(BYTE*)sendbuf,dwLength);<br>lpPerIoDataRPP-&gt;m_wsaDataBuf.buf&nbsp;=&nbsp;(char*)lpPerIoDataRPP-&gt;m_pOutBuf;<br>lpPerIoDataRPP-&gt;m_wsaDataBuf.len&nbsp;=&nbsp;dwLength;<br>int&nbsp;nRetVal&nbsp;=&nbsp;WSASend(pContext-&gt;m_Socket,<br>&amp;lpPerIoDataRPP-&gt;m_wsaDataBuf,<br>1,<br>&amp;lpPerIoDataRPP-&gt;m_wsaDataBuf.len,&nbsp;<br>0,<br>&amp;(lpPerIoDataRPP-&gt;Overlapped),&nbsp;<br>NULL);<br>if&nbsp;(&nbsp;nRetVal&nbsp;==&nbsp;SOCKET_ERROR&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0><br>{<br>DWORD&nbsp;dwError=WSAGetLastError();<br>if(&nbsp;dwError&nbsp;!=&nbsp;WSA_IO_PENDING&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;<br>{<br>RemoveStaleClient(&nbsp;socClient,&nbsp;FALSE&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>return&nbsp;NC_E_TCP_SENDRESPONSE|dwError;<br>}<br>}<br>//&nbsp;发送完毕<br>}<br>*/<br><br>if(&nbsp;lpPerIoData&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;{&nbsp;pThis-&gt;FreePerIoData(lpPerIoData);&nbsp;lpPerIoData=NULL;&nbsp;}<br>}<br>}<br><br>InterlockedDecrement(&amp;pThis-&gt;m_nWorkerCnt);<br><br>return&nbsp;0;<br>}<br>*/<br><br>//&nbsp;绑定服务器地址<br>SOCKADDR_IN&nbsp;saiServer;<br>saiServer.sin_family&nbsp;=&nbsp;AF_INET;<br>saiServer.sin_port&nbsp;&nbsp;&nbsp;=&nbsp;htons(NC_S_PORT_LISTEN);<br>saiServer.sin_addr.S_un.S_addr&nbsp;=&nbsp;inet_addr(pszLocalAddr);<br>if(bind(m_socListen,(SOCKADDR*)&amp;saiServer,sizeof(saiServer))==SOCKET_ERROR)&nbsp;return&nbsp;NC_E_TCP_LISTEN|WSAGetLastError();<br>//&nbsp;开始侦听<br>if(listen(m_socListen,SOMAXCONN)==SOCKET_ERROR)&nbsp;return&nbsp;NC_E_TCP_LISTEN|WSAGetLastError();<br><br><br>case&nbsp;FD_ACCEPT:&nbsp;IOCP_OnAccept();break;<br><br>IOCP_OnAccept()<br>{<br>SOCKET&nbsp;socClient;<br>SOCKADDR_IN&nbsp;saiRemote;<br>int&nbsp;nLen&nbsp;=&nbsp;sizeof(saiRemote);<br>if(&nbsp;(socClient=accept(m_socListen,(LPSOCKADDR)&amp;saiRemote,&amp;nLen))==SOCKET_ERROR)&nbsp;return&nbsp;NC_E_TCP_ONACCEPT|WSAGetLastError();<br><br>//&nbsp;1、设置套接字选项<br>BOOL&nbsp;bOpt&nbsp;=&nbsp;true;<br>int&nbsp;nRcvBuf&nbsp;=&nbsp;BUFSIZE_NC_S_RCVBUF;<br>int&nbsp;nSndBuf&nbsp;=&nbsp;BUFSIZE_NC_S_SNDBUF;<br>int&nbsp;nErr&nbsp;=&nbsp;0;<br>int&nbsp;nTimeout&nbsp;=&nbsp;10*1000; <br>nErr&nbsp;=&nbsp;setsockopt(socClient,&nbsp;SOL_SOCKET,&nbsp;SO_SNDTIMEO,&nbsp;(char*)&amp;nTimeout,&nbsp;sizeof(nTimeout));<br>nErr&nbsp;=&nbsp;setsockopt(socClient,&nbsp;SOL_SOCKET,&nbsp;SO_RCVBUF,&nbsp;(const&nbsp;char*)&amp;nRcvBuf,sizeof(nRcvBuf));<br>nErr&nbsp;=&nbsp;setsockopt(socClient,&nbsp;SOL_SOCKET,&nbsp;SO_SNDBUF,&nbsp;(const&nbsp;char*)&amp;nSndBuf,sizeof(nSndBuf));<br><br>//&nbsp;2、将该套接字关联完成端口<br>//&nbsp;Create&nbsp;the&nbsp;Client&nbsp;context&nbsp;to&nbsp;be&nbsp;associted&nbsp;with&nbsp;the&nbsp;completion&nbsp;port<br>PERHANDLEDATA*&nbsp;pContext&nbsp;=&nbsp;IOCP_AllocatePerHandleContext();<br><br>/*<br>typedef&nbsp;struct&nbsp;tagPerHandleData<br>{<br>SOCKET&nbsp;m_Socket;<br>DWORD&nbsp;&nbsp;m_dwStatus;<br><br>//&nbsp;Input&nbsp;Elements&nbsp;for&nbsp;Winsock<br>WSABUF&nbsp;m_wsaInBuffer;<br>BYTE&nbsp;&nbsp;&nbsp;m_byInBuffer[DATA_BUFSIZE];&nbsp;&nbsp;&nbsp;&nbsp;<br><br>//&nbsp;Output&nbsp;elements&nbsp;for&nbsp;Winsock<br>WSABUF&nbsp;m_wsaOutBuffer;<br><br>}PERHANDLEDATA,&nbsp;*LPPERHANDLEDATA;<br><br>IOCP_AllocatePerHandleContext()<br>{<br>PERHANDLEDATA*&nbsp;pContext&nbsp;=&nbsp;NULL;<br>if&nbsp;(!m_listFreePool.IsEmpty())<br>{<br>pContext&nbsp;=&nbsp;m_listFreePool.RemoveHead();<br>}<br>else<br>{<br>pContext&nbsp;=&nbsp;new&nbsp;PERHANDLEDATA;<br>memset(pContext,0,sizeof(PERHANDLEDATA));<br>}<br>if(pContext)&nbsp;memset(pContext,0,sizeof(PERHANDLEDATA));<br>return&nbsp;pContext;<br>}<br>*/<br><br>pContext-&gt;m_Socket&nbsp;=&nbsp;socClient;<br>pContext-&gt;m_wsaInBuffer.buf&nbsp;=&nbsp;(char*)pContext-&gt;m_byInBuffer;<br>pContext-&gt;m_wsaInBuffer.len&nbsp;=&nbsp;0;<br><br>//&nbsp;保存PerHandleContext的列表<br>PERHANDLEDATA*&nbsp;pContextTmp&nbsp;=&nbsp;NULL;<br>if&nbsp;(m_listContexts.Lookup(socClient,&nbsp;pContextTmp))<br>RemoveStaleClient(socClient,TRUE);<br>else<br>m_listContexts.SetAt(nSocket,&nbsp;pContext);<br><br>/*<br>RemoveStaleClient(SOCKET&nbsp;s,BOOL&nbsp;bGraceful)<br>{<br>PERHANDLEDATA*&nbsp;pContext=NULL;<br>m_listContexts.Lookup(nSocket,pContext);<br>if(pContext==NULL)&nbsp;return;<br><br>LINGER&nbsp;lingerStruct;<br>if&nbsp;(&nbsp;bGraceful&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>&nbsp;<br>{<br>lingerStruct.l_onoff&nbsp;=&nbsp;1;<br>lingerStruct.l_linger=&nbsp;30; //&nbsp;linger&nbsp;for&nbsp;30&nbsp;seconds,&nbsp;0&nbsp;for&nbsp;abort<br>}<br>else<br>{<br>lingerStruct.l_onoff&nbsp;=&nbsp;0;<br>lingerStruct.l_linger=&nbsp;0; //&nbsp;ignored<br>}<br>setsockopt(&nbsp;pContext-&gt;m_Socket,&nbsp;SOL_SOCKET,&nbsp;SO_LINGER,&nbsp;(char&nbsp;*)&amp;lingerStruct,&nbsp;sizeof(lingerStruct)&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>while&nbsp;(!HasOverlappedIoCompleted((LPOVERLAPPED)pContext))&nbsp;Sleep(0);<br>MoveToFreePool(nSocket);<br><br>//<br>//MoveToFreePool(SOCKET&nbsp;s)<br>//{<br>// PERHANDLEDATA*&nbsp;pContext&nbsp;=&nbsp;NULL;<br>//<br>//&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Free&nbsp;context&nbsp;structures<br>// if&nbsp;(m_listContexts.Lookup(nSocket,&nbsp;pContext))&nbsp;<br>// {<br>// m_listFreePool.AddTail(pContext);<br>// m_listContexts.RemoveKey(nSocket);<br>// }<br>//}<br>//<br><br>//&nbsp;Free&nbsp;context&nbsp;structures<br>//&nbsp;Now&nbsp;close&nbsp;the&nbsp;socket&nbsp;handle.&nbsp;&nbsp;This&nbsp;will&nbsp;do&nbsp;an&nbsp;abortive&nbsp;or&nbsp;&nbsp;graceful&nbsp;close,&nbsp;as&nbsp;requested.&nbsp;&nbsp;<br>closesocket(&nbsp;pContext-&gt;m_Socket&nbsp;<img onclick="window.open('/images/wink.gif','_blank');" hspace=2 src="http://www.blogcn.com/images/wink.gif" onload="javascript:if(this.width>screen.width/2)this.width=screen.width/2" vspace=2 border=0>;<br>pContext-&gt;m_Socket&nbsp;=&nbsp;INVALID_SOCKET;<br>}<br>*/<br><br>//&nbsp;Associate&nbsp;the&nbsp;new&nbsp;socket&nbsp;with&nbsp;a&nbsp;completion&nbsp;port.<br>if(IOCP_AssociateSocketWithPort(pContext-&gt;m_Socket,&nbsp;m_hCompletionPort,&nbsp;(DWORD)pContext)!=0)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RemoveStaleClient(socClient,TRUE);<br>DWORD&nbsp;dwError=GetLastError();<br>return&nbsp;NC_E_TCP_ONACCEPT;<br>}<br>/*<br>IOCP_AssociateSocketWithPort(SOCKET&nbsp;socket,&nbsp;HANDLE&nbsp;hCompletionPort,&nbsp;DWORD&nbsp;dwCompletionKey)<br>{<br>HANDLE&nbsp;h=CreateIoCompletionPort((HANDLE)&nbsp;socket,&nbsp;hCompletionPort,&nbsp;dwCompletionKey,&nbsp;0);<br>return&nbsp;(h==hCompletionPort)?0:-1;<br>}<br>*/<br><br>//&nbsp;3、该套接字的完成端口初始化<br>//&nbsp;Trigger&nbsp;first&nbsp;IO&nbsp;Completion&nbsp;Request<br>//&nbsp;Otherwise&nbsp;the&nbsp;Worker&nbsp;thread&nbsp;will&nbsp;remain&nbsp;blocked&nbsp;waiting&nbsp;for&nbsp;GetQueuedCompletionStatus...<br>//&nbsp;The&nbsp;first&nbsp;message&nbsp;that&nbsp;gets&nbsp;queued&nbsp;up&nbsp;is&nbsp;ClientIoInitializing&nbsp;-&nbsp;see&nbsp;ThreadPoolFunc&nbsp;<br>/*<br>typedef&nbsp;struct&nbsp;tagPerIoOperationData<br>{<br>OVERLAPPED&nbsp;Overlapped;<br>IOType m_ioType;<br><br>BYTE*&nbsp;&nbsp;m_pOutBuf;<br>WSABUF&nbsp;m_wsaDataBuf;<br>CHAR&nbsp;&nbsp;&nbsp;m_Buffer[DATA_BUFSIZE];<br><br>}&nbsp;PERIOOPERATIONDATA,*LPPERIOOPERATIONDATA;<br>*/<br>PERIOOPERATIONDATA*&nbsp;&nbsp;lpPerIoData&nbsp;=&nbsp;NULL;<br>lpPerIoData&nbsp;=&nbsp;new&nbsp;PERIOOPERATIONDATA;<br>memset(lpPerIoData,0,sizeof(PERIOOPERATIONDATA));<br>lpPerIoData-&gt;m_ioType&nbsp;=&nbsp;IOInitialize;<br>lpPerIoData-&gt;m_wsaDataBuf.len&nbsp;=&nbsp;sizeof(lpPerIoData-&gt;m_Buffer);<br>lpPerIoData-&gt;m_wsaDataBuf.buf&nbsp;=&nbsp;lpPerIoData-&gt;m_Buffer;<br>BOOL&nbsp;bSuccess&nbsp;=&nbsp;PostQueuedCompletionStatus(m_hCompletionPort,&nbsp;0,&nbsp;(DWORD)&nbsp;pContext,&nbsp;&amp;lpPerIoData-&gt;Overlapped);<br>if(!bSuccess)&nbsp;return&nbsp;NC_E_TCP_ONACCEPT|WSAGetLastError();<br>return&nbsp;socClient;<br>}<br><br>//&nbsp;结束的时候需要关闭完成端口<br>CloseListenSocket()<br>{<br>//&nbsp;关闭每个连接,&nbsp;从列表中移去关键字<br>PERHANDLEDATA*&nbsp;pContext&nbsp;=&nbsp;NULL;<br>int&nbsp;nSocket=0;<br>do<br>{<br>POSITION&nbsp;pos&nbsp;&nbsp;=&nbsp;m_listContexts.GetStartPosition();<br>if&nbsp;(pos)<br>{<br>m_listContexts.GetNextAssoc(pos,&nbsp;nSocket,&nbsp;pContext); <br>RemoveStaleClient(nSocket,&nbsp;FALSE);<br>}<br>}while&nbsp;(!m_listContexts.IsEmpty());<br><br>//&nbsp;释放每个连接相关的分配内存<br>while&nbsp;(!m_listFreePool.IsEmpty())<br>{<br>PERHANDLEDATA*&nbsp;pContext&nbsp;=&nbsp;m_listFreePool.RemoveTail();<br>delete&nbsp;pContext;<br>}<br><br>m_bStopIOCP=TRUE;<br><br>if(m_bIOCPInitialed==TRUE)<br>{<br>IOCP_CloseCompletionPort();<br>/*<br>IOCP_CloseCompletionPort()<br>{<br>while&nbsp;(m_nWorkerCnt)<br>{<br>PostQueuedCompletionStatus(m_hCompletionPort,&nbsp;0,&nbsp;(DWORD)&nbsp;NULL,&nbsp;NULL);<br>Sleep(1000);<br>}<br>//&nbsp;Close&nbsp;the&nbsp;CompletionPort&nbsp;and&nbsp;stop&nbsp;any&nbsp;more&nbsp;requests<br>CloseHandle(m_hCompletionPort);<br>m_bIOCPInitialed=FALSE;<br>m_bStopIOCP=TRUE;<br>}<br>} */<br>}<br><br>if(m_socListen!=0)<br>{<br>closesocket(m_socListen);<br>m_socListen=0;<br>}<br>return&nbsp;0;<br>}<br></span>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30234.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 13:10 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30234.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Using I/O completion ports with UDP</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30232.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 04:56:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30232.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30232.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30232.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30232.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30232.html</trackback:ping><description><![CDATA[<h2>Introduction</h2>
<p>I have been writing server applications on Windows server platforms for quite sometime. We all know that I/O completion port is one of the nicest things available for writing server applications (or even in clients where you can apply Worker-Boss pattern). There are tons of articles and samples to show how I/O completion port can be used with TCP/IP. Surprisingly enough, I did not find any source code examples to demonstrate the use of I/O completion ports with UDP.</p>
<p>To spice up things a little, I took the multicast topic to demonstrate the code. Most of the multicast code is borrowed from Mr. Bob Quinn's article.</p>
<p>This sample consists of two projects (Client and Server). In order to test this application as it is, routers in your network should be enabled for multicast routing. If not, modify samples appropriately to act just UDP server and client.</p>
<p>Since there is so much written about I/O completion port and multicast, I won't go into all the details in this article.</p>
<p>Server application design and implementation is an art. You can put so many bells and whistles. Typical server application should have the following:</p>
<ol>
    <li>
    <h3>Object Pooling</h3>
    <p>If any of the object is to be created and destroyed often, consider making a pool of such objects. Typical example would be your sockets, user context objects, database connections.</p>
    <li>
    <h3>Ease of configuration</h3>
    <p>Do not hardcode configuration information in the code. Try to use XML configuration files. Let the application read these vales during the startup. Typical examples would be, IP address of other servers, Oracle TNS names, user ids and passwords (!!!), number of worker threads, email addresses etc.</p>
    <p>Besides, there should be some mechanism to change these parameters while the server is running.</p>
    <li>
    <h3>Monitoring and logging</h3>
    <p>Since most of these servers will be running somewhere in the data center, it is nice to have a small component sitting in your server application which will send log messages to central server from where you multicast those messages to other client applications. Make sure you have very good filtering mechanism so that you can control the number of messages being logged during the run time.</p>
    <li>
    <h3>Error handling</h3>
    <p>Your servers have to be running nonstop (four nines most of the time). That means you should have proper error handling mechanism. Make use of SEH in a responsible manner. Besides keep track of number of TCP connections (using SNMP) and CPU usage, memory usage (Microsoft provides a library by which you can query all these parameters from within your program).</p>
    <li>
    <h3>Thread Synchronization</h3>
    <p>Make sure you thread protects the shared data.</p>
    <li>
    <h3>Load balancing.</h3>
    <p>You can not serve infinite number of clients from one server box. Always keep the 'scale-out' factor in mind. Do proper capacity planning to quantify your server hardware requirement. 80-20 rule is quite effective.</p>
    </li>
</ol>
<h2>Source Code</h2>
<div class=precollapse id=premain0 style="WIDTH: 100%"><img id=preimg0 style="CURSOR: hand" height=9 src="http://www.codeproject.com/images/minus.gif" width=9 preid="0"><span id=precollapse0 style="MARGIN-BOTTOM: 0px; CURSOR: hand" preid="0"> Collapse</span></div>
<pre id=pre0 style="MARGIN-TOP: 0px"><span class=cpp-comment>//NOTE</span>
<span class=cpp-comment>// </span>
<span class=cpp-comment>//This code taken from <strong>Mr. Bob Quinn's</strong> article </span>
<span class=cpp-comment>//titled 'Internet Multicasting'</span>
<span class=cpp-comment>//published in Dr. Dobb's Journal dated Oct 1997</span>
<span class=cpp-comment>//I have modified the original code to illustrate </span>
<span class=cpp-comment>//the use I/O completion ports with UDP.</span>
<span class=cpp-comment>//If you have any comments email me : <strong>shapall@hotmail.com</strong></span>
<span class=cpp-preprocessor>#include "StdAfx.h"</span>
<span class=cpp-preprocessor>#include &lt;winsock2.h&gt;</span>
<span class=cpp-preprocessor>#include &lt;ws2tcpip.h&gt;</span>
<span class=cpp-preprocessor>#include "Stdio.h"</span>
<span class=cpp-preprocessor>#define BUFSIZE 1024 //max size of incoming data buffer</span>
<span class=cpp-preprocessor>#define MAXADDRSTR 16</span>
<span class=cpp-preprocessor>#define DEFAULT_GROUP_ADDRESS "239.254.1.2"</span>
<span class=cpp-preprocessor>#define DEFAULT_PORT 7125 </span>
LONG nCount = <span class=cpp-literal>0</span>;
HANDLE g_hCompletionPort;
DWORD WINAPI WorkerThread( LPVOID WorkContext );
BOOL HandleIncomingData( UCHAR* pBuf);
BOOL CreateNetConnections( VOID );
BOOL CreateWorkers( UINT );
<span class=cpp-keyword>void</span> InitWinsock2();
<span class=cpp-keyword>void</span> UnInitWinsock2();
HANDLE g_hReadEvent;
SOCKET g_hSocket;
UCHAR achInBuf [BUFSIZE];
<span class=cpp-keyword>char</span> achMCAddr[MAXADDRSTR] = DEFAULT_GROUP_ADDRESS;
u_short nPort = DEFAULT_PORT;
OVERLAPPED Overlapped;
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong><span class=cpp-keyword>void</span> InitWinsock2()</strong>
{
WSADATA data;
WORD version;
<span class=cpp-keyword>int</span> ret = <span class=cpp-literal>0</span>;
version = (MAKEWORD(<span class=cpp-literal>2</span>, <span class=cpp-literal>2</span>));
ret = WSAStartup(version, &amp;data);
<span class=cpp-keyword>if</span> (ret != <span class=cpp-literal>0</span>)
{
ret = WSAGetLastError();
<span class=cpp-keyword>if</span> (ret == WSANOTINITIALISED)
{
printf(<span class=cpp-string>"not initialised"</span>);
}
}
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong><span class=cpp-keyword>void</span> UnInitWinsock2()</strong>
{
WSACleanup();
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong>BOOL CreateNetConnections (<span class=cpp-keyword>void</span>)</strong>
{
DWORD nbytes;
BOOL b;
BOOL fFlag = TRUE;
<span class=cpp-keyword>int</span> nRet=<span class=cpp-literal>0</span>;
SOCKADDR_IN stLclAddr;
<span class=cpp-keyword>struct</span> ip_mreq stMreq; <span class=cpp-comment>// Multicast interface structure  </span>
<span class=cpp-comment>// Get a datagram socket  </span>
g_hSocket = socket(AF_INET, SOCK_DGRAM,<span class=cpp-literal>0</span>);
<span class=cpp-keyword>if</span> (g_hSocket == INVALID_SOCKET)
{
printf (<span class=cpp-string>"socket() failed, Err: %d\n"</span>, WSAGetLastError());
<span class=cpp-keyword>return</span> FALSE;
}
nRet = setsockopt(g_hSocket,SOL_SOCKET,
SO_REUSEADDR, (<span class=cpp-keyword>char</span> *)&amp;fFlag, <span class=cpp-keyword>sizeof</span>(fFlag));
<span class=cpp-keyword>if</span> (nRet == SOCKET_ERROR)
{
printf (<span class=cpp-string>"setsockopt() SO_REUSEADDR failed,
Err: %d\n"</span>,WSAGetLastError());
}
<span class=cpp-comment>// Name the socket (assign the local port number to receive on)  </span>
stLclAddr.sin_family = AF_INET;
stLclAddr.sin_addr.s_addr = htonl(INADDR_ANY);
stLclAddr.sin_port = htons(nPort);
nRet = bind(g_hSocket,(<span class=cpp-keyword>struct</span> sockaddr*) &amp;stLclAddr,<span class=cpp-keyword>sizeof</span>(stLclAddr));
<span class=cpp-keyword>if</span> (nRet == SOCKET_ERROR)
{
printf (<span class=cpp-string>"bind() port: %d failed, Err: %d\n"</span>,
nPort,WSAGetLastError());
}
<span class=cpp-comment>// Join the multicast group so we can receive from it  </span>
stMreq.imr_multiaddr.s_addr = inet_addr(achMCAddr);
stMreq.imr_interface.s_addr = INADDR_ANY;
nRet = setsockopt(g_hSocket,IPPROTO_IP,
IP_ADD_MEMBERSHIP,(<span class=cpp-keyword>char</span> *)&amp;stMreq,<span class=cpp-keyword>sizeof</span>(stMreq));
<span class=cpp-keyword>if</span> (nRet == SOCKET_ERROR)
{
printf(<span class=cpp-string>"setsockopt() IP_ADD_MEMBERSHIP address %s failed,
Err: %d\n"</span>,achMCAddr,
WSAGetLastError());
}
<span class=cpp-comment>//</span>
<span class=cpp-comment>//note the 10 says how many concurrent cpu bound threads to allow thru </span>
<span class=cpp-comment>//this should be tunable based on the requests. CPU bound requests will </span>
<span class=cpp-comment>// really really honor this. </span>
<span class=cpp-comment>// </span>
g_hCompletionPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE,
NULL,<span class=cpp-literal>0</span>,<span class=cpp-literal>3</span>);
<span class=cpp-keyword>if</span> (!g_hCompletionPort)
{
fprintf (stdout, <span class=cpp-string>"g_hCompletionPort Create Failed\n"</span>);
<span class=cpp-keyword>return</span> FALSE;
}
<span class=cpp-comment>//Associate this socket to this I/O completion port </span>
CreateIoCompletionPort((HANDLE)g_hSocket,g_hCompletionPort,
(DWORD)g_hSocket,<span class=cpp-literal>3</span>);
<span class=cpp-comment>//</span>
<span class=cpp-comment>// Start off an asynchronous read on the socket.  </span>
<span class=cpp-comment>//  </span>
Overlapped.hEvent = g_hReadEvent;
Overlapped.Internal = <span class=cpp-literal>0</span>;
Overlapped.InternalHigh = <span class=cpp-literal>0</span>;
Overlapped.Offset = <span class=cpp-literal>0</span>;
Overlapped.OffsetHigh = <span class=cpp-literal>0</span>;
b = ReadFile ((HANDLE)g_hSocket,&amp;achInBuf,
<span class=cpp-keyword>sizeof</span>(achInBuf),&amp;nbytes,&amp;Overlapped);
<span class=cpp-keyword>if</span> (!b &amp;&amp; GetLastError () != ERROR_IO_PENDING)
{
fprintf (stdout, <span class=cpp-string>"ReadFile Failed\n"</span>);
<span class=cpp-keyword>return</span> FALSE;
}
<span class=cpp-keyword>return</span> TRUE;
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong>BOOL CreateWorkers (UINT dwNumberOfWorkers)</strong>
{
DWORD ThreadId;
HANDLE ThreadHandle;
DWORD i;
<span class=cpp-keyword>for</span> (i = <span class=cpp-literal>0</span>; i &lt; dwNumberOfWorkers; i++)
{
ThreadHandle = CreateThread (NULL,<span class=cpp-literal>0</span>,
WorkerThread,NULL,<span class=cpp-literal>0</span>,&amp;ThreadId);
<span class=cpp-keyword>if</span> (!ThreadHandle)
{
fprintf (stdout, <span class=cpp-string>"Create Worker Thread Failed\n"</span>);
<span class=cpp-keyword>return</span> FALSE;
}
CloseHandle (ThreadHandle);
}
<span class=cpp-keyword>return</span> TRUE;
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong>DWORD WINAPI WorkerThread (LPVOID WorkContext)</strong>
{
DWORD nSocket;
BOOL b;
OVERLAPPED ovl;
LPOVERLAPPED lpo=&amp;ovl;
DWORD nBytesRead=<span class=cpp-literal>0</span>;
DWORD nBytesToBeRead;
UCHAR ReadBuffer[BUFSIZE];
LPVOID lpMsgBuf;
memset(&amp;ReadBuffer,<span class=cpp-literal>0</span>,BUFSIZE);
<span class=cpp-keyword>for</span> (;;)
{
b = GetQueuedCompletionStatus(g_hCompletionPort,
&amp;nBytesToBeRead,&amp;nSocket,&amp;lpo,INFINITE);
<span class=cpp-keyword>if</span> (b || lpo)
{
<span class=cpp-keyword>if</span> (b)
{
<span class=cpp-comment>// </span>
<span class=cpp-comment>// Determine how long a response was desired by the client. </span>
<span class=cpp-comment>// </span>
OVERLAPPED ol;
ol.hEvent = g_hReadEvent;
ol.Offset = <span class=cpp-literal>0</span>;
ol.OffsetHigh = <span class=cpp-literal>0</span>;
b = ReadFile ((HANDLE)nSocket,&amp;ReadBuffer,
nBytesToBeRead,&amp;nBytesRead,&amp;ol);
<span class=cpp-keyword>if</span> (!b )
{
DWORD dwErrCode = GetLastError();
<span class=cpp-keyword>if</span>( dwErrCode != ERROR_IO_PENDING )
{
<span class=cpp-comment>// something has gone wrong here... </span>
printf(<span class=cpp-string>"Something has gone
wrong:Error code - %d\n"</span>,dwErrCode );
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dwErrCode,
MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT),<span class=cpp-comment>// Default language</span>
(LPTSTR) &amp;lpMsgBuf, <span class=cpp-literal>0</span>, NULL);
OutputDebugString((LPCTSTR)lpMsgBuf);
<span class=cpp-comment>//Free the buffer. </span>
LocalFree(lpMsgBuf );
}
<span class=cpp-keyword>else</span> <span class=cpp-keyword>if</span>( dwErrCode == ERROR_IO_PENDING )
{
<span class=cpp-comment>// I had to do this for my UDP sample </span>
<span class=cpp-comment>//Never did for my TCP servers </span>
WaitForSingleObject(ol.hEvent,INFINITE);
HandleIncomingData(ReadBuffer);
}
}
<span class=cpp-keyword>else</span>
{
HandleIncomingData(ReadBuffer);
}
<span class=cpp-keyword>continue</span>;
}
<span class=cpp-keyword>else</span>
{
fprintf (stdout, <span class=cpp-string>"WorkThread Wait Failed\n"</span>);
<span class=cpp-comment>//exit (1); </span>
}
}
<span class=cpp-keyword>return</span> <span class=cpp-literal>1</span>;
}
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
<strong>BOOL HandleIncomingData( UCHAR* pBuf)</strong>
{
InterlockedIncrement(&amp;nCount);
SYSTEMTIME *lpstSysTime;
lpstSysTime = (SYSTEMTIME *)(pBuf);
printf(<span class=cpp-string>"[%d]UTC Time %02d:%02d:%02d:%03d on %02d-%02d-%d \n"</span>,nCount,
lpstSysTime-&gt;wHour, lpstSysTime-&gt;wMinute,
lpstSysTime-&gt;wSecond, lpstSysTime-&gt;wMilliseconds,
lpstSysTime-&gt;wMonth, lpstSysTime-&gt;wDay, lpstSysTime-&gt;wYear);
memset(&amp;pBuf,<span class=cpp-literal>0</span>,BUFSIZE);
<span class=cpp-comment>//just making sure that i am not showing stale data </span>
<span class=cpp-keyword>return</span> TRUE;
}
<span class=cpp-comment>//-----------------------------------------------------------------</span>
main ()
{
<span class=cpp-comment>//You can modify your program to take some arguments for port number </span>
<span class=cpp-comment>//and multicast group address here </span>
printf(<span class=cpp-string>"\n***************************************\n"</span>);
printf(<span class=cpp-string>"Group IP address: %s\n"</span>,achMCAddr);
printf(<span class=cpp-string>"Port number : %d\n"</span>,nPort);
printf(<span class=cpp-string>"\n***************************************\n"</span>);
<span class=cpp-comment>//Initialize winsock 2 </span>
InitWinsock2();
<span class=cpp-comment>//We want to keep the main thread running </span>
HANDLE hWait2Exit = CreateEvent(NULL,FALSE,TRUE,<span class=cpp-string>"MCLIENT"</span>);
ResetEvent(hWait2Exit );
<span class=cpp-comment>//This OVERLAPPED event </span>
g_hReadEvent = CreateEvent(NULL,TRUE,TRUE,NULL);
<span class=cpp-comment>// </span>
<span class=cpp-comment>// try to get timing more accurate... Avoid context </span>
<span class=cpp-comment>// switch that could occur when threads are released </span>
<span class=cpp-comment>// </span>
SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
<span class=cpp-keyword>if</span> (!CreateNetConnections ())
{
printf(<span class=cpp-string>"Error condition @ CreateNetConnections , exiting\n"</span>);
<span class=cpp-keyword>return</span> <span class=cpp-literal>1</span>;
}
<span class=cpp-keyword>if</span> (!CreateWorkers (<span class=cpp-literal>5</span>))
{
printf(<span class=cpp-string>"Error condition @CreateWorkers, exiting\n"</span>);
<span class=cpp-keyword>return</span> <span class=cpp-literal>1</span>;
}
WaitForSingleObject(hWait2Exit,INFINITE);
UnInitWinsock2();
<span class=cpp-keyword>return</span> <span class=cpp-literal>1</span>;
}</pre>
<!-- Article Ends -->
<script src="/script/togglePre.js" type=text/javascript></script>
<h2>&nbsp;</h2>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30232.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 12:56 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30232.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows Socket五种I/O模型——代码全攻略</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30228.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 04:14:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30228.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30228.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30228.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30228.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30228.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 如果你想在Windows平台上构建服务器应用，那么I/O模型是你必须考虑的。Windows操作系统提供了选择（Select）、异步选择（WSAAsyncSelect）、事件选择（WSAEventSelect）、重叠I/O（Overlapped I/O）和完成端口（Completion Port)共五种I/O模型。每一种模型均适用于一种特定的应用场景。程序员应该对自己的应用需求非常明确，而且综合考虑...&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2007/08/17/30228.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/30228.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 12:14 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30228.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Socket I/O模型全接触</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30227.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 04:10:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30227.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30227.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30227.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30227.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30227.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 本文简单介绍了当前Windows支持的各种Socket I/O模型，如果你发现其中存在什么错误请务必赐教。一：select模型 二：WSAAsyncSelect模型 三：WSAEventSelect模型 四：Overlapped I/O 事件通知模型 五：Overlapped I/O 完成例程模型 六：IOCP模型 老陈有一个在外地工作的女儿，不能经常回来，老陈和她通过信件联系。他们的信会...&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2007/08/17/30227.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/30227.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 12:10 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30227.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个简单的完成端口的例子</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30226.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 04:08:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30226.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30226.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30226.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30226.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30226.html</trackback:ping><description><![CDATA[一个简单的完成端口的例子： &nbsp; <br>&nbsp; // &nbsp; Module &nbsp; Name: &nbsp; iocmplt.cpp &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; Description: &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; This &nbsp; sample &nbsp; illustrates &nbsp; how &nbsp; to &nbsp; develop &nbsp; a &nbsp; simple &nbsp; echo &nbsp; server &nbsp; Winsock &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; application &nbsp; using &nbsp; the &nbsp; completeion &nbsp; port &nbsp; I/O &nbsp; model. &nbsp; This &nbsp; &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; sample &nbsp; is &nbsp; implemented &nbsp; as &nbsp; a &nbsp; console-style &nbsp; application &nbsp; and &nbsp; simply &nbsp; prints &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; messages &nbsp; when &nbsp; connections &nbsp; are &nbsp; established &nbsp; and &nbsp; removed &nbsp; from &nbsp; the &nbsp; server. &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; The &nbsp; application &nbsp; listens &nbsp; for &nbsp; TCP &nbsp; connections &nbsp; on &nbsp; port &nbsp; 5150 &nbsp; and &nbsp; accepts &nbsp; them &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; as &nbsp; they &nbsp; arrive. &nbsp; When &nbsp; this &nbsp; application &nbsp; receives &nbsp; data &nbsp; from &nbsp; a &nbsp; client, &nbsp; it &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; simply &nbsp; echos &nbsp; (this &nbsp; is &nbsp; why &nbsp; we &nbsp; call &nbsp; it &nbsp; an &nbsp; echo &nbsp; server) &nbsp; the &nbsp; data &nbsp; back &nbsp; in &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; it's &nbsp; original &nbsp; form &nbsp; until &nbsp; the &nbsp; client &nbsp; closes &nbsp; the &nbsp; connection. &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; Compile: &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; cl &nbsp; -o &nbsp; iocmplt &nbsp; iocmplt.cpp &nbsp; ws2_32.lib &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; Command &nbsp; Line &nbsp; Options: &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; iocmplt.exe &nbsp; &nbsp; <br>&nbsp; // &nbsp; <br>&nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; Note: &nbsp; There &nbsp; are &nbsp; no &nbsp; command &nbsp; line &nbsp; options &nbsp; for &nbsp; this &nbsp; sample. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; #include &nbsp; &lt;winsock2.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;windows.h&gt; &nbsp; <br>&nbsp; #include &nbsp; &lt;stdio.h&gt; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; #define &nbsp; PORT &nbsp; 5150 &nbsp; <br>&nbsp; #define &nbsp; DATA_BUFSIZE &nbsp; 8192 &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; typedef &nbsp; struct &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; OVERLAPPED &nbsp; Overlapped; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; WSABUF &nbsp; DataBuf; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; CHAR &nbsp; Buffer[DATA_BUFSIZE]; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; BytesSEND; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; BytesRECV; &nbsp; <br>&nbsp; } &nbsp; PER_IO_OPERATION_DATA, &nbsp; * &nbsp; LPPER_IO_OPERATION_DATA; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; typedef &nbsp; struct &nbsp; &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; SOCKET &nbsp; Socket; &nbsp; <br>&nbsp; } &nbsp; PER_HANDLE_DATA, &nbsp; * &nbsp; LPPER_HANDLE_DATA; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; DWORD &nbsp; WINAPI &nbsp; ServerWorkerThread(LPVOID &nbsp; CompletionPortID); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; void &nbsp; main(void) &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; SOCKADDR_IN &nbsp; InternetAddr; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; SOCKET &nbsp; Listen; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; SOCKET &nbsp; Accept; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; HANDLE &nbsp; CompletionPort; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; SYSTEM_INFO &nbsp; SystemInfo; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; LPPER_HANDLE_DATA &nbsp; PerHandleData; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; LPPER_IO_OPERATION_DATA &nbsp; PerIoData; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; int &nbsp; i; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; RecvBytes; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; Flags; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; ThreadID; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; WSADATA &nbsp; wsaData; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; Ret; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((Ret &nbsp; = &nbsp; WSAStartup(0x0202, &nbsp; &amp;wsaData)) &nbsp; != &nbsp; 0) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("WSAStartup &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; Ret); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Setup &nbsp; an &nbsp; I/O &nbsp; completion &nbsp; port. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((CompletionPort &nbsp; = &nbsp; CreateIoCompletionPort(INVALID_HANDLE_VALUE, &nbsp; NULL, &nbsp; 0, &nbsp; 0)) &nbsp; == &nbsp; NULL) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf( &nbsp; "CreateIoCompletionPort &nbsp; failed &nbsp; with &nbsp; error: &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Determine &nbsp; how &nbsp; many &nbsp; processors &nbsp; are &nbsp; on &nbsp; the &nbsp; system. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; GetSystemInfo(&amp;SystemInfo); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Create &nbsp; worker &nbsp; threads &nbsp; based &nbsp; on &nbsp; the &nbsp; number &nbsp; of &nbsp; processors &nbsp; available &nbsp; on &nbsp; the &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; system. &nbsp; Create &nbsp; two &nbsp; worker &nbsp; threads &nbsp; for &nbsp; each &nbsp; processor. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; for(i &nbsp; = &nbsp; 0; &nbsp; i &nbsp; &lt; &nbsp; SystemInfo.dwNumberOfProcessors &nbsp; * &nbsp; 2; &nbsp; i++) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HANDLE &nbsp; ThreadHandle; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Create &nbsp; a &nbsp; server &nbsp; worker &nbsp; thread &nbsp; and &nbsp; pass &nbsp; the &nbsp; completion &nbsp; port &nbsp; to &nbsp; the &nbsp; thread. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((ThreadHandle &nbsp; = &nbsp; CreateThread(NULL, &nbsp; 0, &nbsp; ServerWorkerThread, &nbsp; CompletionPort, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0, &nbsp; &amp;ThreadID)) &nbsp; == &nbsp; NULL) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("CreateThread() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Close &nbsp; the &nbsp; thread &nbsp; handle &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CloseHandle(ThreadHandle); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; <br>
<div class=ReplysRegion id=ReplysRegion_33875633>&nbsp;<br>// &nbsp; Create &nbsp; a &nbsp; listening &nbsp; socket &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((Listen &nbsp; = &nbsp; WSASocket(AF_INET, &nbsp; SOCK_STREAM, &nbsp; 0, &nbsp; NULL, &nbsp; 0, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WSA_FLAG_OVERLAPPED)) &nbsp; == &nbsp; INVALID_SOCKET) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("WSASocket() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; InternetAddr.sin_family &nbsp; = &nbsp; AF_INET; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; InternetAddr.sin_addr.s_addr &nbsp; = &nbsp; htonl(INADDR_ANY); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; InternetAddr.sin_port &nbsp; = &nbsp; htons(PORT); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (bind(Listen, &nbsp; (PSOCKADDR) &nbsp; &amp;InternetAddr, &nbsp; sizeof(InternetAddr)) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("bind() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Prepare &nbsp; socket &nbsp; for &nbsp; listening &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (listen(Listen, &nbsp; 5) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("listen() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; // &nbsp; Accept &nbsp; connections &nbsp; and &nbsp; assign &nbsp; to &nbsp; the &nbsp; completion &nbsp; port. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; while(TRUE) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((Accept &nbsp; = &nbsp; WSAAccept(Listen, &nbsp; NULL, &nbsp; NULL, &nbsp; NULL, &nbsp; 0)) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("WSAAccept() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Create &nbsp; a &nbsp; socket &nbsp; information &nbsp; structure &nbsp; to &nbsp; associate &nbsp; with &nbsp; the &nbsp; socket &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((PerHandleData &nbsp; = &nbsp; (LPPER_HANDLE_DATA) &nbsp; GlobalAlloc(GPTR, &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sizeof(PER_HANDLE_DATA))) &nbsp; == &nbsp; NULL) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("GlobalAlloc() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Associate &nbsp; the &nbsp; accepted &nbsp; socket &nbsp; with &nbsp; the &nbsp; original &nbsp; completion &nbsp; port. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("Socket &nbsp; number &nbsp; %d &nbsp; connected\n", &nbsp; Accept); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerHandleData-&gt;Socket &nbsp; = &nbsp; Accept; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (CreateIoCompletionPort((HANDLE)Accept, &nbsp; CompletionPort, &nbsp; (DWORD) &nbsp; PerHandleData, &nbsp; &nbsp; 0) &nbsp; == &nbsp; NULL) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("CreateIoCompletionPort &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Create &nbsp; per &nbsp; I/O &nbsp; socket &nbsp; information &nbsp; structure &nbsp; to &nbsp; associate &nbsp; with &nbsp; the &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; WSARecv &nbsp; call &nbsp; below. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; ((PerIoData &nbsp; = &nbsp; (LPPER_IO_OPERATION_DATA) &nbsp; GlobalAlloc(GPTR, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sizeof(PER_IO_OPERATION_DATA))) &nbsp; == &nbsp; NULL) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("GlobalAlloc() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), &nbsp; sizeof(OVERLAPPED)); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesSEND &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesRECV &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.len &nbsp; = &nbsp; DATA_BUFSIZE; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.buf &nbsp; = &nbsp; PerIoData-&gt;Buffer; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Flags &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (WSARecv(Accept, &nbsp; &amp;(PerIoData-&gt;DataBuf), &nbsp; 1, &nbsp; &amp;RecvBytes, &nbsp; &amp;Flags, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;(PerIoData-&gt;Overlapped), &nbsp; NULL) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (WSAGetLastError() &nbsp; != &nbsp; ERROR_IO_PENDING) &nbsp; <br>&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; printf("WSARecv() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; <br>&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; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; DWORD &nbsp; WINAPI &nbsp; ServerWorkerThread(LPVOID &nbsp; CompletionPortID) &nbsp; <br>&nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; HANDLE &nbsp; CompletionPort &nbsp; = &nbsp; (HANDLE) &nbsp; CompletionPortID; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; BytesTransferred; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; LPOVERLAPPED &nbsp; Overlapped; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; LPPER_HANDLE_DATA &nbsp; PerHandleData; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; LPPER_IO_OPERATION_DATA &nbsp; PerIoData; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; SendBytes, &nbsp; RecvBytes; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; DWORD &nbsp; Flags; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; while(TRUE) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (GetQueuedCompletionStatus(CompletionPort, &nbsp; &amp;BytesTransferred, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (LPDWORD)&amp;PerHandleData, &nbsp; (LPOVERLAPPED &nbsp; *) &nbsp; &amp;PerIoData, &nbsp; INFINITE) &nbsp; == &nbsp; 0) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("GetQueuedCompletionStatus &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; GetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; First &nbsp; check &nbsp; to &nbsp; see &nbsp; if &nbsp; an &nbsp; error &nbsp; has &nbsp; occured &nbsp; on &nbsp; the &nbsp; socket &nbsp; and &nbsp; if &nbsp; so &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; then &nbsp; close &nbsp; the &nbsp; socket &nbsp; and &nbsp; cleanup &nbsp; the &nbsp; SOCKET_INFORMATION &nbsp; structure &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; associated &nbsp; with &nbsp; the &nbsp; socket. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (BytesTransferred &nbsp; == &nbsp; 0) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf("Closing &nbsp; socket &nbsp; %d\n", &nbsp; PerHandleData-&gt;Socket); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (closesocket(PerHandleData-&gt;Socket) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&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; printf("closesocket() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GlobalFree(PerHandleData); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; GlobalFree(PerIoData); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Check &nbsp; to &nbsp; see &nbsp; if &nbsp; the &nbsp; BytesRECV &nbsp; field &nbsp; equals &nbsp; zero. &nbsp; If &nbsp; this &nbsp; is &nbsp; so, &nbsp; then &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; this &nbsp; means &nbsp; a &nbsp; WSARecv &nbsp; call &nbsp; just &nbsp; completed &nbsp; so &nbsp; update &nbsp; the &nbsp; BytesRECV &nbsp; field &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; with &nbsp; the &nbsp; BytesTransferred &nbsp; value &nbsp; from &nbsp; the &nbsp; completed &nbsp; WSARecv() &nbsp; call. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (PerIoData-&gt;BytesRECV &nbsp; == &nbsp; 0) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesRECV &nbsp; = &nbsp; BytesTransferred; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesSEND &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesSEND &nbsp; += &nbsp; BytesTransferred; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (PerIoData-&gt;BytesRECV &nbsp; &gt; &nbsp; PerIoData-&gt;BytesSEND) &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Post &nbsp; another &nbsp; WSASend() &nbsp; request. &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Since &nbsp; WSASend() &nbsp; is &nbsp; not &nbsp; gauranteed &nbsp; to &nbsp; send &nbsp; all &nbsp; of &nbsp; the &nbsp; bytes &nbsp; requested, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; continue &nbsp; posting &nbsp; WSASend() &nbsp; calls &nbsp; until &nbsp; all &nbsp; received &nbsp; bytes &nbsp; are &nbsp; sent. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), &nbsp; sizeof(OVERLAPPED)); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.buf &nbsp; = &nbsp; PerIoData-&gt;Buffer &nbsp; + &nbsp; PerIoData-&gt;BytesSEND; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.len &nbsp; = &nbsp; PerIoData-&gt;BytesRECV &nbsp; - &nbsp; PerIoData-&gt;BytesSEND; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (WSASend(PerHandleData-&gt;Socket, &nbsp; &amp;(PerIoData-&gt;DataBuf), &nbsp; 1, &nbsp; &amp;SendBytes, &nbsp; 0, &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;(PerIoData-&gt;Overlapped), &nbsp; NULL) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&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; if &nbsp; (WSAGetLastError() &nbsp; != &nbsp; ERROR_IO_PENDING) &nbsp; <br>&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; printf("WSASend() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &nbsp; 0; &nbsp; <br>&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; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;BytesRECV &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Now &nbsp; that &nbsp; there &nbsp; are &nbsp; no &nbsp; more &nbsp; bytes &nbsp; to &nbsp; send &nbsp; post &nbsp; another &nbsp; WSARecv() &nbsp; request. &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Flags &nbsp; = &nbsp; 0; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ZeroMemory(&amp;(PerIoData-&gt;Overlapped), &nbsp; sizeof(OVERLAPPED)); &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.len &nbsp; = &nbsp; DATA_BUFSIZE; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PerIoData-&gt;DataBuf.buf &nbsp; = &nbsp; PerIoData-&gt;Buffer; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if &nbsp; (WSARecv(PerHandleData-&gt;Socket, &nbsp; &amp;(PerIoData-&gt;DataBuf), &nbsp; 1, &nbsp; &amp;RecvBytes, &nbsp; &amp;Flags, &nbsp; &amp;(PerIoData-&gt;Overlapped), &nbsp; NULL) &nbsp; == &nbsp; SOCKET_ERROR) &nbsp; <br>&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; if &nbsp; (WSAGetLastError() &nbsp; != &nbsp; ERROR_IO_PENDING) &nbsp; <br>&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; printf("WSARecv() &nbsp; failed &nbsp; with &nbsp; error &nbsp; %d\n", &nbsp; WSAGetLastError()); &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return &nbsp; 0; &nbsp; <br>&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; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; <br>&nbsp; }&nbsp;&nbsp; <br></div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30226.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 12:08 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30226.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】Normal IOCP 的使用 </title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30225.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 04:01:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30225.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30225.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30225.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30225.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30225.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1、创建IOCPACE_WIN32_Proactor::ACE_WIN32_Proactor (size_t number_of_threads,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nb...&nbsp;&nbsp;<a href='http://www.cppblog.com/niewenlong/archive/2007/08/17/30225.html'>阅读全文</a><img src ="http://www.cppblog.com/niewenlong/aggbug/30225.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 12:01 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30225.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IOCP的例子 </title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30224.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 03:55:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30224.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30224.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30224.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30224.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30224.html</trackback:ping><description><![CDATA[<p>以前在书上看过了IOCP，不过一直都没有写过代码。现在写的时候，着时对很多问题摸不着头脑。不过好在CSDN上有许多的对于IOCP问题的讨论帖，让我受益非浅啊，也把心中的一些迷茫解开了，下面给出的是可以运行的IOCP的C/S代码，自已试了在一个机器上开了一百来个客户端，跑起来暂时没出现问题（因为通信内容太简单了^-^)。</p>
<p>IOCP的三个函数：CreateIoCompletionPort、GetQueuedCompletionStatus、PostQueuedCompletionStatus；一个是用来创建想要的IOCP的HANDLE同时也是用来把我们想要的SOCKET绑定到这个HANDLE上，一个是获取IO这个HANDLE上对应的对列的状态，看有没有事件完成，一个是用来通知所有工作线程退出（这个函数我还没用到，关于这个功用是看资料上说的）。</p>
<p>我在写这个代码的时候，最主要的问题就是当通信完成了之后，是怎么样来判断是哪个SOCKET的哪个状态（SEND还是RECV）完成了。《WINDOWS网络编程》这本书里给的代码不是很全的哦，它的配套光盘又没有，不过好在CSDN里CB那块中有个朋友刚好帖出了这一章的代码。通过比较和一夜的思量，算是搞明白啦。主要的就是以下的数据：</p>
<p>1、在第二次CreateIoCompletionPort中，会传进去一个CompletionKey，这个就是要来关联到我们想要的SOCKET上的一些感兴趣的数据内容，当然最好是要一个SOCKET，也可以是其它，看自己程序的需要了。而通过GetQueueCompletionStatus的通过，就可以获得这些数据的地址了。</p>
<p>typedef struct _PER_HANDLE_DATA<br>{<br>&nbsp;&nbsp;&nbsp; SOCKET sock;<br>}PER_HANDLE_DATA,* LPPER_HANDLE_DATA;</p>
<p>2、第二个主要的数据结构就是这个了，现在真的是佩服当初设计这个结构的人啊（没办法，自己就是没想到这样利用法）。因为在POST操作（SEND或是RECV）是，都要一个OVERLAPPED，所以就把这个OVERLAPPED和要指明这次POST操作类型的代码OperationType（POST_SEND或POST_RECV）以及其它一些数据（比如接发收的缓冲）。这样子，在GetQueueCompletionStatus的时候，通过获取事件，也同时得到了OperationType和缓冲。这样，知道了通信类型，也得到了缓冲数据的缓冲区。这样就可以控制我们的通信了。</p>
<p>这个例子比较简单，没有复杂的数据处理过程（正在设计中，和大家交流交流）。用的是BCB的平台，不过写法上还是和VC里的一模一样的啊。</p>
<p>typedef struct _PER_IO_OPERATION_DATA<br>{<br>&nbsp;&nbsp;&nbsp; OVERLAPPED Overlapped;<br>&nbsp;&nbsp;&nbsp; WSABUF DataBuff[1];<br>&nbsp;&nbsp;&nbsp; char Buff[24];<br>&nbsp;&nbsp;&nbsp; BOOL OperationType;<br>}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;</p>
<p>简单的客户端：</p>
<p>//---------------------------------------------------------------------------</p>
<p>#pragma hdrstop<br>#include &lt;winsock2.h&gt;<br>#include &lt;stdio.h&gt;<br>#include &lt;iostream&gt;<br>using namespace std;<br>//---------------------------------------------------------------------------</p>
<p>#pragma argsused</p>
<p>SOCKET sockClient;<br>struct sockaddr_in addrServer;<br>char buf[24];<br>int n = 0;<br>int Init();</p>
<p>int main(int argc, char* argv[])<br>{<br>&nbsp;&nbsp;&nbsp; if(Init() != 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;</p>
<p>&nbsp;&nbsp;&nbsp; sockClient = socket(AF_INET,SOCK_STREAM,0);<br>&nbsp;&nbsp;&nbsp; if(sockClient == INVALID_SOCKET)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"socket 失败"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; memset(&amp;addrServer,0,sizeof(sockaddr_in));<br>&nbsp;&nbsp;&nbsp; addrServer.sin_family = AF_INET;<br>&nbsp;&nbsp;&nbsp; addrServer.sin_addr.s_addr = inet_addr("127.0.0.1");<br>&nbsp;&nbsp;&nbsp; addrServer.sin_port = htons(9090);<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"连接服务器..."&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; if(connect(sockClient,(const struct sockaddr *)&amp;addrServer,sizeof(sockaddr)) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"connect 失败"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"开始发送测试包"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; memset(buf,0,24);<br>&nbsp;&nbsp;&nbsp; while(true)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sprintf(buf,"第%d个包", n);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"发送："&lt;&lt;buf&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(send(sockClient,buf,strlen(buf),0) &lt;= 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"send失败,可能连接断开"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(buf,0,24);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //接收服务端应答<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(recv(sockClient,buf,24,0) &lt;= 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"recv失败,可能连接断开"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //break;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"服务器应答："&lt;&lt;buf&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(buf,0,24);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Sleep(200);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; n++;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; <br>theend:<br>&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp; getchar();<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br>//---------------------------------------------------------------------------<br>int Init()<br>{<br>&nbsp;&nbsp;&nbsp; WSAData wsaData;<br>&nbsp;&nbsp;&nbsp; if(WSAStartup(MAKEWORD(2,2),&amp;wsaData) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"WSAStartup失败"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"SOCKET版本不对"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>服务端。</p>
<p>//---------------------------------------------------------------------------</p>
<p>#pragma hdrstop</p>
<p>//---------------------------------------------------------------------------<br>#pragma argsused<br>#pragma comment(lib,"ws2_32.lib")<br>#include &lt;stdio.h&gt;<br>#include &lt;memory.h&gt;<br>#include &lt;winsock2.h&gt;<br>#include &lt;iostream&gt;<br>using namespace std;</p>
<p>#define RECV_POSTED 1001<br>#define SEND_POSTED 1002</p>
<p>int Init();</p>
<p>HANDLE hCompletionPort;<br>typedef struct _PER_HANDLE_DATA<br>{<br>&nbsp;&nbsp;&nbsp; SOCKET sock;<br>}PER_HANDLE_DATA,* LPPER_HANDLE_DATA;</p>
<p>typedef struct _PER_IO_OPERATION_DATA<br>{<br>&nbsp;&nbsp;&nbsp; OVERLAPPED Overlapped;<br>&nbsp;&nbsp;&nbsp; WSABUF DataBuff[1];<br>&nbsp;&nbsp;&nbsp; char Buff[24];<br>&nbsp;&nbsp;&nbsp; BOOL OperationType;<br>}PER_IO_OPERATION_DATA,* LPPER_IO_OPERATION_DATA;</p>
<p>DWORD WINAPI ServerWorkerThread(LPVOID CompletionPort);</p>
<p>int main(int argc, char* argv[])<br>{<br>&nbsp;&nbsp;&nbsp; LPPER_HANDLE_DATA perHandleData;<br>&nbsp;&nbsp;&nbsp; LPPER_IO_OPERATION_DATA ioperdata;<br>&nbsp;&nbsp;&nbsp; SYSTEM_INFO siSys;<br>&nbsp;&nbsp;&nbsp; SOCKET sockListen;<br>&nbsp;&nbsp;&nbsp; struct sockaddr_in addrLocal;<br>&nbsp;&nbsp;&nbsp; char buf[24];<br>&nbsp;&nbsp;&nbsp; int nRet = 0;<br>&nbsp;&nbsp;&nbsp; DWORD nThreadID;<br>&nbsp;&nbsp;&nbsp; SOCKET sockAccept;<br>&nbsp;&nbsp;&nbsp; DWORD dwFlags;<br>&nbsp;&nbsp;&nbsp; DWORD dwRecvBytes;<br>&nbsp;&nbsp;&nbsp; int nReuseAddr = 1;</p>
<p>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"初始环境..."&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; if(Init() != 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;</p>
<p>&nbsp;&nbsp;&nbsp; //创建一个IO完成端口<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"创建一个IO完成端口"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);<br>&nbsp;&nbsp;&nbsp; if(hCompletionPort == INVALID_HANDLE_VALUE)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"创建IO完成端口失败"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; //获取CPU数目<br>&nbsp;&nbsp;&nbsp; GetSystemInfo(&amp;siSys);<br>&nbsp;&nbsp;&nbsp; //创建一定数目的工作者线程，本例中以一个处理器一个线程搭配<br>&nbsp;&nbsp;&nbsp; for(int i = 0;i&lt;(int)siSys.dwNumberOfProcessors*2;i++)//NumberOfProcessors<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE hThread;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hThread = CreateThread(NULL,0,ServerWorkerThread,(LPVOID)hCompletionPort,0,&amp;nThreadID);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"创建工作者线程"&lt;&lt;i&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(hThread);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; //创建监听SOCKET<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"创建监听SOCKET"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; sockListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);<br>&nbsp;&nbsp;&nbsp; if(sockListen == SOCKET_ERROR)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"WSASocket错误"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if(setsockopt(sockListen,SOL_SOCKET,SO_REUSEADDR,(const char *)&amp;nReuseAddr,sizeof(int)) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"setsockopt错误"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; addrLocal.sin_family = AF_INET;<br>&nbsp;&nbsp;&nbsp; addrLocal.sin_addr.s_addr = htonl(INADDR_ANY);<br>&nbsp;&nbsp;&nbsp; addrLocal.sin_port = htons(9090);<br>&nbsp;&nbsp;&nbsp; if(bind(sockListen,(struct sockaddr *)&amp;addrLocal,sizeof(sockaddr_in)) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"bind错误"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int n = WSAGetLastError();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; //准备监听<br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;"准备监听"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp; if(listen(sockListen,5)!=0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"listen错误"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; goto theend;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; while(true)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //接收用户连接，被和完成端口关联<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sockAccept = WSAAccept(sockListen,NULL,NULL,NULL,0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perHandleData = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(perHandleData == NULL)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"socket number "&lt;&lt;sockAccept&lt;&lt;"接入"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; perHandleData-&gt;sock = sockAccept;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ioperdata = (LPPER_IO_OPERATION_DATA)malloc(sizeof(PER_IO_OPERATION_DATA));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; memset(&amp;(ioperdata-&gt;Overlapped),0,sizeof(OVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (ioperdata-&gt;DataBuff[0]).len = 24;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (ioperdata-&gt;DataBuff[0]).buf = ioperdata-&gt;Buff;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ioperdata-&gt;OperationType = RECV_POSTED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( ioperdata == NULL)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(perHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //关联<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"关联SOCKET和完成端口"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(CreateIoCompletionPort((HANDLE)sockAccept,hCompletionPort,(DWORD)perHandleData,1) == NULL)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;sockAccept&lt;&lt;"createiocompletionport错误"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(perHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(ioperdata);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //投递接收操作<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"投递接收操作"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(perHandleData-&gt;sock,ioperdata-&gt;DataBuff,1,&amp;dwRecvBytes,&amp;dwFlags,&amp;(ioperdata-&gt;Overlapped),NULL);<br>&nbsp;&nbsp;&nbsp; }<br>theend:<br>&nbsp;&nbsp;&nbsp; getchar();<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br>//---------------------------------------------------------------------------<br>int Init()<br>{<br>&nbsp;&nbsp;&nbsp; WSAData wsaData;<br>&nbsp;&nbsp;&nbsp; if(WSAStartup(MAKEWORD(2,2),&amp;wsaData) != 0)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"WSAStartup失败"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"SOCKET版本不对"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSACleanup();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return -1;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</p>
<p>DWORD WINAPI ServerWorkerThread(LPVOID CompletionPort)<br>{<br>&nbsp;&nbsp;&nbsp; HANDLE ComPort = (HANDLE)CompletionPort;<br>&nbsp;&nbsp;&nbsp; DWORD BytesTransferred;<br>&nbsp;&nbsp;&nbsp; LPOVERLAPPED Overlapped;<br>&nbsp;&nbsp;&nbsp; LPPER_HANDLE_DATA PerHandleData;<br>&nbsp;&nbsp;&nbsp; LPPER_IO_OPERATION_DATA PerIoData;<br>&nbsp;&nbsp;&nbsp; DWORD SendBytes,RecvBytes;<br>&nbsp;&nbsp;&nbsp; DWORD Flags;<br>&nbsp;&nbsp;&nbsp; BOOL bT;</p>
<p>&nbsp;&nbsp;&nbsp; while(TRUE)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //等待完成端口上SOCKET的完成<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"等待完成端口上SOCKET的完成"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bT = GetQueuedCompletionStatus(ComPort,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &amp;BytesTransferred,(LPDWORD)&amp;PerHandleData,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (LPOVERLAPPED *)&amp;PerIoData,INFINITE);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检查是否有错误产生<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(BytesTransferred == 0 &amp;&amp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (PerIoData-&gt;OperationType == RECV_POSTED ||<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;OperationType == SEND_POSTED))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //关闭SOCKET<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;PerHandleData-&gt;sock&lt;&lt;"SOCKET关闭"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(PerHandleData-&gt;sock);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(PerHandleData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; free(PerIoData);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //为请求服务<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(PerIoData-&gt;OperationType == RECV_POSTED)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"接收处理"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;PerHandleData-&gt;sock&lt;&lt;"SOCKET :"&lt;&lt;PerIoData-&gt;Buff&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //回应客户端<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory(PerIoData-&gt;Buff,24);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strcpy(PerIoData-&gt;Buff,"OK");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory((LPVOID)&amp;(PerIoData-&gt;Overlapped),sizeof(OVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuff[0].len = 2;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuff[0].buf = PerIoData-&gt;Buff;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;OperationType = SEND_POSTED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSASend(PerHandleData-&gt;sock,PerIoData-&gt;DataBuff,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,&amp;SendBytes,0,&amp;(PerIoData-&gt;Overlapped),NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else //if(PerIoData-&gt;OperationType == SEND_POSTED)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //发送时的处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;"发送处理"&lt;&lt;endl;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Flags = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory((LPVOID)&amp;(PerIoData-&gt;Overlapped),sizeof(OVERLAPPED));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ZeroMemory(PerIoData-&gt;Buff,24);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuff[0].len = 24;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;DataBuff[0].buf = PerIoData-&gt;Buff;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PerIoData-&gt;OperationType = RECV_POSTED;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(PerHandleData-&gt;sock,PerIoData-&gt;DataBuff,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1,&amp;RecvBytes,&amp;Flags,&amp;(PerIoData-&gt;Overlapped),NULL);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<br>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30224.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 11:55 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30224.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】Winsock完成端口编程与应用 </title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30222.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 03:46:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30222.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30222.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30222.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30222.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30222.html</trackback:ping><description><![CDATA[<p>通常要开发网络应用程序并不是一件轻松的事情，不过，实际上只要掌握几个关键的原则也就可以了——创建和连接一个套接字，尝试进行连接，然后收发数据。真正难的是要写出一个可以接纳少则一个，多则数千个连接的网络应用程序。本文将讨论如何通过Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的焦点在客户机/服务器模型的服务器这一方，当然，其中的许多要点对模型的双方都适用。 </p>
<p><font color=#009999><strong>API与响应规模</strong></font></p>
<p>通过Win32的重叠I/O机制，应用程序可以提请一项I/O操作，重叠的操作请求在后台完成，而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过，像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用，但是它们不能满足响应规模的需要。而完成端口机制是针对操作系统内部进行了优化，在Windows NT 和 Windows 2000上，使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。</p>
<p><font color=#009999><strong>完成端口</strong></font></p>
<p>一个完成端口其实就是一个通知队列，由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成，某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后，可以在任何时候与某个完成端口进行关联。</p>
<p>通常情况下，我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是，线程数量等于处理器的数量，不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作，以免线程阻塞。每个线程都将分到一定的CPU时间，在此期间该线程可以运行，然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作，操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说，前一个线程没有充分使用其时间片，当发生这样的情况时，应用程序应该准备其它线程来充分利用这些时间片。</p>
<p>完成端口的使用分为两步。首先创建完成端口，如以下代码所示：<br></p>
<pre>HANDLE    hIocp;
hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
(ULONG_PTR)0,
0);
if (hIocp == NULL) {
// Error
}
</pre>
<p>完成端口创建后，要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数，第一个参数FileHandle设为套接字的句柄，第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。<br>以下代码创建了一个套接字，并把它和前面创建的完成端口关联起来： </p>
<pre>SOCKET    s;
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
// Error
if (CreateIoCompletionPort((HANDLE)s,
hIocp,
(ULONG_PTR)0,
0) == NULL)
{
// Error
}
...
}
</pre>
<p><br>这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。注意，CreateIoCompletionPort()函数中的第三个参数用来设置一个与该套接字相关的&#8220;完成键(completion key)&#8221;(译者注：完成键可以是任何数据类型)。每当完成通知到来时，应用程序可以读取相应的完成键，因此，完成键可用来给套接字传递一些背景信息。 </p>
<p>在创建了完成端口、将一个或多个套接字与之相关联之后，我们就要创建若干个线程来处理完成通知。这些线程不断循环调用GetQueuedCompletionStatus ()函数并返回完成通知。</p>
<p>下面，我们先来看看应用程序如何跟踪这些重叠操作。当应用程序调用一个重叠操作函数时，要把指向一个overlapped结构的指针包括在其参数中。当操作完成后，我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不过，单是根据这个指针所指向的overlapped结构，应用程序并不能分辨究竟完成的是哪个操作。要实现对操作的跟踪，你可以自己定义一个OVERLAPPED结构，在其中加入所需的跟踪信息。</p>
<p>无论何时调用重叠操作函数时，总是会通过其lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如WSASend、 WSARecv等函数)。这就允许你为每一个重叠调用操作设置某些操作状态信息，当操作结束后，你可以通过GetQueuedCompletionStatus()函数获得你自定义结构的指针。注意OVERLAPPED字段不要求一定是这个扩展后的结构的第一个字段。当得到了指向OVERLAPPED结构的指针以后，可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针。</p>
<p>OVERLAPPED 结构的定义如下：</p>
<pre>typedef struct _OVERLAPPEDPLUS {
OVERLAPPED        ol;
SOCKET            s, sclient;
int               OpCode;
WSABUF            wbuf;
DWORD             dwBytes, dwFlags;
// 其它有用的信息
} OVERLAPPEDPLUS;
#define OP_READ     0
#define OP_WRITE    1
#define OP_ACCEPT   2
</pre>
<p>下面让我们来看看工作者线程的情况。<br><br>工作线程WorkerThread代码： </p>
<pre>DWORD WINAPI WorkerThread(LPVOID lpParam)
{
ULONG_PTR       *PerHandleKey;
OVERLAPPED      *Overlap;
OVERLAPPEDPLUS  *OverlapPlus,
*newolp;
DWORD           dwBytesXfered;
while (1)
{
ret = GetQueuedCompletionStatus(
hIocp,
&amp;dwBytesXfered,
(PULONG_PTR)&amp;PerHandleKey,
&amp;Overlap,
INFINITE);
if (ret == 0)
{
// Operation failed
continue;
}
OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol);
switch (OverlapPlus-&gt;OpCode)
{
case OP_ACCEPT:
// Client socket is contained in OverlapPlus.sclient
// Add client to completion port
CreateIoCompletionPort(
(HANDLE)OverlapPlus-&gt;sclient,
hIocp,
(ULONG_PTR)0,
0);
//  Need a new OVERLAPPEDPLUS structure
//  for the newly accepted socket. Perhaps
//  keep a look aside list of free structures.
newolp = AllocateOverlappedPlus();
if (!newolp)
{
// Error
}
newolp-&gt;s = OverlapPlus-&gt;sclient;
newolp-&gt;OpCode = OP_READ;
// This function prepares the data to be sent
PrepareSendBuffer(&amp;newolp-&gt;wbuf);
ret = WSASend(
newolp-&gt;s,
&amp;newolp-&gt;wbuf,
1,
&amp;newolp-&gt;dwBytes,
0,
&amp;newolp.ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
// Put structure in look aside list for later use
FreeOverlappedPlus(OverlapPlus);
// Signal accept thread to issue another AcceptEx
SetEvent(hAcceptThread);
break;
case OP_READ:
// Process the data read
// ...
// Repost the read if necessary, reusing the same
// receive buffer as before
memset(&amp;OverlapPlus-&gt;ol, 0, sizeof(OVERLAPPED));
ret = WSARecv(
OverlapPlus-&gt;s,
&amp;OverlapPlus-&gt;wbuf,
1,
&amp;OverlapPlus-&gt;dwBytes,
&amp;OverlapPlus-&gt;dwFlags,
&amp;OverlapPlus-&gt;ol,
NULL);
if (ret == SOCKET_ERROR)
{
if (WSAGetLastError() != WSA_IO_PENDING)
{
// Error
}
}
break;
case OP_WRITE:
// Process the data sent, etc.
break;
} // switch
} // while
}  // WorkerThread
</pre>
<p>其中每句柄键(PerHandleKey)变量的内容，是在把完成端口与套接字进行关联时所设置的完成键参数；Overlap参数返回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUS结构的指针。 </p>
<p>要记住，如果重叠操作调用失败时(也就是说，返回值是SOCKET_ERROR，并且错误原因不是WSA_IO_PENDING)，那么完成端口将不会收到任何完成通知。如果重叠操作调用成功，或者发生原因是WSA_IO_PENDING的错误时，完成端口将总是能够收到完成通知。<br><br><strong><font color=#009999>Windows NT和Windows 2000的套接字架构</font></strong><br><br>对于开发大响应规模的Winsock应用程序而言，对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。下图是Windows 2000中的Winsock架构：<br><img height=231 src="http://www.vckbase.com/document/journal/vckbase50/images/winsockfig03.gif" width=200></p>
<p>与其它类型操作系统不同，Windows NT和Windows 2000的传输协议没有一种风格像套接字那样的、可以和应用程序直接交谈的界面，而是采用了一种更为底层的API，叫做传输驱动程序界面(Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和缓冲区管理，以便向应用程序提供套接字仿真(在AFD.SYS文件中实现)，同时负责与底层传输驱动程序对话。</p>
<p><strong><font color=#009999>谁来负责管理缓冲区？</font></strong><br><br>正如上面所说的，应用程序通过Winsock来和传输协议驱动程序交谈，而AFD.SYS负责为应用程序进行缓冲区管理。也就是说，当应用程序调用send()或WSASend()函数来发送数据时，AFD.SYS将把数据拷贝进它自己的内部缓冲区(取决于SO_SNDBUF设定值)，然后send()或WSASend()函数立即返回。也可以这么说，AFD.SYS在后台负责把数据发送出去。不过，如果应用程序要求发出的数据超过了SO_SNDBUF设定的缓冲区大小，那么WSASend()函数会阻塞，直至所有数据发送完毕。</p>
<p>从远程客户端接收数据的情况也类似。只要不用从应用程序那里接收大量的数据，而且没有超出SO_RCVBUF设定的值，AFD.SYS将把数据先拷贝到其内部缓冲区中。当应用程序调用recv()或WSARecv()函数时，数据将从内部缓冲拷贝到应用程序提供的缓冲区。</p>
<p>多数情况下，这样的架构运行良好，特别在是应用程序采用传统的套接字下非重叠的send()和receive()模式编写的时候。不过程序员要小心的是，尽管可以通过setsockopt()这个API来把SO_SNDBUF和SO_RCVBUF选项值设成0(关闭内部缓冲区)，但是程序员必须十分清楚把AFD.SYS的内部缓冲区关掉会造成什么后果，避免收发数据时有关的缓冲区拷贝可能引起的系统崩溃。</p>
<p>举例来说，一个应用程序通过设定SO_SNDBUF为0把缓冲区关闭，然后发出一个阻塞send()调用。在这样的情况下，系统内核会把应用程序的缓冲区锁定，直到接收方确认收到了整个缓冲区后send()调用才返回。似乎这是一种判定你的数据是否已经为对方全部收到的简洁的方法，实际上却并非如此。想想看，即使远端TCP通知数据已经收到，其实也根本不代表数据已经成功送给客户端应用程序，比如对方可能发生资源不足的情况，导致AFD.SYS不能把数据拷贝给应用程序。另一个更要紧的问题是，在每个线程中每次只能进行一次发送调用，效率极其低下。</p>
<p>把SO_RCVBUF设为0，关闭AFD.SYS的接收缓冲区也不能让性能得到提升，这只会迫使接收到的数据在比Winsock更低的层次进行缓冲，当你发出receive调用时，同样要进行缓冲区拷贝，因此你本来想避免缓冲区拷贝的阴谋不会得逞。</p>
<p>现在我们应该清楚了，关闭缓冲区对于多数应用程序而言并不是什么好主意。只要要应用程序注意随时在某个连接上保持几个WSARecvs重叠调用，那么通常没有必要关闭接收缓冲区。如果AFD.SYS总是有由应用程序提供的缓冲区可用，那么它将没有必要使用内部缓冲区。</p>
<p>高性能的服务器应用程序可以关闭发送缓冲区，同时不会损失性能。不过，这样的应用程序必须十分小心，保证它总是发出多个重叠发送调用，而不是等待某个重叠发送结束了才发出下一个。如果应用程序是按一个发完再发下一个的顺序来操作，那浪费掉两次发送中间的空档时间，总之是要保证传输驱动程序在发送完一个缓冲区后，立刻可以转向另一个缓冲区。</p>
<p><font color=#009999><strong>资源的限制条件</strong></font></p>
<p>在设计任何服务器应用程序时，其强健性是主要的目标。也就是说，</p>
<p>你的应用程序要能够应对任何突发的问题，例如并发客户请求数达到峰值、可用内存临时出现不足、以及其它短时间的现象。这就要求程序的设计者注意Windows NT和2000系统下的资源限制条件的问题，从容地处理突发性事件。</p>
<p>你可以直接控制的、最基本的资源就是网络带宽。通常，使用用户数据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制，以最大限度地减少包的丢失。然而，在使用TCP连接时，服务器必须十分小心地控制好，防止网络带宽过载超过一定的时间，否则将需要重发大量的包或造成大量连接中断。关于带宽管理的方法应根据不同的应用程序而定，这超出了本文讨论的范围。</p>
<p>虚拟内存的使用也必须很小心地管理。通过谨慎地申请和释放内存，或者应用lookaside lists(一种高速缓存)技术来重新使用已分配的内存，将有助于控制服务器应用程序的内存开销(原文为&#8220;让服务器应用程序留下的脚印小一点&#8221;)，避免操作系统频繁地将应用程序申请的物理内存交换到虚拟内存中(原文为&#8220;让操作系统能够总是把更多的应用程序地址空间更多地保留在内存中&#8221;)。你也可以通过SetWorkingSetSize()这个Win32 API让操作系统分配给你的应用程序更多的物理内存。</p>
<p>在使用Winsock时还可能碰到另外两个非直接的资源不足情况。一个是被锁定的内存页面的极限。如果你把AFD.SYS的缓冲关闭，当应用程序收发数据时，应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内核驱动程序需要访问这些内存，在此期间这些页面不能交换出去。如果操作系统需要给其它应用程序分配一些可分页的物理内存，而又没有足够的内存时就会发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让系统崩溃的程序。也就是说，你的程序锁定内存时，不要超出系统规定的内存分页极限。</p>
<p>在Windows NT和2000系统上，所有应用程序总共可以锁定的内存大约是物理内存的1/8(不过这只是一个大概的估计，不是你计算内存的依据)。如果你的应用程序不注意这一点，当你的发出太多的重叠收发调用，而且I/O没来得及完成时，就可能偶尔发生ERROR_INSUFFICIENT_RESOURCES的错误。在这种情况下你要避免过度锁定内存。同时要注意，系统会锁定包含你的缓冲区所在的整个内存页面，因此缓冲区靠近页边界时是有代价的(译者理解，缓冲区如果正好超过页面边界，那怕是1个字节，超出的这个字节所在的页面也会被锁定)。</p>
<p>另外一个限制是你的程序可能会遇到系统未分页池资源不足的情况。所谓未分页池是一块永远不被交换出去的内存区域，这块内存用来存储一些供各种内核组件访问的数据，其中有的内核组件是不能访问那些被交换出去的页面空间的。Windows NT和2000的驱动程序能够从这个特定的未分页池分配内存。</p>
<p>当应用程序创建一个套接字(或者是类似的打开某个文件)时，内核会从未分页池中分配一定数量的内存，而且在绑定、连接套接字时，内核又会从未分页池中再分配一些内存。当你注意观察这种行为时你将发现，如果你发出某些I/O请求时(例如收发数据)，你会从未分页池里再分配多一些内存(比如要追踪某个待决的I/O操作，你可能需要给这个操作添加一个自定义结构，如前文所提及的)。最后这就可能会造成一定的问题，操作系统会限制未分页内存的用量。</p>
<p>在Windows NT和2000这两种操作系统上，给每个连接分配的未分页内存的具体数量是不同的，未来版本的Windows很可能也不同。为了使应用程序的生命期更长，你就不应该计算对未分页池内存的具体需求量。</p>
<p>你的程序必须防止消耗到未分页池的极限。当系统中未分页池剩余空间太小时，某些与你的应用程序毫无关系的内核驱动就会发疯，甚至造成系统崩溃，特别是当系统中有第三方设备或驱动程序时，更容易发生这样的惨剧(而且无法预测)。同时你还要记住，同一台电脑上还可能运行有其它同样消耗未分页池的其它应用程序，因此在设计你的应用程序时，对资源量的预估要特别保守和谨慎。</p>
<p>处理资源不足的问题是十分复杂的，因为发生上述情况时你不会收到特别的错误代码，通常你只能收到一般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理这些错误，首先，把你的应用程序工作配置调整到合理的最大值(译者注：所谓工作配置，是指应用程序各部分运行中所需的内存用量，请参考 <a href="http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp" target=_blank><u><font color=#0000ff>http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp</font></u></a> ，关于内存优化，译者另有译文)，如果错误继续出现，那么注意检查是否是网络带宽不足的问题。之后，请确认你没有同时发出太多的收发调用。最后，如果还是收到资源不足的错误，那就很可能是遇到了未分页内存池不足的问题了。要释放未分页内存池空间，请关闭应用程序中相当部分的连接，等待系统自行渡过和修正这个瞬时的错误。<br><br><strong><font color=#009999>接受连接请求</font></strong><br><br>服务器要做的最普通的事情之一就是接受来自客户端的连接请求。在套接字上使用重叠I/O接受连接的惟一API就是AcceptEx()函数。有趣的是，通常的同步接受函数accept()的返回值是一个新的套接字，而AcceptEx()函数则需要另外一个套接字作为它的参数之一。这是因为AcceptEx()是一个重叠操作，所以你需要事先创建一个套接字(但不要绑定或连接它)，并把这个套接字通过参数传给AcceptEx()。以下是一小段典型的使用AcceptEx()的伪代码： </p>
<pre>do {
-等待上一个 AcceptEx 完成
-创建一个新套接字并与完成端口进行关联
-设置背景结构等等
-发出一个 AcceptEx 请求
}while(TRUE);</pre>
<p>作为一个高响应能力的服务器，它必须发出足够的AcceptEx调用，守候着，一旦出现客户端连接请求就立刻响应。至于发出多少个AcceptEx才够，就取决于你的服务器程序所期待的通信交通类型。比如，如果进入连接率高的情况(因为连接持续时间较短，或者出现交通高峰)，那么所需要守候的AcceptEx当然要比那些偶尔进入的客户端连接的情况要多。聪明的做法是，由应用程序来分析交通状况，并调整AcceptEx守候的数量，而不是固定在某个数量上。 </p>
<p>对于Windows2000，Winsock提供了一些机制，帮助你判定AcceptEx的数量是否足够。这就是，在创建监听套接字时创建一个事件，通过WSAEventSelect()这个API并注册FD_ACCEPT事件通知来把套接字和这个事件关联起来。一旦系统收到一个连接请求，如果系统中没有AcceptEx()正在等待接受连接，那么上面的事件将收到一个信号。通过这个事件，你就可以判断你有没有发出足够的AcceptEx()，或者检测出一个非正常的客户请求(下文述)。这种机制对Windows NT 4.0不适用。</p>
<p>使用AcceptEx()的一大好处是，你可以通过一次调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两件事情。也就是说，如果客户端在发出连接的同时传输数据，你的AcceptEx()调用在连接创建并接收了客户端数据后就可以立刻返回。这样可能是很有用的，但是也可能会引发问题，因为AcceptEx()必须等全部客户端数据都收到了才返回。具体来说，如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数，那么AcceptEx()不再是一项原子型的操作，而是分成了两步：接受客户连接，等待接收数据。当缺少一种机制来通知你的应用程序所发生的这种情况：&#8220;连接已经建立了，正在等待客户端数据&#8221;，这将意味着有可能出现客户端只发出连接请求，但是不发送数据。如果你的服务器收到太多这种类型的连接时，它将拒绝连接更多的合法客户端请求。这就是黑客进行&#8220;拒绝服务&#8221;攻击的常见手法。</p>
<p>要预防此类攻击，接受连接的线程应该不时地通过调用getsockopt()函数(选项参数为SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设置为套接字被连接的时间，或者设置为-1(代表套接字尚未建立连接)。这时，WSAEventSelect()的特性就可以很好地利用来做这种检查。如果发现连接已经建立，但是很久都没有收到数据的情况，那么就应该终止连接，方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意，在多数非紧急情况下，如果套接字已经传递给AcceptEx()并开始守候，但还未建立连接，那么你的应用程序不应该关闭它们。这是因为即使关闭了这些套接字，出于提高系统性能的考虑，在连接进入之前，或者监听套接字自身被关闭之前，相应的内核模式的数据结构也不会被干净地清除。</p>
<p>发出AcceptEx()调用的线程，似乎与那个进行完成端口关联操作、处理其它I/O完成通知的线程是同一个，但是，别忘记线程里应该尽力避免执行阻塞型的操作。Winsock2分层结构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思，抱歉)。每个AcceptEx()调用都需要创建一个新套接字，所以最好有一个独立的线程专门调用AcceptEx()，而不参与其它I/O处理。你也可以利用这个线程来执行其它任务，比如事件记录。</p>
<p>有关AcceptEx()的最后一个注意事项：要实现这些API，并不需要其它提供商提供的Winsock2实现。这一点对微软特有的其它API也同样适用，比如TransmitFile()和GetAcceptExSockAddrs()，以及其它可能会被加入到新版Windows的API. 在Windows NT和2000上，这些API是在微软的底层提供者DLL(mswsock.dll)中实现的，可通过与mswsock.lib编译连接进行调用，或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针。</p>
<p>如果在没有事先获得函数指针的情况下直接调用函数(也就是说，编译时静态连接mswsock.lib，在程序中直接调用函数)，那么性能将很受影响。因为AcceptEx()被置于Winsock2架构之外，每次调用时它都被迫通过WSAIoctl()取得函数指针。要避免这种性能损失，需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。<br><br>参见下图套接字架构： <br><img height=231 src="http://www.vckbase.com/document/journal/vckbase50/images/winsockfig03.gif" width=200> <br><br><br><strong><font color=#009999>TransmitFile 和 TransmitPackets </font></strong><br><br>Winsock 提供两个专门为文件和内存数据传输进行了优化的函数。其中TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用，而TransmitPackets()则将在未来版本的Windows中实现。</p>
<p>TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是，先调用CreateFile()打开一个文件，然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率，因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile()，那么只需要给它一个已打开文件的句柄和要发送的字节数，而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次，然后TransmitFile()时再发生一次。这样效率就高多了。</p>
<p>TransmitPackets()比TransmitFile()更进一步，它允许用户只调用一次就可以发送指定的多个文件和内存缓冲区。函数原型如下：<br></p>
<pre>BOOL TransmitPackets(
SOCKET hSocket,
LPTRANSMIT_PACKET_ELEMENT lpPacketArray,
DWORD nElementCount,
DWORD nSendSize,
LPOVERLAPPED lpOverlapped,
DWORD dwFlags
); </pre>
<p>其中，lpPacketArray是一个结构的数组，其中的每个元素既可以是一个文件句柄或者内存缓冲区，该结构定义如下：<br></p>
<pre>typedef struct _TRANSMIT_PACKETS_ELEMENT {
DWORD dwElFlags;
DWORD cLength;
union {
struct {
LARGE_INTEGER     nFileOffset;
HANDLE            hFile;
};
PVOID             pBuffer;
};
} TRANSMIT_FILE_BUFFERS;
</pre>
<p>其中各字段是自描述型的(self explanatory)。<br>dwElFlags字段：指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE 和TF_ELEMENT_MEMORY指定)；<br>cLength字段：指定将从数据源发送的字节数(如果是文件，这个字段值为0表示发送整个文件)；<br>结构中的无名联合体：包含文件句柄的内存缓冲区(以及可能的偏移量)。 </p>
<p>使用这两个API的另一个好处，是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后，就会在传输层级别断开连接，这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程，将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。</p>
<p>这两个API也都有一个共同的弱点：Windows NT Workstation 或 Windows 2000 专业版中，函数每次只能处理两个调用请求，只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。</p>
<p><font color=#009999><strong>放在一起看看</strong></font></p>
<p>以上各节中，我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢？其实，这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时，那么你越能够避开瓶颈问题。</p>
<p>来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么，服务器将需要创建一个监听套接字，把它与某个完成端口进行关联，为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据，所以如果我们准备好接收缓冲区会使事情变得更为容易。当然，不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。</p>
<p>该设计中有一个重要的问题要考虑，我们应该允许多少个AcceptEx()进行守候。这是因为，每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区，那么内存中将会出现很多被锁定的页面(前文说过了，每个重叠操作都会消耗一小部分未分页内存池，同时还会锁定所有涉及的缓冲区)。这个问题很难回答，没有一个确切的答案。最好的方法是把这个值做成可以调整的，通过反复做性能测试，你就可以得出在典型应用环境中最佳的值。</p>
<p>好了，当你测算清楚后，下面就是发送数据的问题了，考虑的重点是你希望服务器同时处理多少个并发的连接。通常情况下，服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多，所消耗的未分页内存池也越多；等候处理的发送调用越多，被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。</p>
<p>对于上述环境，通常不需要关闭单个套接字的缓冲区，因为只在AcceptEx()中有一次接收数据的操作，而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是，如果客户机与服务器交互的方式变一变，客户机在发送了一次数据之后，还需要发送更多的数据，在这种情况下关闭接收缓冲就不太妙了，除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。</p>
<p><font color=#009999><strong>结论</strong></font></p>
<p>开发大响应规模的Winsock服务器并不是很可怕，其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量，防止出现未分页内存池被耗尽，这才是最主要的挑战。按照我们前面讨论的一些原则，你就可以开发出大响应规模的服务器应用程序。</p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30222.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 11:46 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30222.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】完成端口的一个简单封装类 </title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30221.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 03:38:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30221.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30221.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30221.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30221.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30221.html</trackback:ping><description><![CDATA[<p>&nbsp; </p>
<p align=left><span>/////////////////////////////////////////////////////////////////////////////////////</span></p>
<p align=left><span>//&nbsp;&nbsp; Iocp </span><span>头文件</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>#pragma</span><span> <span>once</span></span></p>
<p align=left>&nbsp;</p>
<p align=left><span>#include</span><span> &lt;winsock2.h&gt;</span></p>
<p align=left><span>#pragma</span><span> <span>comment</span>( <span>lib</span>, "ws2_32.lib" )</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>const</span><span> <span>int</span> OP_READ = 0;</span></p>
<p align=left><span>const</span><span> <span>int</span> OP_WRITE = 1;</span></p>
<p align=left><span>const</span><span> <span>int</span> OP_ACCEPT = 2;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>/*</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPEDPLUS </span><span>结构体设计思路</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED </span><span>是一个固定的用于处理网络消息事件返回值的结构体变量</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>在完成端口和重叠</span><span>I/O</span><span>模型里用于返回消息事件的结果</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>因为在处理网络消息的时候，发送的是一个返回值的结构体指针，只要结构体</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>的前面部分满足系统的要求，在系统操作成功的时候也就会把这个结构体指针</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>发回给用户，我们只要在系统定义的结构体后面扩展一些自己的东西，就可以</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>很轻松的确定该消息是谁发过来的。</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>不过好像完成端口在设计的时候也满足了这样的需求，所以在这里我只是放入</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>一些与系统连接有关的数据，用户需要存放的数据这里就不在存放</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>这里存储与系统相关的数据有：</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; socket </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OpCode </span><span>本次消息的操作类型（在完成端口的操作里面，是以消息通知系统，</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>读数据</span><span>/</span><span>写数据，都是要发这样的消息结构体过去的，所以如果系统要同时</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>进行读写操作的话，就需要有一个变量来区分操作了）</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; WSABUF&nbsp;&nbsp; wbuf;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp; </span><span>读写缓冲区结构体变量</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; dwBytes, dwFlags;&nbsp;//&nbsp;&nbsp; </span><span>一些在读写时用到的标志性变量</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; char buf[4096];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp; </span><span>自己的缓冲区</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>上面的</span><span>4</span><span>个变量存放的是一些与消息相关的数据，都是一些操作上用到的，</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span><span>这些东西都是固定的，具体作用需要参考一下完成端口相关函数的参数接口</span></p>
<p align=left><span>*/</span></p>
<p align=left><span>struct</span><span> OVERLAPPEDPLUS</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED&nbsp;&nbsp;&nbsp; ol;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; SOCKET&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>int</span> OpCode;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; WSABUF&nbsp;&nbsp; wbuf;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;&nbsp;&nbsp; dwBytes, dwFlags;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>char</span> buf[4096];</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>class</span><span> CIOCP</span></p>
<p align=left><span>{</span></p>
<p align=left><span>protected</span><span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; HANDLE g_hwThread;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>工作线程句柄</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;m_wthreadID;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; HANDLE g_haThread;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>连接线程句柄</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; DWORD&nbsp;m_athreadID;</span></p>
<p align=left><span>public</span><span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> m_workThread;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> m_acceptThread;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; HANDLE m_hIocp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>完成端口的句柄</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; SOCKET m_sSocket;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>public</span><span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; CIOCP(<span>void</span>);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ~CIOCP(<span>void</span>);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>virtual</span> <span>void</span> OnRead(<span>void</span> * p, <span>char</span> *buf, <span>int</span> len){};</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>virtual</span> <span>void</span> OnAccept(SOCKET socket);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>virtual</span> <span>void</span> OnClose(<span>void</span> * p){};</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> SetIoCompletionPort(SOCKET socket, <span>void</span> *p, <span>char</span> *buf = NULL, <span>int</span> len = 0);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>把一个</span><span>socket</span><span>与一个自定义的结构体关联到完成端口（相当于把</span><span>socket</span><span>与一个结构体变量进行绑定），</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>这样当发送上面</span><span>3</span><span>种网络事件的时候，该结构体变量会再传回给程序</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>这样就可以区分当前网络事件是那个</span><span>socket</span><span>发出的</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> Init(<span>void</span>);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> Listen(<span>int</span> port);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>static</span> DWORD <span>__stdcall</span> WorkThread(LPVOID Param);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>static</span> DWORD <span>__stdcall</span> AcceptThread(LPVOID Param);</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>class</span><span> CIOCPClient: <span>public</span> CIOCP</span></p>
<p align=left><span>{</span></p>
<p align=left><span>protected</span><span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; SOCKET m_socket;</span></p>
<p align=left><span>public</span><span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>bool</span> Connect(<span>char</span> *ip, <span>int</span> port);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>void</span> Send(<span>char</span> *buf, <span>int</span> len);</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left><span>//////////////////////////////////////////////////////////////////////////////////////////</span></p>
<p align=left><span>//&nbsp;&nbsp; Iocp </span><span>实现文件</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>#include</span><span> "StdAfx.h"</span></p>
<p align=left><span>#include</span><span> "iocp.h"</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>static</span><span> <span>bool</span> bInit = <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>DWORD <span>__stdcall</span> CIOCP::WorkThread(LPVOID Param)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; CIOCP * pthis = (CIOCP *)Param;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>void</span> * re;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPED * pOverlap;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; DWORD berByte;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>while</span>(pthis-&gt;m_workThread)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>int</span> ret;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ret = GetQueuedCompletionStatus(pthis-&gt;m_hIocp, &amp;berByte, (LPDWORD)&amp;re, (LPOVERLAPPED *)&amp;pOverlap, INFINITE);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (ret == ERROR_SUCCESS)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (berByte == 0)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>客户端断开连接</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthis-&gt;OnClose(re);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPEDPLUS *olp = (OVERLAPPEDPLUS *)pOverlap;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; closesocket(olp-&gt;s);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>delete</span> olp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>释放</span><span> </span><span>与</span><span>socket</span><span>绑定的结构体变量</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>continue</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (re == NULL) <span>return</span> 0;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPEDPLUS *olp = (OVERLAPPEDPLUS *)pOverlap;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>switch</span>(olp-&gt;OpCode)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>case</span> OP_READ:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthis-&gt;OnRead(re, olp-&gt;wbuf.buf, berByte);&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>调用</span><span> OnRead() </span><span>通知应用程序，服务器收到来自客户端的网络数据</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WSARecv(olp-&gt;s, &amp;olp-&gt;wbuf, 1, &amp;olp-&gt;dwBytes, &amp;olp-&gt;dwFlags, &amp;olp-&gt;ol, NULL);&nbsp;<span>//&nbsp;&nbsp; </span></span><span>继续调用一个接收的</span><span> I/O </span><span>异步请求</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>break</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>default</span>:</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>break</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> 0;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>DWORD <span>__stdcall</span> CIOCP::AcceptThread(LPVOID Param)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; CIOCP * pthis = (CIOCP *)Param;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>while</span>(pthis-&gt;m_acceptThread)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SOCKET client;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> ((client= accept(pthis-&gt;m_sSocket, NULL, NULL)) == INVALID_SOCKET)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>错误处理</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pthis-&gt;OnAccept(client);&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>调用</span><span> OnAccept()</span><span>通知应用程序有新客户端连接</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> 1;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>CIOCP::CIOCP(<span>void</span>)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>CIOCP::~CIOCP(<span>void</span>)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>bool</span><span> CIOCP::Init(<span>void</span>)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (bInit)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>true</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; WSADATA wsd;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (WSAStartup(MAKEWORD(2,2), &amp;wsd) != 0) </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>; </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; bInit = <span>true</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>true</span>;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>bool</span><span> CIOCP::Listen(<span>int</span> port)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (!bInit)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (!Init())</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; m_sSocket = socket(AF_INET, SOCK_STREAM, 0); </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (m_sSocket == INVALID_SOCKET) </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>; </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//SOCKADDR_IN addr; </span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; sockaddr_in addr;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; addr.sin_family = AF_INET; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; addr.sin_port = htons(port); </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//addr.sin_addr.S_un.S_addr = inet_addr(ip); </span></span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (bind(m_sSocket, (<span>struct</span> sockaddr *)&amp;addr, <span>sizeof</span>(addr)) == SOCKET_ERROR) </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>; </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (listen(m_sSocket, 10) == SOCKET_ERROR) </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>; </span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> ((m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0)) == NULL)&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>创建完成端口的句柄</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>this</span>-&gt;m_acceptThread = <span>true</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; g_haThread = CreateThread(NULL, 0, AcceptThread, (LPVOID)<span>this</span>, 0, &amp;m_athreadID);&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>创建连接线程，用来接收客户端的连接</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>this</span>-&gt;m_workThread = <span>true</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; g_hwThread = CreateThread(NULL, 0, WorkThread, (LPVOID)<span>this</span>, 0, &amp;m_wthreadID); <span>//&nbsp;&nbsp; </span></span><span>创建工作线程，用来处理完成端口消息的</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>true</span>;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>bool</span><span> CIOCP::SetIoCompletionPort(SOCKET socket, <span>void</span> *p, <span>char</span> *buf, <span>int</span> len)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (CreateIoCompletionPort((HANDLE)socket, m_hIocp, (ULONG_PTR)p, 0) == NULL)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; OVERLAPPEDPLUS *olp = <span>new</span> OVERLAPPEDPLUS;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; memset(olp, 0, <span>sizeof</span>(OVERLAPPEDPLUS));</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;s = socket;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (buf)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>这里可以使用用户自定义的缓冲区地址，如果用户不想设置，也可以采用默认分配的缓冲区</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;wbuf.buf = buf;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;wbuf.len = len;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>else</span></span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;wbuf.buf = olp-&gt;buf;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;wbuf.len = 4096;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; olp-&gt;OpCode = OP_READ;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>int</span> ret = WSARecv(olp-&gt;s, &amp;olp-&gt;wbuf, 1, &amp;olp-&gt;dwBytes, &amp;olp-&gt;dwFlags, &amp;olp-&gt;ol, NULL);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (ret == SOCKET_ERROR)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (WSAGetLastError() != ERROR_IO_PENDING)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>true</span>;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>void</span><span> CIOCP::OnAccept(SOCKET socket)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>this</span>-&gt;SetIoCompletionPort(socket, NULL);</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>//===================================================================================</span></p>
<p align=left><span>bool</span><span> CIOCPClient::Connect(<span>char</span> *ip, <span>int</span> port)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>连接服务器</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (!bInit)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (!Init())</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>初始化连接</span><span>socket</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (m_socket == SOCKET_ERROR)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("cocket Create fail");</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>// </span></span><span>填写服务器地址信息</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>// </span></span><span>端口为</span><span>1982</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>// IP</span></span><span>地址为</span><span>INADDR_ANY</span><span>，注意使用</span><span>htonl</span><span>将</span><span>IP</span><span>地址转换为网络格式</span><span>ServerAddr.sin_family = AF_INET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; sockaddr_in ClientAddr;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ClientAddr.sin_family = AF_INET;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ClientAddr.sin_port = htons(port);&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; ClientAddr.sin_addr.s_addr = inet_addr(ip);</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>// </span></span><span>绑定监听端口</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; bind(m_socket, (SOCKADDR *)&amp;ClientAddr, <span>sizeof</span>(ClientAddr));</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> (connect(m_socket, (SOCKADDR *)&amp;ClientAddr, <span>sizeof</span>(ClientAddr)) == SOCKET_ERROR)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; {</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; }</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>if</span> ((m_hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0)) == NULL)&nbsp;&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>创建完成端口的句柄</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>false</span>;</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>this</span>-&gt;m_workThread = <span>true</span>;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; g_hwThread = CreateThread(NULL, 0, WorkThread, (LPVOID)<span>this</span>, 0, &amp;m_wthreadID); <span>//&nbsp;&nbsp; </span></span><span>创建工作线程，用来处理完成端口消息的</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>this</span>-&gt;SetIoCompletionPort(m_socket, &amp;m_socket);&nbsp;&nbsp;&nbsp; <span>//&nbsp;&nbsp; </span></span><span>设置完成端口监听的</span><span>socket</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> <span>true</span>;</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>void</span><span> CIOCPClient::Send(<span>char</span> *buf, <span>int</span> len)</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; send(m_socket, buf, len, 0);</span></p>
<p align=left><span>}</span></p>
<p align=left>&nbsp;</p>
<p align=left>&nbsp;</p>
<p align=left><span>///////////////////////////////////////////////////////////////////////////////////</span></p>
<p align=left><span>// IOCPclient </span><span>应用代码</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>#include</span><span> "stdafx.h"</span></p>
<p align=left><span>#include</span><span> "IOCP.h"</span></p>
<p align=left><span>#include</span><span> "TClientSocket.h"</span></p>
<p align=left><span>class</span><span> Iocp :<span>public</span> CIOCPClient </span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<span>void</span> OnRead(<span>void</span> * p, <span>char</span> *buf, <span>int</span> len)</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;printf(buf);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;Sleep(1000);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<span>this</span>-&gt;Send(buf, len);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;}</span></p>
<p align=left><span>};</span></p>
<p align=left>&nbsp;</p>
<p align=left><span>int</span><span> _tmain(<span>int</span> argc, _TCHAR* argv[])</span></p>
<p align=left><span>{</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; Iocp iocp;</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; iocp.Init();</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; iocp.Connect("127.0.0.1", 4311);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; iocp.Send("test\0", 5);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; gets(<span>new</span> <span>char</span>[1000]);</span></p>
<p align=left><span>&nbsp;&nbsp;&nbsp;&nbsp; <span>return</span> 0;</span></p>
<p align=left><span>}</span></p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30221.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 11:38 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30221.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】完成端口与高性能服务器程序开发</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30214.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 03:07:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30214.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30214.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30214.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30214.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30214.html</trackback:ping><description><![CDATA[<div class=postcontent style="FONT-FAMILY: Arial"><font size=2>早在两年前我就已经能很熟练的运用完成端口这种技术了,只是一直没有机会将它用在什么项目中,这段时间见到这种技术被过分炒作,过分的神秘化,就想写一篇解释它如何工作的文章.想告诉大家它没有传说中的那么高深难懂!有什么错误的地方还请高人指正.转载请注明出处及作者,谢谢!<br><br>以一个文件传输服务端为例,在我的机器上它只起两个线程就可以为很多个个客户端同时提供文件下载服务,程序的性能会随机器内CPU个数的增加而线性增长,我尽可能做到使它清晰易懂,虽然程序很小却用到了NT 5的一些新特性,重叠IO,完成端口以及线程池,基于这种模型的服务端程序应该是NT系统上性能最好的了.<br><br>首先.做为完成端口的基础,我们应该理解重叠IO,这需要你已经理解了内核对象及操作系统的一些概念概念,什么是信号/非信号态,什么是等待函数,什么是成功等待的副作用,什么是线程挂起等,如果这些概令还没有理解,你应该先看一下Windows 核心编程中的相关内容.如果已经理解这些,那么重叠IO对你来说并不难.<br><br>你可以这样认为重叠IO,现在你已经进入一个服务器/客户机环境,请不要混淆概念,这里的服务器是指操作系统,而客户机是指你的程序(它进行IO操作),是当你进行IO操作(send,recv,writefile,readfile....)时你发送一个IO请求给服务器(操作系统),由服务器来完成你需要的操作,然后你什么事都没有了,当服务器完成IO请求时它会通知你,当然在这期间你可以做任何事,一个常用的技巧是在发送重叠IO请求后,程序在一个循环中一边调用PeekMessage,TranslateMessage和DispatchMessage更新界面,同时调用GetOverlappedResult等待服务器完成IO操作,更高效一点的做法是使用IO完成例程来处理服务器(操作系统)返回的结果,但并不是每个支持重叠IO操作的函数都支持完成例程如TransmitFile函数.<br><br>例1.一次重叠写操作过程(GetOverlappedResult方法):<br>1.填写一个OVERLAPPED结构<br>2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针)<br>3.做其它事(如更新界面)<br>4.GetOverlappedResult取操作结果<br>5.如果IO请求没有完成,并且没有出错则回到期３<br>6.处理IO操作结果<br><br>例2.一次重叠写操作过程(完成例程方法):<br>1.填写一个OVERLAPPED结构<br>2.进行一次写操作,并指定重叠操作参数(上面的OVERLAPPED结构变量的指针),并指定完成例程<br>3.做其它事(如更新界面)<br>4.当完成例程被调用说明IO操作已经完成或出错,现在可以对操作结果进行处理了<br><br><br>如果你已经理解上面的概念,就已经很接近IO完成端口了,当然这只是很常规的重叠操作它已经非常高效,但如果再结合多线程对一个File或是Socket进行重叠IO操作就会非常复杂,通常程序员很难把握这种复杂度.完成端口可以说就是为了充分发挥多线程和重叠IO操作相结合的性能而设计的.很多人都说它复杂,其实如果你自己实现一个多线程的对一个File或是Socket进行重叠IO操作的程序(注意是多个线程对一个HANDLE或SOCKET进行重叠IO操作,而不是启一个线程对一个HANDLE进行重叠IO操作)就会发现完成端口实际上简化了多线程里使用重叠IO的复杂度,并且性能更高,性能高在哪?下面进行说明.<br><br>我们可能写过这样的服务端程序:<br><br>例3.主程序:<br>1.监听一个端口<br>2.等待连接<br>3.当有连接来时<br>4.启一个线程对这个客户端进行处理<br>5.回到2<br><br>服务线程:<br>1.读客户端请求<br>2.如果客户端不再有请求,执行6<br>3.处理请求<br>4.返回操作结果<br>5.回到1<br>6.退出线程<br><br>这是一种最简单的网络服务器模型,我们把它优化一下<br><br>例4.主程序:<br>1.开一个线程池,里面有机器能承受的最大线程数个线程,线程都处于挂起(suspend)状态<br>1.监听一个端口<br>2.等待连接<br>3.当有连接来时<br>4.从线程池里Resume一个线程对这个客户端进行处理<br>5.回到2<br><br>服务线程与例3模型里的相同,只是当线程处理完客户端所有请求后,不是退出而是回到线程池,再次挂起让出CPU时间,并等待为下一个客户机服务.当然在此期间线程会因为IO操作(服务线程的第1,5操作,也许还有其它阻塞操作)挂起自己,但不会回到线程池,也就是说它一次只能为一个客户端服务.<br><br>这可能是你能想到的最高效的服务端模型了吧!它与第一个服务端模型相比少了很多个用户态到内核态的CONTEXT Switch,反映也更加快速,也许你可能觉得这很微不足道,这说明你缺少对大规模高性能服务器程序(比如网游服务端)的认识,如果你的服务端程序要对几千万个客户端进行服务呢?这也是微软Windows NT开发组在NT 5以上的系统中添加线程池的原因.<br><br>思考一下什么样的模型可以让一个线程为多个客户端服务呢!那就要跳出每来一个连接启线程为其服务的固定思维模式,我们把线程服务的最小单元分割为单独的读或写操作(注意是读或写不是读和写),而不是一个客户端从连接到断开期间的所有读写操作.每个线程都使用重叠IO进行读写操作,投递了读写请求后线程回到线程池,等待为其它客户机服务,当操作完成或出错时再回来处理操作结果,然后再回到线程池.<br><br>看看这样的服务器模型:<br>例5.主程序:<br>1.开一个线程池,里面有机器内CPU个数两倍的线程,线程都处于挂起(suspend)状态,它们在都等处理一次重叠IO操作的完成结果<br>1.监听一个端口<br>2.等待连接<br>3.当有连接来时<br>4.投递一个重叠读操作读取命令<br>5.回到2<br><br>服务线程:<br>1.如果读完成,则处理读取的内容(如HTTP GET命令),否则执行3<br>2.投递一个重叠写操作(如返回HTTP GET命令需要的网页)<br>3.如果是一个写操作完成,可以再投递一个重叠读操作,读取客户机的下一个请求,或者是关闭连接(如HTTP协议里每发完一个网页就断开)<br>4.取得下一个重叠IO操作结果,如果IO操作没有完成或没有IO操作则回到线程池<br><br>假设这是一个WEB服务器程序,可以看到工作者线程是以读或写为最小的工作单元运行的,在主程序里面进行了一次重叠读操作<br><br>当读操作完成时一个线程池中的一个工作者线程被激活取得了操作结果,处理GET或POST命令,然后发送一个网页内容,发送也是一个重叠操作,然后处理对其它客户机的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.可以看到使用这种模型发送和接收可以是也可以不是一个线程.<br><br>当发送操作完成时,线程池中的一个工作者线程池激活,它关闭连接(HTTP协议),然后处理其它的IO操作结果,如果没有其它的东西需要处理时回到线程池等待.<br><br>看看在这样的模型中一个线程怎么为多个客户端服务,同样是模拟一个WEB服务器例子:<br><br>假如现在系统中有两个线程,ThreadA,ThreadB它们在都等处理一次重叠IO操作的完成结果<br><br>当一个客户机ClientA连接来时主程序投递一个重叠读操作,然后等待下一个客户机连接,当读操作完成时ThreadA被激活,它收到一个HTTP GET命令,然后ThreadA使用重叠写操作发送一个网页给ClientA,然后立即回到线程池等待处理下一个IO操作结果,这时发送操作还没有完成,又有一个客户机ClientB连接来,主程序再投递一个重叠读操作,当读操作完成时ThreadA(当然也可能是ThreadB)再次被激活,它重复同样步骤,收到一个GET命令,使用重叠写操作发送一个网页给ClientB,这次它没有来得及回到线程池时,又有一个连接ClientC连入,主程序再投递一个重叠读操作,读操作完成时ThreadB被激活(因为ThreadA还没有回到线程池)它收到一个HTTP GET命令,然后ThreadB使用重叠写操作发送一个网页给ClientC,然后ThreadB回到线程池,这时ThreadA也回到了线程池.<br><br>可以想象现在有三个挂起的发送操作分别是ThreadA发送给ClientA和ClientB的网页,以及ThreadB发送给ClientC的网页,它们由操作系统内核来处理.ThreadA和ThreadB现在已经回到线程池,可以继续为其它任何客户端服务.<br><br>当对ClientA的重叠写操作已经完成,ThreadA(也可以是ThreadB)又被激活它关闭与ClientA连接,但还没有回到线程池,与此同时发送给ClientB的重叠写操作也完成,ThreadB被激活(因为ThreadA还没有回到线程池)它关闭与ClientB的连接,然后回到线程池,这时ClientC的写操作也完成,ThreadB再次被激活(因为ThreadA还是没有回到线程池),它再关闭与ClientC的连接,这时ThreadA回到线程池,ThreadB也回到线程池.这时对三个客户端的服务全部完成.可以看到在整个服务过程中,"建立连接","读数据","写数据"和"关闭连接"等操作是逻辑上连续而实际上分开的.<br><br>到现在为止两个线程处理了三次读操作和三次写操作,在这些读写操作过程中所出现的状态机(state machine)是比较复杂的,我们模拟的是经过我简化过的,实际上的状态要比这个还要复杂很多,然而这样的服务端模型在客户端请求越多时与前两个模型相比的性能越高.而使用完成端口我们可以很容易实现这样的服务器模型.<br><br>微软的IIS WEB服务器就是使用这样的服务端模型,很多人说什么阿帕奇服务器比IIS的性能好什么什么的我表示怀疑,除非阿帕奇服务器可以将线程分割成,为更小的单元服务,我觉得不太可能!这种完成端口模型已经将单个读或写操作作为最小的服务单元,我觉得在相同机器配置的情况下IIS的性能要远远高于其它WEB服务器,这也是从实现机理上来分析的,如果出现性能上的差别可能是在不同的操作系统上,也许Linux的内核比Windows的要好,有人真的研究过吗?还是大家一起在炒作啊.<br><br>对于状态机概念,在很多方面都用到,TCPIP中有,编译原理中有,OpengGL中有等等,我的离散数学不好(我是会计专业不学这个),不过还是搞懂了些,我想如果你多花些时间看,还是可以搞懂的.最后是一个简单的文件传输服务器程序代码,只用了两个线程(我的机器里只有一块CPU)就可以服务多个客户端.我调试时用它同时为6个nc客户端提供文件下载服务都没有问题,当然更多也不会有问题,只是略为使用了一下NT 5的线程池和完成端口技术就可以有这样高的性能,更不用说IIS的性能咯!<br><br>希望大家不要陷在这个程序的框架中,Ctrl+C,Ctrl+V没有什么意义,要理解它的实质.程序使用Visual C++ 6.0 SP5+2003 Platform SDK编译通过,在Windows XP Professional下调试运行通过.程序运行的最低要求是Windows 2000操作系统.<br><br>/********************************************************************<br>&nbsp; created: &nbsp; 2005/12/24<br>&nbsp; created: &nbsp; 24:12:2005 &nbsp; 20:25<br>&nbsp; modified: &nbsp; 2005/12/24<br>&nbsp; filename: &nbsp; d:\vcwork\iocomp\iocomp.cpp<br>&nbsp; file path: &nbsp; d:\vcwork\iocomp<br>&nbsp; file base: &nbsp; iocomp<br>&nbsp; file ext: &nbsp; cpp<br>&nbsp; author: &nbsp; &nbsp; kruglinski(kruglinski_at_gmail_dot_com)<br>&nbsp; <br>&nbsp; purpose: &nbsp; 利用完成端口技术实现的高性能文件下载服务程序<br>*********************************************************************/<br><br>#define _WIN32_WINNT &nbsp; 0x0500<br><br>#include &lt;cstdlib&gt;<br>#include &lt;clocale&gt;<br>#include &lt;ctime&gt;<br>#include &lt;iostream&gt;//一使用输入输出流程序顿时增大70K<br>#include &lt;vector&gt;<br>#include &lt;algorithm&gt;<br>#include &lt;winsock2.h&gt;<br>#include &lt;mswsock.h&gt;<br><br>using namespace std;<br><br>#pragma comment(lib,"ws2_32.lib")<br>#pragma comment(lib,"mswsock.lib")<br><br>const int MAX_BUFFER_SIZE=1024;<br>const int PRE_SEND_SIZE=1024;<br>const int QUIT_TIME_OUT=3000;<br>const int PRE_DOT_TIMER=QUIT_TIME_OUT/80;<br><br>typedef enum{IoTransFile,IoSend,IoRecv,IoQuit} IO_TYPE;<br><br>typedef struct<br>{<br>&nbsp; SOCKET hSocket;<br>&nbsp; SOCKADDR_IN ClientAddr;<br>}PRE_SOCKET_DATA,*PPRE_SOCKET_DATA;<br><br>typedef struct<br>{<br>&nbsp; OVERLAPPED &nbsp; oa;<br>&nbsp; WSABUF &nbsp; &nbsp; DataBuf;<br>&nbsp; char &nbsp; &nbsp; Buffer[MAX_BUFFER_SIZE];<br>&nbsp; IO_TYPE &nbsp; &nbsp; IoType;<br>}PRE_IO_DATA,*PPRE_IO_DATA;<br><br>typedef vector&lt;PPRE_SOCKET_DATA&gt; &nbsp; SocketDataVector;<br>typedef vector&lt;PPRE_IO_DATA&gt; &nbsp; &nbsp; IoDataVector;<br><br>SocketDataVector &nbsp; gSockDataVec;<br>IoDataVector &nbsp; &nbsp; gIoDataVec;<br><br>CRITICAL_SECTION &nbsp; csProtection;<br><br>char* TimeNow(void)<br>{<br>&nbsp; time_t t=time(NULL);<br>&nbsp; tm *localtm=localtime(&amp;t);<br>&nbsp; static char timemsg[512]={0};<br>&nbsp; <br>&nbsp; strftime(timemsg,512,"%Z: %B %d %X,%Y",localtm);<br>&nbsp; return timemsg;<br>}<br><br>BOOL TransFile(PPRE_IO_DATA pIoData,PPRE_SOCKET_DATA pSocketData,DWORD dwNameLen)<br>{<br>&nbsp; //这一句是为nc做的,你可以修改它<br>&nbsp; pIoData-&gt;Buffer[dwNameLen-1]='\0';<br>&nbsp; <br>&nbsp; HANDLE hFile=CreateFile(pIoData-&gt;Buffer,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);<br>&nbsp; BOOL bRet=FALSE;<br><br>&nbsp; if(hFile!=INVALID_HANDLE_VALUE)<br>&nbsp; {<br>&nbsp; &nbsp; cout&lt;&lt;"Transmit File "&lt;&lt;pIoData-&gt;Buffer&lt;&lt;" to client"&lt;&lt;endl;<br>&nbsp; &nbsp; pIoData-&gt;IoType=IoTransFile;<br>&nbsp; &nbsp; memset(&amp;pIoData-&gt;oa,0,sizeof(OVERLAPPED));<br>&nbsp; &nbsp; *reinterpret_cast&lt;HANDLE*&gt;(pIoData-&gt;Buffer)=hFile;<br>&nbsp; &nbsp; TransmitFile(pSocketData-&gt;hSocket,hFile,GetFileSize(hFile,NULL),PRE_SEND_SIZE,reinterpret_cast&lt;LPOVERLAPPED&gt;(pIoData),NULL,TF_USE_SYSTEM_THREAD);<br>&nbsp; &nbsp; bRet=WSAGetLastError()==WSA_IO_PENDING;<br>&nbsp; }<br>&nbsp; else<br>&nbsp; &nbsp; cout&lt;&lt;"Transmit File "&lt;&lt;"Error:"&lt;&lt;GetLastError()&lt;&lt;endl;<br><br>&nbsp; return bRet;<br>}<br><br>DWORD WINAPI ThreadProc(LPVOID IocpHandle)<br>{<br>&nbsp; DWORD dwRecv=0;<br>&nbsp; DWORD dwFlags=0;<br>&nbsp; <br>&nbsp; HANDLE hIocp=reinterpret_cast&lt;HANDLE&gt;(IocpHandle);<br>&nbsp; DWORD dwTransCount=0;<br>&nbsp; PPRE_IO_DATA pPreIoData=NULL;<br>&nbsp; PPRE_SOCKET_DATA pPreHandleData=NULL;<br><br>&nbsp; while(TRUE)<br>&nbsp; {<br>&nbsp; &nbsp; if(GetQueuedCompletionStatus(hIocp,&amp;dwTransCount,<br>&nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;LPDWORD&gt;(&amp;pPreHandleData),<br>&nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;LPOVERLAPPED*&gt;(&amp;pPreIoData),INFINITE))<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; if(0==dwTransCount&amp;&amp;IoQuit!=pPreIoData-&gt;IoType)<br>&nbsp; &nbsp; &nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;"Client:"<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;inet_ntoa(pPreHandleData-&gt;ClientAddr.sin_addr)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;":"&lt;&lt;ntohs(pPreHandleData-&gt;ClientAddr.sin_port)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;" is closed"&lt;&lt;endl;<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; closesocket(pPreHandleData-&gt;hSocket);<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EnterCriticalSection(&amp;csProtection);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; IoDataVector::iterator itrIoDelete=find(gIoDataVec.begin(),gIoDataVec.end(),pPreIoData);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gIoDataVec.erase(itrIoDelete);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SocketDataVector::iterator itrSockDelete=find(gSockDataVec.begin(),gSockDataVec.end(),pPreHandleData);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gSockDataVec.erase(itrSockDelete);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LeaveCriticalSection(&amp;csProtection);<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete *itrIoDelete;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete *itrSockDelete;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue;<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; switch(pPreIoData-&gt;IoType){<br>&nbsp; &nbsp; &nbsp; &nbsp; case IoTransFile:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;"Client:"<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;inet_ntoa(pPreHandleData-&gt;ClientAddr.sin_addr)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;":"&lt;&lt;ntohs(pPreHandleData-&gt;ClientAddr.sin_port)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;" Transmit finished"&lt;&lt;endl;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CloseHandle(*reinterpret_cast&lt;HANDLE*&gt;(pPreIoData-&gt;Buffer));<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; goto LRERECV;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; case IoSend:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;"Client:"<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;inet_ntoa(pPreHandleData-&gt;ClientAddr.sin_addr)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;":"&lt;&lt;ntohs(pPreHandleData-&gt;ClientAddr.sin_port)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;" Send finished"&lt;&lt;endl;<br><br>LRERECV:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;IoType=IoRecv;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;DataBuf.len=MAX_BUFFER_SIZE;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; memset(&amp;pPreIoData-&gt;oa,0,sizeof(OVERLAPPED));<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WSARecv(pPreHandleData-&gt;hSocket,&amp;pPreIoData-&gt;DataBuf,1,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;dwRecv,&amp;dwFlags,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;LPWSAOVERLAPPED&gt;(pPreIoData),NULL);<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;<br><br>&nbsp; &nbsp; &nbsp; &nbsp; case IoRecv:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;"Client:"<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;inet_ntoa(pPreHandleData-&gt;ClientAddr.sin_addr)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;":"&lt;&lt;ntohs(pPreHandleData-&gt;ClientAddr.sin_port)<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &lt;&lt;" recv finished"&lt;&lt;endl;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;IoType=IoSend;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(!TransFile(pPreIoData,pPreHandleData,dwTransCount))<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; memset(&amp;pPreIoData-&gt;oa,0,sizeof(OVERLAPPED));<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; strcpy(pPreIoData-&gt;DataBuf.buf,"File transmit error!\r\n");<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;DataBuf.len=strlen(pPreIoData-&gt;DataBuf.buf);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WSASend(pPreHandleData-&gt;hSocket,&amp;pPreIoData-&gt;DataBuf,1,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;dwRecv,dwFlags,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;LPWSAOVERLAPPED&gt;(pPreIoData),NULL);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; case IoQuit:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; goto LQUIT;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; default:<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; } &nbsp; <br>&nbsp; }<br>&nbsp; <br>LQUIT:<br>&nbsp; return 0;<br>}<br><br>HANDLE hIocp=NULL;<br>SOCKET hListen=NULL;<br><br>BOOL WINAPI ShutdownHandler(DWORD dwCtrlType)<br>{<br>&nbsp; PRE_SOCKET_DATA PreSockData={0};<br>&nbsp; PRE_IO_DATA PreIoData={0};<br><br>&nbsp; PreIoData.IoType=IoQuit;<br><br>&nbsp; if(hIocp)<br>&nbsp; {<br>&nbsp; &nbsp; PostQueuedCompletionStatus(hIocp,1,<br>&nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;ULONG_PTR&gt;(&amp;PreSockData),<br>&nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;LPOVERLAPPED&gt;(&amp;PreIoData));<br><br>&nbsp; &nbsp; cout&lt;&lt;"Shutdown at "&lt;&lt;TimeNow()&lt;&lt;endl&lt;&lt;"wait for a moment please"&lt;&lt;endl;<br>&nbsp; &nbsp; <br>&nbsp; &nbsp; //让出CPU时间,让线程退出<br>&nbsp; &nbsp; for(int t=0;t&lt;80;t+=1)<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; Sleep(PRE_DOT_TIMER);<br>&nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;".";<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; <br>&nbsp; &nbsp; CloseHandle(hIocp);<br>&nbsp; }<br>&nbsp; <br>&nbsp; int i=0;<br><br>&nbsp; for(;i&lt;gSockDataVec.size();i++)<br>&nbsp; {<br>&nbsp; &nbsp; PPRE_SOCKET_DATA pSockData=gSockDataVec[i];<br>&nbsp; &nbsp; closesocket(pSockData-&gt;hSocket);<br>&nbsp; &nbsp; delete pSockData;<br>&nbsp; }<br><br>&nbsp; for(i=0;i&lt;gIoDataVec.size();i++)<br>&nbsp; {<br>&nbsp; &nbsp; PPRE_IO_DATA pIoData=gIoDataVec[i];<br>&nbsp; &nbsp; delete pIoData;<br>&nbsp; }<br><br>&nbsp; DeleteCriticalSection(&amp;csProtection);<br>&nbsp; if(hListen)<br>&nbsp; &nbsp; closesocket(hListen);<br><br>&nbsp; WSACleanup();<br>&nbsp; exit(0);<br>&nbsp; return TRUE;<br>}<br><br>LONG WINAPI MyExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)<br>{<br>&nbsp; ShutdownHandler(0);<br>&nbsp; return EXCEPTION_EXECUTE_HANDLER;<br>}<br><br>u_short DefPort=8182;<br><br>int main(int argc,char **argv)<br>{<br>&nbsp; if(argc==2)<br>&nbsp; &nbsp; DefPort=atoi(argv[1]);<br><br>&nbsp; InitializeCriticalSection(&amp;csProtection);<br>&nbsp; SetUnhandledExceptionFilter(MyExceptionFilter);<br>&nbsp; SetConsoleCtrlHandler(ShutdownHandler,TRUE);<br><br>&nbsp; hIocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);<br><br>&nbsp; WSADATA data={0};<br>&nbsp; WSAStartup(0x0202,&amp;data);<br><br>&nbsp; hListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);<br>&nbsp; if(INVALID_SOCKET==hListen)<br>&nbsp; {<br>&nbsp; &nbsp; ShutdownHandler(0);<br>&nbsp; }<br>&nbsp; <br>&nbsp; SOCKADDR_IN addr={0};<br>&nbsp; addr.sin_family=AF_INET;<br>&nbsp; addr.sin_port=htons(DefPort);<br>&nbsp; <br>&nbsp; if(bind(hListen,reinterpret_cast&lt;PSOCKADDR&gt;(&amp;addr),<br>&nbsp; &nbsp; sizeof(addr))==SOCKET_ERROR)<br>&nbsp; {<br>&nbsp; &nbsp; ShutdownHandler(0);<br>&nbsp; }<br>&nbsp; <br>&nbsp; if(listen(hListen,256)==SOCKET_ERROR)<br>&nbsp; &nbsp; ShutdownHandler(0);<br><br>&nbsp; SYSTEM_INFO si={0};<br>&nbsp; GetSystemInfo(&amp;si);<br>&nbsp; si.dwNumberOfProcessors&lt;&lt;=1;<br><br>&nbsp; for(int i=0;i&lt;si.dwNumberOfProcessors;i++)<br>&nbsp; {<br>&nbsp; &nbsp; <br>&nbsp; &nbsp; QueueUserWorkItem(ThreadProc,hIocp,WT_EXECUTELONGFUNCTION);<br>&nbsp; }<br>&nbsp; <br>&nbsp; cout&lt;&lt;"Startup at "&lt;&lt;TimeNow()&lt;&lt;endl<br>&nbsp; &nbsp; &lt;&lt;"work on port "&lt;&lt;DefPort&lt;&lt;endl<br>&nbsp; &nbsp; &lt;&lt;"press CTRL+C to shutdown"&lt;&lt;endl&lt;&lt;endl&lt;&lt;endl;<br><br>&nbsp; while(TRUE)<br>&nbsp; {<br>&nbsp; &nbsp; int namelen=sizeof(addr);<br>&nbsp; &nbsp; memset(&amp;addr,0,sizeof(addr));<br>&nbsp; &nbsp; SOCKET hAccept=accept(hListen,reinterpret_cast&lt;PSOCKADDR&gt;(&amp;addr),&amp;namelen);<br><br>&nbsp; &nbsp; if(hAccept!=INVALID_SOCKET)<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; cout&lt;&lt;"accept a client:"&lt;&lt;inet_ntoa(addr.sin_addr)&lt;&lt;":"&lt;&lt;ntohs(addr.sin_port)&lt;&lt;endl;<br><br>&nbsp; &nbsp; &nbsp; &nbsp; PPRE_SOCKET_DATA pPreHandleData=new PRE_SOCKET_DATA;<br>&nbsp; &nbsp; &nbsp; &nbsp; pPreHandleData-&gt;hSocket=hAccept;<br>&nbsp; &nbsp; &nbsp; &nbsp; memcpy(&amp;pPreHandleData-&gt;ClientAddr,&amp;addr,sizeof(addr));<br>&nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; CreateIoCompletionPort(reinterpret_cast&lt;HANDLE&gt;(hAccept),<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hIocp,reinterpret_cast&lt;DWORD&gt;(pPreHandleData),0);<br>&nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; PPRE_IO_DATA pPreIoData=new(nothrow) PRE_IO_DATA; <br><br>&nbsp; &nbsp; &nbsp; &nbsp; if(pPreIoData)<br>&nbsp; &nbsp; &nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EnterCriticalSection(&amp;csProtection);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gSockDataVec.push_back(pPreHandleData);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gIoDataVec.push_back(pPreIoData);<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LeaveCriticalSection(&amp;csProtection);<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; memset(pPreIoData,0,sizeof(PRE_IO_DATA));<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;IoType=IoRecv;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;DataBuf.len=MAX_BUFFER_SIZE;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pPreIoData-&gt;DataBuf.buf=pPreIoData-&gt;Buffer;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DWORD dwRecv=0;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DWORD dwFlags=0;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WSARecv(hAccept,&amp;pPreIoData-&gt;DataBuf,1,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &amp;dwRecv,&amp;dwFlags,<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; reinterpret_cast&lt;WSAOVERLAPPED*&gt;(pPreIoData),NULL);<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; &nbsp; &nbsp; else<br>&nbsp; &nbsp; &nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete pPreHandleData;<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; closesocket(hAccept);<br>&nbsp; &nbsp; &nbsp; &nbsp; }<br>&nbsp; &nbsp; }<br>&nbsp; }<br>&nbsp; <br>&nbsp; return 0;<br>}<br><br>参考资料:<br>《MSDN 2001》<br>《Windows 网络编程》<br>《Windows 核心编程》<br>《TCP/IP详解》</font><br></div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30214.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 11:07 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30214.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转]理解I/O Completion Port(完成端口)</title><link>http://www.cppblog.com/niewenlong/archive/2007/08/17/30213.html</link><dc:creator>聂文龙</dc:creator><author>聂文龙</author><pubDate>Fri, 17 Aug 2007 03:03:00 GMT</pubDate><guid>http://www.cppblog.com/niewenlong/archive/2007/08/17/30213.html</guid><wfw:comment>http://www.cppblog.com/niewenlong/comments/30213.html</wfw:comment><comments>http://www.cppblog.com/niewenlong/archive/2007/08/17/30213.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/niewenlong/comments/commentRss/30213.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/niewenlong/services/trackbacks/30213.html</trackback:ping><description><![CDATA[<div class=postcontent>欢迎阅读此篇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>什么是IOCP？<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>看来我们完全有理由相信IOCP是大型网络架构的首选。那IOCP到底是什么呢？<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>IOCP和网络又有什么关系？<br><br></font>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; bind(ListeningSocket, (SOCKADDR*)&amp;ServerAddr, sizeof(ServerAddr));<br>&nbsp;&nbsp;&nbsp; listen(ListeningSocket, 5);<br>&nbsp;&nbsp;&nbsp; int nlistenAddrLen = sizeof(ClientAddr);<br>&nbsp;&nbsp;&nbsp; while(TRUE)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NewConnection = accept(ListeningSocket, (SOCKADDR*)&amp;ClientAddr, &amp;nlistenAddrLen);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, (void*) NewConnection, 0, &amp;dwTreadId);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(hThread);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br><br>　　相信只要写过网络的朋友，应该对这样的结构在熟悉不过了。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>实现方法<br><br>Microsoft为IOCP提供了相应的API函数，主要的就两个，我们逐一的来看一下：<br>HANDLE CreateIoCompletionPort (<br>&nbsp;&nbsp;&nbsp; HANDLE FileHandle,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // handle to file<br>&nbsp;&nbsp;&nbsp; HANDLE ExistingCompletionPort,&nbsp; // handle to I/O completion port<br>&nbsp;&nbsp;&nbsp; ULONG_PTR CompletionKey, &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; // completion key<br>&nbsp;&nbsp;&nbsp; DWORD NumberOfConcurrentThreads // number of threads to execute concurrently<br>);<br><br>在讨论各参数之前，首先要注意该函数实际用于两个截然不同的目的：<br>1．用于创建一个完成端口对象<br>2．将一个句柄[HANDLE]和完成端口关联到一起<br><br>　　在创建一个完成一个端口的时候，我们只需要填写一下NumberOfConcurrentThreads这个参数就可以了。它告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下，所开线程数和CPU数量相同，但经验给我们一个公式：<br>　　线程数 = CPU数 * 2 + 2<br>要使完成端口有用，你必须把它同一个或多个设备相关联。这也是调用CreateIoCompletionPort完成的。你要向该函数传递一个已有的完成端口的句柄，我们既然要处理网络事件，那也就是将客户的socket作为HANDLE传进去。和一个完成键[对你有意义的一个32位值，也就是一个指针，操作系统并不关心你传什么]。每当你向端口关联一个设备时，系统向该完成端口的设备列表中加入一条信息纪录。<br><br>另一个API就是<br>BOOL GetQueuedCompletionStatus(<br>&nbsp;&nbsp;&nbsp; HANDLE CompletionPort, &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; <span>// handle to completion port<br>&nbsp;&nbsp;&nbsp; LPDWORD lpNumberOfBytes,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // bytes transferred<br>&nbsp;&nbsp;&nbsp; PULONG_PTR lpCompletionKey,&nbsp;&nbsp; // file completion key<br>&nbsp;&nbsp;&nbsp; LPOVERLAPPED *lpOverlapped,&nbsp;&nbsp; // buffer<br>&nbsp;&nbsp;&nbsp; DWORD dwMilliseconds &nbsp;&nbsp;&nbsp; &nbsp; 　 </span>// optional timeout value<br>);<br><br>第一个参数指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个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>至此文章也该告一段落了,我带着您做了一趟旋风般的旅游,游览了所谓的完成端口。 </div>
<img src ="http://www.cppblog.com/niewenlong/aggbug/30213.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/niewenlong/" target="_blank">聂文龙</a> 2007-08-17 11:03 <a href="http://www.cppblog.com/niewenlong/archive/2007/08/17/30213.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>