接收器和接收链
在发送消息之前或接收消息之后,信道沿着信道接收对象链发送每个消息。该接收链包含基本信道功能所需的接收器(如格式化程序接收器、传输接收器或堆栈生成器接收器),但是,您可以自定义信道接收链以便用消息或流执行特殊任务。
每个信道接收器或者实现 IClientChannelSink,或者实现 IServerChannelSink。客户端上的第一个信道接收器还必须实现 IMessageSink。它通常实现 IClientFormatterSink(它从 IMessageSink、IChannelSinkBase 和 IClientChannelSink 继承),并被称作格式化程序接收器,因为它将传入的消息转换为流(IMessage 对象)。
信道接收链处理任何发送到应用程序域或从应用程序域发送的消息。此时,您只有消息,但是您可以对这些消息执行任何操作,而后面的处理将使用您在处理之后返回给系统的消息。这是实现日志记录服务、任何种类的筛选器或者客户端或服务器上的加密或其他安全措施的理所当然的位置。以下插图显示基本信道接收链的结构。
基本信道接收链
请注意,每个信道接收器都处理流,然后将流传递到下一个信道接收器,这意味着您的接收器之前或之后的对象应当知道该如何处理传递给它们的流。
备注
消息接收器不得引发异常。消息接收器可以控制此状况的一种方法是在 try-catch 块中环绕方法代码。
信道接收提供程序(实现 IClientChannelSinkProvider、IClientFormatterSinkProvider 或 IServerChannelSinkProvider 接口的对象)负责创建远程处理消息所流过的信道接收器。在远程类型被激活后,将从信道中检索信道接收提供程序;然后在该接收提供程序上调用 CreateSink 方法以检索链上的第一个信道接收器。
信道接收器还负责在客户端和服务器之间传输消息。信道接收器也链接在一起而形成一个链。当在接收提供程序上调用 CreateSink 方法时,该方法应该执行以下操作:
创建它自己的信道接收器。
在链中的下一个接收提供程序上调用 CreateSink。
确保下一个接收器和当前的接收器链接在一起。
将其接收器返回到调用方。
信道接收器负责将在它们上面进行的所有调用转发到链中的下一个接收器,并且应当提供用于存储对下一个接收器的引用的机制。
在信道接收器沿着接收链发送何种内容方面,信道接收器有很大的灵活性。例如,要在发送实际的序列化的原始消息之前协商身份验证的安全接收器可以停留在完整信道消息上,用它们自己的内容替换内容流,并且沿着接收链发送它,一直发送到远程应用程序域。在返回的行程上,安全接收器可以截获答复消息,创建与远程应用程序域中的对应安全接收器的对话。协议一经达成,起始安全接收器便可以将原始内容流一直发送到远程应用程序域。
信道接收链中的消息处理
.NET 远程处理系统一旦找到可以处理 IMethodCallMessage 实现的信道,该信道便通过调用 IMessageSink.SyncProcessMessage(或 IMessageSink.AsyncProcessMessage)将消息传递给格式化程序信道接收器。格式化程序接收器创建传输头数组并在下一个接收器上调用 IClientChannelSink.GetRequestStream。该调用被沿着接收链转发,任何接收器都可以创建将被传递回格式化程序接收器的请求流。如果 GetRequestStream 返回空引用(在 Visual Basic 中为 Nothing),格式化程序接收器将创建它自己的接收器以用于序列化。该调用一旦返回,消息便被序列化,并且处理方法的相应消息将在接收链中的第一个信道接收器上调用。
接收器不能将数据写到流中,但是可以从流读取或者沿着所需的地方传递新的流。接收器还可以将头添加到头数组(如果它们以前没有在下一个接收器上调用 GetRequestStream),并且在将调用转发到下一个接收器之前将它们自己添加到接收器堆栈。当调用到达链末端的传输接收器时,传输接收器将头和序列化的消息通过信道发送到服务器,在该服务器上,整个过程将反向进行。传输接收器(在服务器端)从流的服务器端检索头和序列化的消息,并通过接收链转发它们,直到到达格式化程序接收器。格式化程序接收器反序列化该消息并将其转发到远程处理系统,在那里消息将被恢复为方法调用,并在服务器对象上被调用。
创建信道接收链
要创建新的信道接收器,必须实现和配置远程处理系统以识别 IServerChannelSinkProvider 或 IClientChannelSinkProvider 实现,这两个实现可以创建自定义的 IClientChannelSink 或 IServerChannelSink 实现或检索链中的下一个接收器。可以使用 BaseChannelSinkWithProperties 抽象类来帮助实现自定义信道接收器。
生成信道接收提供程序
应用程序可以在构造信道时将服务器或客户端信道接收提供程序作为参数来提供。信道接收提供程序应当以链的形式存储,用户应负责在将外边的那个提供程序传递到信道构造函数之前,将所有信道接收提供程序链接在一起。为此,信道接收提供程序实现了 Next 属性。下面的代码示例阐释如何生成客户端信道接收提供程序。在远程处理示例:信道接收提供程序中提供了一个完整的示例。
private Function CreateDefaultClientProviderChain() As IClientChannelSinkProvider
Dim chain As New FirstClientFormatterSinkProvider
Dim sink As IClientChannelSinkProvider
sink = chain
sink.Next = New SecondClientFormatterSinkProvider
sink = sink.Next
return chain
End Function
private IClientChannelSinkProvider CreateDefaultClientProviderChain(){
IClientChannelSinkProvider chain = new FirstClientFormatterSinkProvider();
IClientChannelSinkProvider sink = chain;
sink.Next = new SecondClientFormatterSinkProvider();
sink = sink.Next;
return chain;
}
备注
当配置文件中提供了多个信道接收提供程序时,远程处理系统会按照它们在配置文件中出现的顺序将它们链接在一起。在 RemotingConfiguration.Configure 调用期间创建信道时,将创建信道接收提供程序。
格式化程序接收器
格式化程序接收器会将信道消息当作实现 IMessage 的对象序列化为消息流。一些格式化程序接收器实现使用系统提供的格式化程序类型(BinaryFormatter 和 SoapFormatter)。其他实现可以使用自己的方法将信道消息转换为流。
格式化程序接收器的功能是生成所需的头并将消息序列化到流中。在格式化程序接收器之后,消息将通过 IMessageSink.ProcessMessage 或 Imessagesink.AsyncProcessMessage 调用转发到接收链中的所有接收器。在此阶段消息已经序列化,仅作为信息提供。
备注
需要创建或修改消息本身的接收器必须在接收链中放在格式化程序之前。通过实现 IClientFormatterSink 可以轻松地做到这一点,从而使系统相信它具有对格式化程序接收器的引用。这样,真正的格式化程序接收器便可以放在接收链中靠后的位置。
在返回行程中,格式化程序接收器将消息流转换回信道消息元素(返回消息)。客户端上的第一个接收器必须实现 IClientFormatterSink 接口。当 CreateSink 返回到信道时,返回的引用被强制转换为 IClientFormatterSink 类型,这样就可以调用 IMessage 接口的 SyncProcessMessage。如果强制转换失败,系统将引发异常。
自定义信道接收器
在客户端上,自定义信道接收器被插入到格式化程序接收器和最后一个传输接收器之间的对象链中。通过在客户端或服务器信道中插入自定义信道接收器,您可以在以下两个时间点之一处理 IMessage:
在表示为消息的调用被转换为流并通过网络发送的过程期间。
在流被从网络中取出并发送到 StackBuilderSink 对象或客户端上的代理对象的过程期间,其中 StackBuilderSink 对象是服务器上远程对象前的最后一个消息接收器。
自定义接收器可以从流中读取数据或者将数据写入流中(取决于调用是传出的还是传入的),并在需要的时候将附加的信息添加到头。在这一阶段,消息已经被格式化程序序列化且不能修改。当消息调用被转发到链末端的传输接收器时,传输接收器通过使用信道所规定的传输协议将头写入到流,并将流转发到服务器上的传输接收器。
传输接收器
传输接收器是客户端上的链中最后一个接收器和服务器端上的链中第一个接收器。除了传输序列化的消息,传输接收器还负责将头发送到服务器并在调用从服务器返回时检索头和流。这些接收器内置在信道中,并且无法扩展。
替换默认的格式化程序
由于信道是抽象的网络机制,因此可以配置 .NET 远程处理系统,将系统实现的信道与您选择的任何格式化程序结合起来使用。为此,您可以使用采用信道属性的 IDictionary 实现、服务器端格式化程序和客户端格式化程序的信道构造函数。还可以在配置文件中指定格式化程序。下面的示例指示 .NET 远程处理配置系统创建 HttpChannel,但在客户端使用 BinaryClientFormatterSink。
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http">
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
<channels>
</application>
</system.runtime.remoting>
</configuration>
下面的代码以编程方式完成相同功能(假定有一个实现 GetServerString 和 GetServerTime 的远程接口类型 IService)。
Imports System
Imports System.Collections
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http
Public Class ClientProcess
<MTAThread()> _
Public Shared Sub Main()
' Note that any name/value pairs of configuration attributes can be
' placed in this dictionary (the configuration system calls this same
' constructor).
Dim properties As New Hashtable()
properties("name") = "HttpBinary"
ChannelServices.RegisterChannel(New HttpChannel(properties, New BinaryClientFormatterSinkProvider(), Nothing))
' The last parameter above (Nothing) is the server sink provider chain
' to obtain the default behavior (which includes SOAP and
' binary formatters on the server side).
Dim service As IService = CType(Activator.GetObject(GetType(IService), "http://computer:8080/SAService"), IService)
Console.WriteLine("Server string is: " + service.GetServerString())
Console.WriteLine("Server time is: " + service.GetServerTime())
End Sub
End Class
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
public class ClientProcess{
public static void Main(string[] Args){
// Note that any name/value pairs of configuration attributes can be
// placed in this dictionary (the configuration system calls this
// same HttpChannel constructor).
IDictionary properties = new Hashtable();
properties["name"] = "HttpBinary";
// The last parameter below is the server sink provider chain
// to obtain the default behavior (which includes SOAP and binary
// formatters) on the server side.
ChannelServices.RegisterChannel(new HttpChannel(null, new BinaryClientFormatterSinkProvider(), null));
IService service = (IService)Activator.GetObject(typeof(IService),"http://computer:8080/SAService");
Console.WriteLine("Server string is: " + service.GetServerString());
Console.WriteLine("Server time is: " + service.GetServerTime());
}
}
有关在 Internet 信息服务 (IIS) 中承载的这一信道和格式化程序组合的完整示例,请参见远程处理示例:在 Internet 信息服务 (IIS) 中承载。
要将该客户端更改为使用具有 SoapClientFormatterSink 对象的 TcpChannel 对象,只需要更改命名空间和 RegisterChannel 调用,如下面的代码示例所示。
ChannelServices.RegisterChannel(New TcpChannel(properties, New SoapClientFormatterSinkProvider(), Nothing))
ChannelServices.RegisterChannel(new TcpChannel(null, new SoapClientFormatterSinkProvider(), null));
请参见
概念
在 Internet 信息服务 (IIS) 中承载远程对象
远程处理示例:在 Internet 信息服务 (IIS) 中承载