WCF后续之旅(14):TCP端口共享

在基于TCP/IP协议簇的对等网络通信下,相互通信的应用程序运行各自的进程中,出于应用层的进程将数据局封装成数据报,并通过传输层的TCP或者UDP进行网络通信。而TCP和UPD则通过一个16bit的端口来识别不同的应用程序。

对于一些常用网络服务,他们都有一个知名的端口好与之匹配。比如,FTP服务是用的TCP端口为21;Telnet服务的TCP端口为23等等。而对于客户端通常对所使用的端口并不关心,只需要保证端口在本机是唯一的就可以了,这样的端口又成为临时端口,临时端口一般在1024到5000之间。

一般来讲,在某一个时刻,一个端口只能供一个应用程序使用。对于WCF来说,当我们通过一个托管的应用程序对某个服务进行寄宿的时候,一个端口被该应用程序独占使用。如何多个寄宿进行使用相同的端口

在下面的例子中,我通过两个不同的控制台应用程序对两个服务,Service1和Service2进行寄宿,两个服务的终结点地址共享相同的端口:9999  

   1: using(ServiceHost serviceHost = new ServiceHost(typeof(Service1)))
   2: {
   3:        serviceHost.AddServiceEndpoint(typeof(IService1), new NetTcpBinding(), "net.tcp://127.0.0.1:9999/service1");
   4:        serviceHost.Open();
   5:        Console.Read();
   6: } 
   1: using(ServiceHost serviceHost = new ServiceHost(typeof(Service2)))
   2: {
   3:        serviceHost.AddServiceEndpoint(typeof(IService2), new NetTcpBinding(), "net.tcp://127.0.0.1:9999/service2");
   4:        serviceHost.Open();
   5:        Console.Read();
   6: } 

当我们先后运行这两个服务寄宿应用程序,第一个能够正常运行,但是对于第二个,则会抛出如下一个AdressAlreadyInUseException异常,错误信息为:

IP 终结点 127.0.0.1:9999 上已有侦听器。请确保未在应用程序中多次尝试使用该终结点,并确保没有其他应用程序在侦听该终结点。

TCP Port Sharing2

在本节中,我们将介绍如何解决这种端口被某一个应用程序独占使用的问题,让不同的监听程序能够共享同一个端口。在这之前,我们需要了解一下,端口的共享具有什么现实的意义。

一、 端口共享在WCF中的意义何在?

在一般的网络环境中,尽可能避免网络攻击,都会通过防火墙将绝大部分的端口封掉,仅仅保留那些常用的网络服务所用的端口,或者为某一个类应用保留少量的端口。总而言之,我们不能保证每个跨防火墙通信的应用都具有一个唯一的端口,他们只能共享一个或者少量的几个端口。

对于Intranet内部,为了保证部署于局域网内的其他计算机的网络应用能够与本机进行正常通信,通常会在本机的防火墙中预留一个可用的端口。Intranet内部的主机之间可以使用这些预留的端口通过相应的传输协议,比如TCP、HTTP、Named Pipe等等,进行通信。而对于处于Internet和本地网络之间的防火墙,通常仅仅只有保留80端口,保证基于HTTP的网络通信能够正常进行。

所以,无论是基于Intranet还是Internet,无论是采用何种传输协议,端口共享——让多个网络应用程序使用相同的端口进行通信,都具有重要的现实意义。

对于WCF来讲,当我们将某个服务寄宿于一个进程中,实际上就是通过该进程监听和处理来自客户端的Socket请求。在一般情况下,一个端口被一个监听进行独占使用,也就是说,如何你的主机上部署了若干服务,而这些服务寄宿于不同的应用程序中,对于这种寄宿应用程序来说,监听的端口必须不同。

所以,我们需要通过特殊的途径实现基于WCF寄宿的端口共享。对于采用不同的传输协议,我们有不同的解决方案,对于HTTP协议,我们可以通过IIS的寄宿方式实现端口的共享,对于TCP,.NET Framework3.0提供了一个特殊的Windows服务,Net.TCP Port Sharing Service,帮助我们轻松的实现端口的共享。我们接下来就讨论这种端口共享解决方案。

二、Net.TCP Port Sharing Service

从功能上讲,Net.TCP Port Sharing Service实现了和HTTP.SYS相同的功能:请求的监听和分发(request listening and dispatching)。唯一不同是,HTTP.SYS运行在内核模式(Kernel Mode)下,而Net.TCP Port Sharing Service运行在用户模式(User Mode)下。

WCF对Net.TCP Port Sharing Service提供了原生的支持。Net.TCP Port Sharing Service在WCF的实现原理如下图所示:在Net.TCP Port Sharing Service开启的状态下,如果我们通过两个服务寄宿应用程序分别寄宿两个服务,Service1和Service2,并且它们共享一样的监听端口:8888。实际上,当ServiceHost的Open方法被执行的时候,WCF会将这两个地址,net.tcp://artech.com:8888/service1和net.tcp://artech.com:8888/service2注册到Net.TCP Port Sharing Service中。而对于Net.TCP Port Sharing Service来说,在其内部维护者一个目的地址和进程的列表,在进行目的地址注册的时候,会将这两个地址和对应的服务寄宿地址的匹配关系添加到该列表之中。

当我们的服务客户端,proxy1和proxy2,分别调用service1和service2。当基于他们各自服务调用的socket连接请求抵达artech.com的时候,Net.TCP Port Sharing Service会截获请求消息,并获取目的地址。根据该目的地址,结合内部维护的目的地址和目标进程匹配列表,Net.TCP Port Sharing Service得到对应的目标应用程序,并将请求消息向真正的目标程序进行转发。

TCP Port Sharing

三 、基于TCP端口共享的编程

由于WCF下基于TCP的端口共享是建立在Net.TCP Port Sharing Service Windows服务上的。所有安装有.NET Framework3.0的操作系统都具有该Windows服务,但是在默认的情况下,该服务是不可用的。当你第一次使用Net.TCP Port Sharing Service,或者发现该服务被禁用,你需要手工的启用该服务。

注:通过“开始”-〉“控制面板”-〉“管理工具”-〉服务,打开如下图所示的“服务对话框”,然后定位到Net.TCP Port Sharing Service。

在基于TCP的WCF通信中,我们使用NetTcpBinding处理通信的所有细节,这些细节中也包括端口的共享。为此在NetTcpBinding中,定义了一个特殊的属性,PortSharingEnabled,表明是否启动端口共享机制。

 

   1: public class NetTcpBinding : Binding, IBindingRuntimePreferences
   2: {
   3:       // ... ...
   4:       public bool PortSharingEnabled { get; set; }
   5: } 

如何我们以代码的方式进行服务的寄宿的话,我们仅仅需要将终结点的NetTcpBinding上将该属性设为True就可以了:

 

   1: using(ServiceHost serviceHost = new ServiceHost(typeof(Service1)))
   2: {
   3:       NetTcpBinding binding = new NetTcpBinding();
   4:       binding.PortSharingEnabled = true;
   5:       serviceHost.AddServiceEndpoint(typeof(IService1),binding,"net.tcp://127.0.0.1:9999/service1");
   6:       serviceHost.Open();
   7:       Console.Read();
   8: } 

当然,你也通过通过培植的方式来指定NetTcpBinding的PortSharingEnabled属性:

   1: <configuration>
   2:     <system.serviceModel>
   3:         <bindings>
   4:             <netTcpBinding>
   5:                 <binding name="portSharingBinding" portSharingEnabled="true" />
   6:             </netTcpBinding>
   7:         </bindings>
   8:         <services>
   9:             <service name="Artech.WcfServices.Services.CalculateService">
  10:                 <endpoint binding="netTcpBinding"   bindingConfiguration="portSharingBinding"
  11:                     contract="Artech.WcfServices.Contracts.ICalculate" />
  12:             </service>
  13:         </services>
  14:     </system.serviceModel>
  15: </configuration>

WCF后续之旅: 
WCF后续之旅(1): WCF是如何通过Binding进行通信的 
WCF后续之旅(2): 如何对Channel Layer进行扩展——创建自定义Channel 
WCF后续之旅(3): WCF Service Mode Layer 的中枢—Dispatcher 
WCF后续之旅(4):WCF Extension Point 概览 
WCF后续之旅(5): 通过WCF Extension实现Localization 
WCF后续之旅(6): 通过WCF Extension实现Context信息的传递 
WCF后续之旅(7):通过WCF Extension实现和Enterprise Library Unity Container的集成 
WCF后续之旅(8):通过WCF Extension 实现与MS Enterprise Library Policy Injection Application Block 的集成 
WCF后续之旅(9):通过WCF的双向通信实现Session管理[Part I] 
WCF后续之旅(9): 通过WCF双向通信实现Session管理[Part II] 
WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance 
WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity) 
WCF后续之旅(12): 线程关联性(Thread Affinity)对WCF并发访问的影响 
WCF后续之旅(13): 创建一个简单的WCF SOAP Message拦截、转发工具[上篇] 
WCF后续之旅(13):创建一个简单的SOAP Message拦截、转发工具[下篇] 
WCF后续之旅(14):TCP端口共享 
WCF后续之旅(15): 逻辑地址和物理地址 
WCF后续之旅(16): 消息是如何分发到Endpoint的--消息筛选(Message Filter) 
WCF后续之旅(17):通过tcpTracer进行消息的路由


作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2008-09-16 09:23  Artech  阅读(...)  评论(... 编辑 收藏