Tzot

We must accept finite disappointment, but we must never lose infinite hope. -- Mattin Luther King
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

2009年1月6日

虽然在目前项目开发中在使用WCF,但是一直没有系统的学习过。希望自己从现在开始能把WCF梳理一遍!希望和大家一起学习,一起进步。

1.概述

2.WCF契约设计--服务契约(Service Contracts)

3.WCF契约版本处理

4.异常与错误处理

5.绑定(Bingdings)

6.宿主(Hosting)

7.消息模式

8.实例模式

9.并发、吞吐量与限流

posted @ 2009-01-06 22:59 Tzot 阅读(53) 评论(0) 编辑

更新时间:2009-01-06

源代码

服务契约(Service Contracts)

ServiceContract– 定义服务操作

应用于接口或者类中,并使用接口(或类)上的 ServiceContractAttribute 属性定义服务协定。
• 建议应用于接口中
– 消除服务实现的耦合性
– 服务可能会实现多于1个契约,也就是说方便一个类的实现可以继承于多个服务契约的接口。如果将契约置于具体类上,那就不能实现继承多个契约,受制于c#类的单继承

MSDN备注:使用 ServiceContractAttribute 修饰的接口或类还必须至少拥有一个用 OperationContractAttribute 属性标记的方法以公开任意功能。

Name,指定 WSDL <portType> 元素中的协定名称。

Namespace指定 WSDL <portType> 元素中的命名空间。

SessionMode,指定协定是否需要支持会话的绑定。

ConfigurationName,指定要使用的配置文件中的服务元素的名称。

CallbackContract,指定双向(双工)对话中的返回协定。

HasProtectionLevel 和ProtectionLevel指示是否所有支持协定的消息都具有一个显式 ProtectionLevel 值,如果有,处于什么级别。

 

-------------------------------------解决方案------------------------------------------------------

image

 

-------------------------------------服务契约------------------------------------------------------

   /// <summary>
/// 人员管理接口
/// </summary>
// Namespace - 服务契约的命名空间,在WSDL中体现。
// Name - 服务契约的名称,在WSDL中体现。
// ConfigurationName - 服务契约在宿主中所配置的服务名称(默认情况下本例为类的全名“TZOT.WCF.Contract.IPersonManagerService”)
[ServiceContract(Namespace="http://www.cnblogs.com/netlife",
Name="PersonManager",
ConfigurationName="PersonManagerConfig")]
public interface IPersonManagerService
{
/// <summary>
/// 获取某人的姓名
/// </summary>
/// <param name="person">Person对象</param>
/// <returns>对应的名字</returns>
// Name - 操作契约的名称(会对应到相关的wsdl,默认情况下本例为方法名“GetName”)
[OperationContract(Name = "GetPersonName")]
string GetName(Person person);
}

-------------------------------------数据契约------------------------------------------------------

   /// <summary>
/// Person的实体类
/// </summary>
// Name - 数据契约的名称(会对应到相关的wsdl,默认情况下本例为类名“Person”)
[DataContract(Name = "PersonModel")]
public class Person
{
/// <summary>
/// Person的实体类的Age属性
/// </summary>
// Name - 数据成员的名称(会对应到相关的wsdl,默认情况下本例为属性名“Age”)
// IsRequired - 该值指示序列化引擎该成员在读取或反序列化时必须存在
// Order - 数据成员在相关的wsdl中的顺序
// EmitDefaultValue - 如果应该在序列化流中生成成员的默认值,则为 true,否则为 false,默认值为 true
[DataMember(Name = "PersonAge", IsRequired = false, Order = 1)]
public int Age { get; set; }
/// <summary>
/// Person的实体类的Name属性
/// </summary>
// Name - 数据成员的名称(会对应到相关的wsdl,默认情况下本例为属性名“Name”)
// IsRequired - 该值指示序列化引擎该成员在读取或反序列化时必须存在
// Order - 数据成员在相关的wsdl中的顺序
// EmitDefaultValue - 如果应该在序列化流中生成成员的默认值,则为 true,否则为 false,默认值为 true
[DataMember(Name = "PersonName", IsRequired = false, Order = 0)]
public string Name { get; set; }
}

-------------------------------------Hosting App.Config------------------------------------------------------

<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="TZOT.WCF.BusinessServices.PersonManagerService" behaviorConfiguration="serviceBehavior">
<endpoint address="" contract="PersonManagerConfig" binding="basicHttpBinding"/>
<!--contract= 是服务契约中配置的ConfigurationName “PersonManagerConfig”,为我们在服务契约中配置的代码。-->
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/PersonManagerService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>

-------------------------------------启动服务------------------------------------------------------

    class Program
{
static void Main(string[] args)
{
//提供服务的主机
ServiceHost personManagerServiceHosting = null;
try
{
personManagerServiceHosting = new ServiceHost(typeof(PersonManagerService));
personManagerServiceHosting.Open();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate Host");
Console.ReadLine();
}
finally
{
personManagerServiceHosting.Close();
}
}
}

-------------------------------------客户端App.Config------------------------------------------------------

<client>
<endpoint address="http://localhost:8000/PersonManagerService/"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_PersonManager"
contract="PersonManagerSerivceRefrence.PersonManager" name="BasicHttpBinding_PersonManager" />
</client>

-------------------------------------客户端添加服务自动生成的部分代码------------------------------------------------------

可以发现自动生成的接口名字为“PersonManager ”,即在服务契约中Name="PersonManager"的体现。

可以发现自动生成的命名空间为Namespace=http://www.cnblogs.com/netlife

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.cnblogs.com/netlife", ConfigurationName="PersonManagerSerivceRefrence.PersonManager")]
public interface PersonManager {
[System.ServiceModel.OperationContractAttribute(Action="http://www.cnblogs.com/netlife/PersonManager/GetPersonName", ReplyAction="http://www.cnblogs.com/netlife/PersonManager/GetPersonNameResponse")]
string GetPersonName(TZOT.WCF.Client.PersonManagerSerivceRefrence.PersonModel person);
}

posted @ 2009-01-06 22:46 Tzot 阅读(422) 评论(0) 编辑

2008年10月10日

NAT 类型及检测方法

STUN协议是一个客户机/服务器协议,在公网上存在着大量的STUN服务器,用户可以通过在自己主机上运行STUN客户端远程连接STUN服务器来确认自身的网络状况.

客户端主机所在网络可以分为以下类型:

1, Opened: 即主机拥有公网IP,并且没有防火墙,可自由与外部通信.

2, Full Cone NAT: 主机前有NAT设备,NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,并且从任意外部地址发送到该NAT设备UDP端口B的包都会被转到主机端口A.

3, Restricted cone NAT: 主机前有NAT设备,NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP发出到该NAT设备UDP端口B的包才会被转到主机端口A.

4, Port Restricted cone NAT: 主机前有NAT设备,NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP/PORT发出到该NAT设备UDP端口B的包才会被转到主机端口A.

5, Symmetric UDP Firewall: 主机出口处没有NAT设备,但有防火墙,且防火墙规则如下:从主机UDP端口A发出的数据包保持源地址,但只有从之前该主机发出包的目的IP/PORT发出到该主机端口A的包才能通过防火墙.

6, Symmetric NAT: 主机前有NAT设备,NAT规则如下:即使数据包都从主机UDP端A发出,但只要目的地址不同,NAT设备就会为之分配不同的出端口B.

7, Blocked: 防火墙限制UDP通信.

 

 1  /// <summary>
 2     /// Specifies UDP network type.
 3     /// </summary>
 4     public enum STUN_NetType
 5     {
 6         /// <summary>
 7         /// UDP is always blocked.
 8         /// </summary>
 9         UdpBlocked,
10 
11         /// <summary>
12         /// No NAT, public IP, no firewall.
13         /// </summary>
14         OpenInternet,
15 
16         /// <summary>
17         /// No NAT, public IP, but symmetric UDP firewall.
18         /// </summary>
19         SymmetricUdpFirewall,
20 
21         /// <summary>
22         /// A full cone NAT is one where all requests from the same internal IP address and port are 
23         /// mapped to the same external IP address and port. Furthermore, any external host can send 
24         /// a packet to the internal host, by sending a packet to the mapped external address.
25         /// </summary>
26         FullCone,
27 
28         /// <summary>
29         /// A restricted cone NAT is one where all requests from the same internal IP address and 
30         /// port are mapped to the same external IP address and port. Unlike a full cone NAT, an external
31         /// host (with IP address X) can send a packet to the internal host only if the internal host 
32         /// had previously sent a packet to IP address X.
33         /// </summary>
34         RestrictedCone,
35 
36         /// <summary>
37         /// A port restricted cone NAT is like a restricted cone NAT, but the restriction 
38         /// includes port numbers. Specifically, an external host can send a packet, with source IP
39         /// address X and source port P, to the internal host only if the internal host had previously 
40         /// sent a packet to IP address X and port P.
41         /// </summary>
42         PortRestrictedCone,
43 
44         /// <summary>
45         /// A symmetric NAT is one where all requests from the same internal IP address and port, 
46         /// to a specific destination IP address and port, are mapped to the same external IP address and
47         /// port.  If the same host sends a packet with the same source address and port, but to 
48         /// a different destination, a different mapping is used. Furthermore, only the external host that
49         /// receives a packet can send a UDP packet back to the internal host.
50         /// </summary>
51         Symmetric
52     }

 

测试过程

STUN服务器运行在UDP协议之上,它具有两个固定公网地址,能完成以下几个功能:

1. 告诉STUN客户端经NAT设备映射后的公网地址.

2. 根据STUN客户端的要求,从服务器的其他不同IP或端口向客户端回送包.

如何根据STUN服务器提供的功能来确认网络类型呢? rfc3489给出了如下图过程:

clip_image002

这个过程可概括如下:

1, STUN客户端向STUN服务器发送请求,要求得到自身经NAT映射后的地址:

a,收不到服务器回复,则认为UDP被防火墙阻断,不能通信,网络类型:Blocked.

b,收到服务器回复,对比本地地址,如果相同,则认为无NAT设备,进入第2步,否则认为有NAT设备,进入3步.

2, (已确认无NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:

a,收不到服务器从其他IP地址的回复,认为包被前置防火墙阻断,网络类型:Symmetric UDP Firewall.

b,收到则认为客户端处在一个开放的网络上,网络类型:Opened.

3, (已确认存在NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT向客户端回复包:

a,收不到服务器从其他IP地址的回复,认为包被前置NAT设备阻断,进入第4步.

b,收到则认为NAT设备类型为Full Cone,即网络类型:Full Cone NAT.

4, STUN客户端向STUN服务器的另外一个IP地址发送请求,要求得到自身经NAT映射后的地址,并对比之:

a,地址不相同,则网络类型:Symmetric NAT.

b,相同则认为是Restricted NAT,进入第5步,进一步确认类型.

5, (已确认Restricted NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从相同IP的其他PORT向客户端回复包:

a,收不到服务器从其他PORT地址的回复,认为包被前置NAT设备阻断,网络类型:Port Restricted cone NAT.

b,收到则认为网络类型: Restricted cone NAT.

posted @ 2008-10-10 16:29 Tzot 阅读(1741) 评论(1) 编辑

2008年9月27日

以下主要内容取之WebCast

从耦合关系谈起

耦合关系直接决定着软件面对变化时的行为

– 模块与模块之间的紧耦合使得软件面对变化时,相关的模块都要随之更改。

– 模块与模块之间的松耦合使得软件面对变化时,一些模块更容易被替换或者。

动机(Motivation)
在软件系统中,经常面临着“某个对象”的创建工作;由于需求的变化,这个对象经常面临着剧烈的变化,但是它却拥有比较稳定的接口。
如何应对这种变化?如何提供一种“封装机制”来隔离出“这个易变对象”的变化,从而保持系统中“其他依赖该对象的对象”不随着需求改变而改变?

意图(Intent)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。
——《设计模式》GoF

结构(Structure)

2008-09-27_11-16-55

 

Factory Method模式的几个要点
• Factory Method模式主要用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系会导致软件的脆弱。
• Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
• Factory Method模式解决“单个对象”的需求变化,Abstract Factory 模式解决“系列对象”的需求变化,Builder模式解决“对象部分”的需求变化。

posted @ 2008-09-27 11:29 Tzot 阅读(146) 评论(0) 编辑

2008年9月15日

动机(Motivation)

在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?

意图(Intent)
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
——《设计模式》GoF

抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象。

适用性

文献【GOF95】指出,在以下情况下应当考虑使用工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。
这个系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。
系统提供一个产品的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

结构(Structure)

Abstract Factory

 代码实现

 

 1    public abstract class ProductA
 2    {
 3        
 4    }

 5
 6    public class ProductA1 : ProductA
 7    {
 8
 9    }

10
11    public class ProductA2 : ProductA
12    {
13
14    }

15
16    public abstract class ProductB
17    {
18
19    }

20
21    public class ProductB1 : ProductB
22    {
23
24    }

25
26    public class ProductB2 : ProductB
27    {
28
29    }

 

 1public abstract class Creator
 2    {
 3    }

 4
 5    public abstract class ConcreteCreatorA : Creator
 6    {
 7        public ProductA FactoryA()
 8        {
 9            return new ProductA1();
10        }

11
12        public ProductB FactoryB()
13        {
14            return new ProductB1();
15        }

16    }

17
18    public abstract class ConcreteCreatorB : Creator
19    {
20        public ProductA FactoryA()
21        {
22            return new ProductA2();
23        }

24
25        public ProductB FactoryB()
26        {
27            return new ProductB2();
28        }

29    }

Abstract Factory模式的几个要点
? 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的静态工厂完全可以。
? “系列对象”指的是这些对象之间有相互依赖、或作用的关系,例如游戏开发场景中的“道路”与“房屋”的依赖,“道路”与“地道”的依赖。
? Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
? Abstract Factory模式经常和Factory Method模式共同组合来应对“对象创建”的需求变化。

posted @ 2008-09-15 17:57 Tzot 阅读(118) 评论(0) 编辑

2008年9月8日

原文地址:http://e2tox.cnblogs.com/archive/2006/07/13/449836.html

UPNP的全称是 Universal plug-and-play( 通用即插即用).UPnP 是针对智能家电、无线设备以及各种外观尺寸的个人电脑的普遍对等(peer-to-peer)网络连接而设计的一种架构。它旨在为家庭、小型企业、公共场所中或连接到互联网的ad-hoc 网或未管理网络提供易于使用、灵活且基于标准的连接。(引自这里.)

  我们这里用到的自动端口映射只是UPNP的一个小应用。按照UPNP的相关规范,UPNP网络的第0步是寻址(获得一个IP地址,在我要解决的问题中这不是一个问题。)       
    第1步是发现,控制点在网上搜索感兴趣的设备,而设备向网络中的控制点宣告其服务。对于自动端口映射来说就是发现带UPNP功能的路由器。
发现这个过程主要有两步。第一,使用数据报套接字向239.255.255.250:1900,发送一条多播请求,格式如下

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN:"ssdp:discover"
MX:3
ST:UPnP:rootdevice

      这个多播请求的含义如下:M-SEARCH SSDP协议定义的搜索请求方法。HOST必须是这个多播地址。MAN的值也必须是"ssdp:discover" 不可少了双引号。MX的含义是最长等待时间,可以自己设置。ST表示search target 搜索目标。我们在这里用找根设备。另外在编程中我们要在每一行后面加上"rn" 表示换行。(详见源码 UPNPNAT.discovery()).

      第二步,如果你的网络存在一个UPNP设备的话,为了被找到,设备必须向发送查找请求的多播通道的源 IP 地址与端口发送响应信息。所以你可以从239.255.255.250:1900这个地址接收到响应消息。类似下面的消息。

HTTP/1.1 200 OK
CACHE-CONTROL: max-age=100
DATE: Sun, 15 Jan 2006 06:51:02 GMT
EXT:
LOCATION: http://192.168.14.1:1900/igd.xml
SERVER: TP-LINK Wireless Router WR541G/5, UPnP/1.0
ST: upnp:rootdevice
USN:uuid:upnp-InternetGatewayDevice-192168141678900001::upnp:rootdevice

      接下来我们要从里面获得我们要的消息。首先,我们必须找到" 200 OK ",说明没有错误发生,否则一切免谈。接着,我们要找到LOCATION项,获得设备描述URL。(程序中的处理归根到底就是一个子字符的查找。)

       到这里,我们的第一步“发现”完成。
   第2步是描述。在第1步中我们往往能获得一个设备的描述URL,在第2步中我们要通过一个URL,下载一个XML文件。并从中找到有关设备的类型,服务类型,控制URL,事件触发URL等。
我们同样分两步进行,首先下载设备描述文件。(请看源码中UPNPNAT::get_description()函数)
    1.解析描述文件的URL,获得主机(host)、端口(port)、路径(path).(parseUrl函数)
    2.连接到host:port (tcp_connect 函数)
    3.构造类似

                                                GET path HTTP/1.1
                                                Host: host:port

的信息(第二行下要一个空行),并通过刚才的TCP 套接字,发送到路由器。(sprintf ,send 函数).

    4.接收数据,我使用flag为 MSG_WAITALL的recv函数,函数一直阻塞直到数据全部读完。 数据最终保存在std::string description_info中。

    我想通过浏览器下载这个文件的过程是类似的吧。

    然后,解析这个XML文件。(请看源码 中UPNPNAT:: parser_description()函数)

我们找到"root"的"deviceType"是"urn:schemas-upnp-org:device:InternetGatewayDevice:1"的"device" childNode ,获得这个"device"的"deviceList",记为A。

    找到A的"deviceType"是"urn:schemas-upnp-org:device:WANDevice:1"的"device" childNode ,获得这个"device"的"deviceList",记为B。

    找到B的"deviceType"是"urn:schemas-upnp-org:device:WANConnectionDevice:1"的"device" childNode ,获得这个"device"的"serviceList",记为C。

    找到C的"serviceType"是"urn:schemas-upnp-org:service:WANIPConnection:1" 或"urn:schemas-upnp-org:service:WANPPPConnection:1" 的"service"  childNode ,记为D.
    获得D的"controlUrl"保存在std::string control_url中。

    但是这里获得control_url一般为相对URL,所以要从"root"下面,找到"URLBase"的值,(如果是空,则用describe_url的"htpp://xxx.xxx.xxx.xxx:xxxx"部分代替.)

    最后在相对的control_url前加上URLBasr 获得完整的control_url.

    至此,第二步“获得控制URL”完成。

    第3步是控制。通过第2步获得的控制URL,通过向其发送控制消息(同样用XML描述)来实现某些功能。对于自动端口映射来说就是查看、增加、删除等。
在这里我先把各种控制信息的格式说明一下。(下面的rn都是表示换行,我输入不了反斜杠。)

  • 增加端口映射。 "AddPortMapping"
  • "<NewRemoteHost></NewRemoteHost>rn"                              "<NewExternalPort>ExternalPort</NewExternalPort>rn"                               "<NewProtocol>Protocol</NewProtocol>rn"                                      "<NewInternalPort>InternalPort</NewInternalPort>n"
    "<NewInternalClient>InternalClient</NewInternalClient>rn"                
    "<NewEnabled>1</NewEnabled>rn"
    "<NewPortMappingDescription>PortMappingDescription"       "</NewPortMappingDescription>rn"      
    "<NewLeaseDuration>LeaseDuration</NewLeaseDuration>rn"
  • 删除端口映射 "DeletePortMapping"
  • "<NewRemoteHost></NewRemoteHost>rn" "<NewExternalPort>ExternalPort</NewExternalPort>rn"   "<NewProtocol>Protocol</NewProtocol>rn"
  • 获得端口映射信息 "GetGenericPortMappingEntry"
  • "<NewPortMappingIndex>PortMappingIndex</NewPortMappingIndex>"  "<NewRemoteHost></NewRemoteHost>rn"   "<NewExternalPort></NewExternalPort>rn" "<NewProtocol></NewProtocol>rn"     "<NewInternalPort></NewInternalPort>rn" "<NewInternalClient></NewInternalClient>rn" "<NewEnabled>1</NewEnabled>rn"  "<NewPortMappingDescription>"                                          "</NewPortMappingDescription>rn"           "<NewLeaseDuration></NewLeaseDuration>rn"

     其中斜体部分需要在编程是填入的。ExternalPort 外部端口。InternalPort内部端口。这 两者一般就填映射的端口。Protocal 填TCP或UDP。InterClient 一般就是本地IP地址。PortMappingDescription 填写端口映射的描述,比如什么程序建立了这个端口。LeaseDuration 是映射的持续时间,用0表示不永久。PortMappingIndex 是端口映射索引,路由上第几个映射。

     我们再来看下面这个XML文档结构。

     "<?xml version="1.0" encoding="utf-8"?>rn"
     "<s:Envelope xmlns:s="
     ""http://schemas.xmlsoap.org/soap/envelope/" "
     "s:encodingStyle="                                  
     ""http://schemas.xmlsoap.org/soap/encoding/">rn"
     "<s:Body>rn"
     "<u:actionName xmlns:u="serviceType">rn"
     "actionParams</u:actionName>rn"
     "</s:Body>rn"
     "</s:Envelope>rn"

     我们在actionName 处填入"AddPortMapping" "DeletePortMapping" "GetGenericPortMappingEntry"。serviceType 处填入设备的服务类型。"urn:schemas-upnp-org:service:WANIPConnection:1"或"urn:schemas-upnp-org:service:WANPPPConnection:1"。actionParams 填入上面的各种控制信息。

    最后在前面加上HTTP头。

    "POST path HTTP/1.1rn"
    "HOST: host:portrn"     
    "SOAPACTION:"serviceType#actionName"rn" 
    "CONTENT-TYPE: text/xml ; charset="utf-8"rn"
    "Content-Length: contentLength rnrn"

    path host port 意思很明显。contentLength面那个XML文档的长度。

  然后连接到host:port,发送到整个信息即可完成控制            

    第4步事件触发和第5步展示在自动端口映射没用用到。有兴趣可以自己看文档。

posted @ 2008-09-08 17:08 Tzot 阅读(396) 评论(0) 编辑

原文转自:http://blog.csdn.net/colinchan/archive/2006/05/08/712773.aspx

一:基本术语

防火墙

防火墙限制了私网与公网的通信,它主要是将(防火墙)认为未经授权的的包丢弃,防火墙只是检验包的数据,并不修改数据包中的IP地址和TCP/UDP端口信息。

网络地址转换(NAT)

当有数据包通过时,网络地址转换器不仅检查包的信息,还要将包头中的IP地址和端口信息进行修改。以使得处于NAT之后的机器共享几个仅有的公网IP地址(通常是一个)。网络地址转换器主要有两种类型.

P2P应用程序

P2P应用程序是指,在已有的一个公共服务器的基础上,并分别利用自己的私有地址或者公有地址(或者两者兼备)来建立一个端到端的会话通信。

P2P防火墙

P2P防火墙是一个提供了防火墙的功能的P2P代理,但是不进行地址转换.

P2P-NAT

P2P-NAT 是一个 P2P代理,提供了NAT的功能,也提供了防火墙的功能,一个最简的P2P代理必须具有锥形NAT对Udp通信支持的功能,并允许应用程序利用Udp打洞技术建立强健的P2P连接。

回环转换

当NAT的私网内部机器想通过公共地址来访问同一台局域网内的机器的时,NAT设备等价于做了两次NAT的事情,在包到达目标机器之前,先将私有地址转换为公网地址,然后再将公网地址转换回私有地址。我们把具有上叙转换功能的NAT设备叫做“回环转换”设备。

二:NAT分类

可以分为基础NAT网络地址和端口转换(NAPT)两大类

(一):基础NAT

基础NAT 将私网主机的私有IP地址转换成公网IP地址,但并不将TCP/UDP端口信息进行转换。基础NAT一般用在当NAT拥有很多公网IP地址的时候,它将公网IP地址与内部主机进行绑定,使得外部可以用公网IP地址访问内部主机。(实际上是只将IP转换,192.168.0.23 <-> 210.42.106.35,这与直接设置IP地址为公网IP还是有一定区别的,特别是对于企业来说,外部的信息都要经过统一防火墙才能到达内部,但是内部主机又可以使用公网IP)

(二):网络地址和端口转换 (NAPT)

这是最普遍的情况,网络地址/端口转换器检查、修改包的IP地址和TCP/UDP端口信息,这样,更多的内部主机就可以同时使用一个公网IP地址。

请参考[RFC1631]和[RFC2993]及[RFC2663]这三个文档了解更多的NAT分类和术语信息。另外,关于NAPT的分类和术语,[RFC2663]做了更多的定义。当一个内部网主机通过NAT打开一个“外出”的TCP或UDP会话时,NAPT分配给这个会话一个公网IP和端口,用来接收外网的响应的数据包,并经过转换通知内部网的主机。这样做的效果是,NAPT在 [私有IP:私有端口] 和[公网IP:公网端口]之间建立了一个端口绑定。

端口绑定指定了NAPT将在这个会话的生存期内进行地址转换任务。这中间存在一个这样的问题,如果P2P应用程序从内部网络的一个[私有IP地址:端口]对同时发出多条会话给不同的外网主机,那么NAT会怎样处理呢?这又可以分为锥形NAT (CONE NAT)与对称NAT (SYMMTRIC NAT)两大类来考虑:

A.锥形NAT

(为什么叫做锥形呢?请看以下图形,终端和外部服务器,都通过NAT分派的这个绑定地址对来传送信息,就象一个漏斗一样,筛选并传递信息)

  当建立了一个 [私有IP:端口]-[公网IP:端口] 端口绑定之后,对于来自同一个[私有IP:端口]会话,锥形NAT服务器允许发起会话的应用程序 重复使用这个端口绑定,一直到这个会话结束才解除(端口绑定)。

  例如,假设 Client A(IP地址信息如上图所示)通过一个锥形NAT 同时发起两个外出的连接,它使用同一个内部端口(10.0.0.1:1234)给公网的两台不同的服务器,S1和S2。锥形NAT 只分配一个公网IP和端口(155.99.25.11:62000)给这个两个会话,通过地址转换可以确保 Client使用端口的“同一性”(即这个Client只使用这个端口)。而基础NATs和防火墙却不能修改经过的数据包端口号,它们可以看作是锥形NAT的精简版本。

进一步分析可以将CONE NAT受限制锥形NAT (RESTRICT CONE)端口受限锥形NAT (PORT RESTRICT CONE) 三大类,以下是详细论述: 分为全双工锥形NAT (FULL CONE) ,

1.全双工锥形NAT

当内部主机发出一个“外出”的连接会话,就会创建了一个公网/私网 地址,一旦这个地址对被创建,全双工锥形NAT会接收随后任何外部端口传入这个公共端口地址的通信。因此,全双工锥形NAT有时候又被称为"混杂"NAT。

2.受限制锥形NAT

受限制的锥形NAT会对传入的数据包进行筛选,当内部主机发出“外出”的会话时,NAT会记录这个外部主机的IP地址信息,所以,也只有这些有记录的外部IP地址,能够将信息传入到NAT内部,受限制的锥形NAT 有效的给防火墙提炼了筛选包的原则——即限定只给那些已知的外部地址“传入”信息到NAT内部。

3.端口受限锥形NAT

端口受限制的锥形NAT,与受限制的锥形NAT不同的是:它同时记录了外部主机的IP地址和端口信息,端口受限制的锥形NAT给内部节点提供了同一级别的保护,在维持端口“同一性”过程中,将会丢弃对称NAT传回的信息。

B.对称NAT

对称NAT,与Cone NAT是大不相同的,并不对会话进行端口绑定,而是分配一个全新的公网端口 给每一个新的会话。

还是上面那个例子:如果 Client A (10.0.0.1:1234)同时发起两个 "外出" 会话,分别发往S1和S2。对称Nat会分配公共地址155.99.25.11:62000给Session1,然后分配另一个不同的公共地址155.99.25.11:62001给Session2。对称Nat能够区别两个不同的会话并进行地址转换,因为在 Session1 和 Session2中的外部地址是不同的,正是因为这样,Client端的应用程序就迷失在这个地址转换边界线了,因为这个应用程序每发出一个会话都会使用一个新的端口,无法保障只使用同一个端口了。

在TCP和UDP通信中,(到底是使用同一个端口,还是分配不同的端口给同一个应用程序),锥形NAT和对称NAT各有各的理由。当然锥形NAT在根据如何公平地将NAT接受的连接直达一个已创建的地址对上有更多的分类。这个分类一般应用在Udp通信(而不是Tcp通信上),因为NATs和防火墙阻止了试图无条件传入的TCP连接,除非明确设置NAT不这样做。

三:NAT对session的处理

以下分析NAPT是依据什么策略来判断是否要为一个请求发出的UDP数据包建立Session的.主要有一下几个策略:

A. 源地址(内网IP地址)不同,忽略其它因素, 在NAPT上肯定对应不同的Session

B. 源地址(内网IP地址)相同,源端口不同,忽略其它的因素,则在NAPT上也肯定对应不同的Session

C. 源地址(内网IP地址)相同,源端口相同,目的地址(公网IP地址)相同,目的端口不同,则在NAPT上肯定对应同一个Session

D. 源地址(内网IP地址)相同,源端口相同,目的地址(公网IP地址)不同,忽略目的端口,则在NAPT上是如何处理Session的呢?

A,B,C三种情况的都是比较简单的,可以很容易的实现.而D的情况就比较复杂了.所以D情况才是我们要重点关心和讨论的问题。

四:完全解决方案

以下针对四种SESSION与四种NAT的完全解决方案,为了方便将使用以下缩写形式:

C代表 CONE NAT

S代表SYMMETRIC NAT,

FC代表 FULL CONE NAT,

RC代表 RESTRICT CONE NAT,

PC 代表 PORT RESTRICT CONE NAT.

首先依据CLIENT (客户)端在NAT后 的个数不同可以分为两大类:

TYPE ONE :一个在NAT后 + 一个在公网中.

这种情况下可以分为两大类:

A. S VS 公网:此种情况下,由于公网的地址在一个SESSION内是不变的,所以可以打洞是可以成功的.

B. C VS 公网: 与上面类似,这种情口下打洞是可以成功的.

TYPE TWO:两个客户都在NAT后面.

这种情况下也可以细分为两大类:

A. 其中一个NAT 是 S(SYMMETRIC NAT) 的,既:S VS C或者是S VS S .

下面论证这种情口下按照常规打洞是行不通的,在常规打洞中,所有的客户首先登陆到一个服务器上去.服务器记录下每个客户的[公网IP:端口],然后在打洞过程中就使用这个记录的值,然而对于S型的NAT来说,它并不绑定[私网IP:端口]和[公网IP:端口]的映射.所以在不同的SESSION中,NAT将会重新分配一对[公网IP:端口].这样一来对于S型的NAT来说打洞的[公网IP:端口]与登记在服务器上的[公网IP:端口]是不同的.而且也没有办法将打洞的[公网IP:端口]通知到另一个位于NAT下的客户端, 所以打洞是不会成功的.然而如果另一个客户端是在公网时,打洞是可以的.前面已经论证了这种情况.

这种情况下的解决方案是只能通过端口预测来进行打洞,具体解决方法如下:例如(以两个都是S型的为例) NAT A 分配了它自己的UDP端口62000,用来保持 客户端A 与服务器S的通信会话, NAT B 也分配了31000端口,用来保持客户端B与服务器S 的通信会话。通过与 服务器S的对话,客户端A 和 客户端B都相互知道了对方所映射的真实IP和端口。

客户端A发送一条UDP消息到138.76.29.7:31001(请注意到端口号的增加),同时客户端B发送一条UDP消息到155.99.25.11:62001。如果NAT A 和NAT B继续分配端口给新的会话,并且从A-S和B-S的会话时间消耗得并不多的话,那么一条处于客户端A和客户端B之间的双向会话通道就建立了。

客户端A发出的消息送达B导致了NAT A打开了一个新的会话,并且我们希望NAT A将会指派62001端口给这个新的会话,因为62001是继62000后,NAT会自动指派给 从服务器S到客户端A之间的新会话的端口号;类似的,客户端B发出的消息送达A导致了 NAT B打开了一个新的会话,并且我们希望 NAT B将会指派31001这个端口给新的会话;如果两个客户端都正确的猜测到了对方新会话被指派的端口号,那么这个 客户端A-客户端B的双向连接就被打通了。其结果如下图所示:

明显的,有许多因素会导致这个方法失败:如果这个预言的新端口(62001和31001) 恰好已经被一个不相关的会话所使用,那么NAT就会跳过这个端口号,这个连接就会宣告失败;如果两个NAT有时或者总是不按照顺序来生成新的端口号,那么这个方法也是行不通的。

如果隐藏在NATA后的一个不同的客户端X(或者在NAT B后)打开了一个新的“外出”UDP 连接,并且无论这个连接的目的如何;只要这个动作发生在客户端A 建立了与服务器S的连接之后,客户端A 与 客户端B 建立连接之前;那么这个无关的客户端X 就会趁人不备地“偷” 到这个我们渴望分配的端口。所以,这个方法变得如此脆弱而且不堪一击,只要任何一个NAT方包含以上碰到的问题,这个方法都不会奏效。

在处于 cone NAT 系列的网络环境中这个方法还是实用的;如果有一方为 cone NAT 而另外一方为 symmetric NAT,那么应用程序就应该预先发现另外一方的 NAT 是什么类型,再做出正确的行为来处理通信,这样就增大了算法的复杂度,并且降低了在真实网络环境中的普适性。

    最后,如果P2P的一方处在两级或者两级以上的NAT下面,并且这些NATS 接近这个客户端是SYMMETRIC NAT的话,端口号预言是无效的!

因此,并不推荐使用这个方法来写新的P2P应用程序,这也是历史的经验和教训!

B. 两个都是CONE NAT型.

这种情况下可以分为六大类型:

A: FC + FC

B: FC + RC

C: FC + PC

D: PC + RC

E: PC + PC

F: RC + RC

虽然有这么多种情况,但是由于CONE NAT 的特性,所以还是很好办的,因为对于CONE NAT 来说,在同一个SESSION中它会绑定一对[私网IP:端口]和[公网IP:端口]的映射,所以它们打洞用的[公网IP:端口]与登记在服务器上的[公网IP:端口]是一致的,所以打洞是可以行的通的.

综上所述,就已经完全的概括了所有类型的NAT之间的可能的通信情况了.并且都提供了可行的解决方案.

五:对前一阶段的总结

1.前一阶段使用的打洞方法是有缺陷的,它只适应于两个都是FULL CONE NAT的类型的CLIENT(客户端).以下论证它不适应于两个都是CONE NAT的类型中的

B: FC + RC

C: FC + PC

D: PC + RC

E: PC + PC

F: RC + RC

这五种情况.

因为对于受限的NAT它登记了外出包的[IP地址&端口],它仅仅接受这些已登记地址发过来的包,所以它们报告服务器的端口只能接受来自服务器的包.不能接受来自另一客户端的包.所以前一阶段的打洞方法是不可行的.

六: 存在的问题

按照理论.NAT将在一定时间后关闭UDP的一个映射,所以为了保持与服务器能够一直通信,服务器必须要发送UDP心跳包,来保持映射不被关闭.这就需要一个合适的时间值.

posted @ 2008-09-08 15:19 Tzot 阅读(634) 评论(0) 编辑

2008年9月1日

摘要: 定义: 作为对象的创建模式[GOF95], Singleton模式确保其一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单态类。单态类有以下几个特点: 单态类只能有一个实例。 单态类必须自己创建自己的这个实例。 单态类必须给所有其他对象提供这个实例。 以下是单态模式的几个实现方法:1 无线程安全。[代码]上面这个写法是没有线程安全,当有两个线程同时走到if (instance...阅读全文

posted @ 2008-09-01 15:31 Tzot 阅读(275) 评论(3) 编辑

2008年8月28日

摘要: 第一次翻译文章,错误在所难免。让大家见笑了!还希望高手多指点。。 Windows Form应用程序在内存使用方面显得非常臃肿。主要是因为.NET应用程序在启动的时候有大量的footprint被JIT编译器加载,并且所有的链代码和WinForms引擎在启动时候被编译,并加载到程序的进程。这一过程占用了处理器时间片的同时也占用了大量的内存。JIT在确定哪些代码应该被编译已经做的非常好了,大部分其编译的...阅读全文

posted @ 2008-08-28 16:27 Tzot 阅读(816) 评论(1) 编辑

摘要: Windows Forms apps are pretty bloated in terms of memory usage. The main reason .Net apps have such a huge footprint is that the JIT compiler loads when the app starts and all that bootstrap code and ...阅读全文

posted @ 2008-08-28 14:34 Tzot 阅读(285) 评论(1) 编辑