信道
信道用于.NET 客户端和服务器之间的通信。.NET Framework 4 发布的信道类使用 TCP 、HTTP 或IPC 进行通信。我们可以为其他的协议创建自定义信道。 HTTP 信道使用 HTTP 协议进行通信。因为防火墙通常让端口 80 处于打开的状态,所以客户端能 够访问 Web 服务器,因为.NET Remoting Web 服务可以侦听端口 80,所以客户端更容易使用它们。 虽然在 Internet 上也可以使用 TCP 信道,但是必须配置防火墙,这样客户端能够访问 TCP 信道 所使用的指定端口。与 HTTP 信道相比,在内部网环境中使用 TCP 信道能够进行更加高效的通信。 IPC 信道适合于在单个系统上进行跨进程的通信。因为它使用 Windows 进程间通信机制,所 以它比其他信道快。当执行远程对象上的方法调用时,导致客户信道对象就把消息发送到远程信道对象中。 服务器应用程序和客户端应用程序都必须创建信道。 下面的代码说明了如何在服务器端创建 TcpServerChannel:
using System.Runtime.Remoting.Channels.Tcp; //省略... TcpServerChannel channel = new TcpServerChannel(8086);
构造函数的参数指定 TCP 套接字侦听哪个端口。服务器信道必须指定一个众所周知的端口,在 访问服务器时,客户端必须使用该端口。但是,在客户端上创建 TcpClientChannel 时,不必指定一 个众所周知的端口,TcpClientChannel 的默认构造函数会选择一个可用端口,在客户端与服务器连接 时,该端口被传递给服务器,以便服务器能够把数据返回给客户端。 创建新的信道实例,会使套接字立即转换到侦听状态,在命令行中输入 netstat –a,可以验证套 接字是否处于侦听状态。
HTTP 信道的使用方式类似于 TCP 信道。可以指定服务器能在哪个端口上创建侦听套接字。 服务器可以侦听多个信道。下面的代码创建并注册 HTTP、TCP 和IPC 信道:
var tcpChannel = new TcpServerChannel(8086); var httpChannel = new HttpServerChannel(8085); var ipcChannel = new IpcServerChannel("myIPCPort"); // register the channels ChannelServices.RegisterChannel(tcpChannel, true); ChannelServices.RegisterChannel(httpChannel, false); ChannelServices.RegisterChannel(ipcChannel, true);
信道类必须实现 IChannel 接口。IChannel 接口有以下两个属性:
● ChannelName 属性是只读的,它返回信道的名称。信道的名称取决于协议的类型,例如, HTTP 信道的名称为 HTTP。
● ChannelPriority 属性也是只读的。在客户端和服务器之间可以使用多个信道进行通信,优先 级定义信道的次序。在客户端上,具有较高优先级的信道首先连接到服务器上。优先级值 越高,优先级就越高,其默认值是 1,但允许使用负值创建较低的优先级。
实现的其他接口要根据信道的类型决定,服务器信道实现 IChannelReceiver 接口,而客户端信 道实现 IChannelSender 接口。 HTTPChannel、TcpChannel 和 IPCChannel 类都可以用于服务器和客户端。它们实现 IChannelSender 和 IChannelReceiver 接口。这些接口都派生自 IChannel 接口。 客户端的 IchannelSender 接口除了有 Ichannel 接口之外,还有一个 CreateMessageSink()方法,这 个方法返回一个实现 IMessageSink 接口的对象。IMessageSink 接口可以把同步和异步消息放到信道 中。在使用服务器端的接口 IChannelReceiver 时,通过 StartListening()方法可以把信道设置为侦听状 态,而通过 StopListening()方法则可以停止对信道的侦听。ChannelData 属性用于访问所获取的数据。
使用信道类的属性,可以获取信道的配置信息。HTTP和TCP 信道都有 ChannelName、ChannelPriority 和ChannelData 属性。使用 ChannelData 属性可以获取存储在 ChannelDataStore 类中的 URI 信息。此外, HttpServerChannel 类还有一个 Scheme 属性。下面的代码显示一个辅助方法 ShowChannelProperties(),该 方法在文件中显示对应的信息:
1 static void ShowChannelProperties(IChannelReceiver channel) 2 { 3 Console.WriteLine("Name: {0}", channel.ChannelName); 4 Console.WriteLine("Priority: {0}", channel.ChannelPriority); 5 if (channel is TcpChannel) 6 { 7 TcpChannel tcpChannel = channel as TcpChannel; 8 Console.WriteLine("is secured: {0}", tcpChannel.IsSecured); 9 } 10 11 if (channel is HttpServerChannel) 12 { 13 HttpServerChannel httpChannel = channel as HttpServerChannel; 14 Console.WriteLine("Scheme: {0}", httpChannel.ChannelScheme); 15 } 16 17 ChannelDataStore data = (ChannelDataStore)channel.ChannelData; 18 if (data != null) 19 { 20 foreach (string uri in data.ChannelUris) 21 { 22 Console.WriteLine("URI: " + uri); 23 } 24 } 25 Console.WriteLine(); 26 }
设置信道属性
使用构造函数 TCPServerChannel(IDictionary,IServerChannelSinkProvider),可以在一个列表中设 置信道的所有属性。Dictionary 泛型类实现 IDictionary 接口,因此,使用这个类可以设置 Name、 Priority 和 Port 属性。为了使用 Dictionary 类,必须导入 System.Collections.Generic 名称空间。 在 TcpServerChannel 类的构造函数中,除了参数 IDictionary 之外,还可以传递实现 IServerChannelSinkProvider 接口的对象。在本例中,设置 BinaryServerFormatterSinkProvider 而不设置 SoapServerFormatterSinkProvider,前者是 HttpServerChannel 的默认值。BinaryServerFormatterSinkProvider 类的默认实现代码把 BinaryServerFormatterSink 类与使用 BinaryFormatter 对象的信道联系起来,以转换 数据,便于传输:
1 var properties = new Dictionary<string, string>(); 2 properties["name"] = "HTTP Channel with a Binary Formatter"; 3 properties["priority"] = "15"; 4 properties["port"] = "8085"; 5 var sinkProvider = new BinaryServerFormatterSinkProvider(); 6 var httpChannel = new HttpServerChannel(properties, sinkProvider);
根据信道的类型,可以指定不同的属性。TCP 和HTTP 信道都支持本例中使用的 name 和priority 信道属性。这些信道也支持其他属性,如 bindTo,如果计算机配置了多个 IP 地址,bindTo 指定绑定可以使用的 IP 地址。TCP 服务器信道支持 rejectRemoteRequests,只允许从本地计算机上连接客 户端。
信道的“可插入性”
创建的自定义信道可以使用 HTTP、TCP 和 IPC 之外的其他传输协议发送消息。此外,也可以 对现有的信道进行扩展,从而提供更多功能:
● 发送部分必须实现 IChannelSender 接口。重要的部分是 CreateMessageSink()方法,在该方 法中,客户端要发送 URL,此外,使用这个方法可以实例化与服务器的连接。在这里必须 创建消息接收器,代理使用该消息接收器把消息发送到信道中。
● 接收部分必须实现 IChannelReceiver 接口。必须在 ChannelData 的get 属性中启动侦听功能。 然后,可以等待另一个线程接收来自客户端的数据。在打乱消息之后,使用 ChannelServices. SyncDispatchMessage()方法把消息分配给对象。