﻿<?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)GetProc