第 8 章 创建连接器

上一章向您展示了各种类型的连接器,以及它们对于请求的同步和异步处理过程。到目前为止,我一直忽略的一个最为重要的步骤是:初始化连接器和连接器链。连接器通常既不是直接待代码中创建,也不是在配置文件中定义出来。相反,是通过链接器链来设置的。它最终在后台返回连接器链。本章将向您展示构建自己的连接器链的基础。这些定制的连接器将在第 9 章中实现。

8.1 理解连接器提供器

如你在第 4 章中所见,你可以使用如下所示的方式,通过 .NET 配置文件来定义连接器链。( 该示例用于客户端的配置文件,对于服务器端连接器链,你需要将 clientProviders 替换为 serverProviders )

<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http">
<clientProviders>
<provider type="MySinks.SomeMessageSinkProvider, Client" />
<formatter ref="soap" />
<provider type="MySinks.SomeClientChannelSinkProvider, Client" />
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
<configuration>

为了更为透彻地说明该示例,我将其中的 ​​\<formatter ref="soap" />​​ 替换为使用 machine.config 中真实的值 ( 包括其中必须的强签名部分 )

这样,完整的链现在看起来是这样的:

<provider type="MySinks.SomeMessageSinkProvider, Client" />
<formatter type="System.Runtime.Remoting.Channels.SoapClientFormatterSinkProvider,
System.Runtime.Remoting, Version=1.0.3300.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"/>
<provider type="MySinks.SomeClientChannelSinkProvider, Client" />

如你所见,在这些示例中,该连接器链 不是 通过 ​​sinks​​ 的名/值对来定义的 ( 也许应该是这样,例如 SoapClientFormatterSink )。相反,是使用连接器 提供器 来完成的,提供器既可以在客户端,也可以在服务器端使用,它至少需要实现如下一个接口:

public interface IClientChannelSinkProvider {
IClientChannelSinkProvider Next { get; set; }
IClientChannelSink CreateSink(IChannelSender channel,
string url,
object remoteChannelData);
}

public interface IServerChannelSinkProvider {
IServerChannelSinkProvider Next { get; set; }
IServerChannelSink CreateSink(IChannelReceiver channel);
void GetChannelData(IChannelDataStore channelData);
}

你可以在接口定义中看到,每个提供器中使用了 ​​Next​​ 属性来构建真实的调用链。

8.1.1 创建客户端的连接器

在加载了前面的示例配置文件之后,提供器链将被如图 8-1 所示的对象调用形式构建出来。前面 3 个提供器将通过配置文件加载,而最后一个 (HttpClientTransportSinkProvider) 则被 HTTP 通道默认初始化出来。

Advanced .NET Remoting: 第 8 章 创建连接器_服务器端
图 8-1 提供器链

在客户端,这些连接器提供器被关联到客户端的通道。你可以使用如下代码来访问这个通道对象:

IChannel chnl = ChannelServices.GetChannel("http");

注意到代码中的 "http" 表示通道的唯一名称。对于 HTTP 与二进制通道来说,名称是在 machine.config 中设置的。

该通道对象创建的相关连接器如图 8-2 所示:

Advanced .NET Remoting: 第 8 章 创建连接器_ide_02
图 8-2 使用提供的连接器提供器创建的 IChannel

当一个远程的服务器端激活对象 (SAO ) 被创建的时候 ( 对于 SAO,额外的 ConstructionCall 消息被发送到服务器,并使用代理的标识对象填充 ),在幕后会发生一系列事情。在使用 ​​new​​​ 操作符的时候,或者调用 ​​Activator.GetObject()​​​ 的时候,方法 ​​RemotingServices.Connect()​​ 被调用,在调用之后发生的事情如图 8-3 所示:

Advanced .NET Remoting: 第 8 章 创建连接器_ide_03
图 8-3 通过连接器提供器链创建连接器

在通过 RemotingServices 调用之后,ChannelServices 在每个注册的通道上调用 ​​CreateMessageSink()​​​ ,直到其中之一接受作为参数之一传递的 URL 地址。比如说是 HTTP 通道,它工作在任何以 ​​http:​​​ 或者以 ​​https:​​​ 开头的 URL 地址上,而 TCP 通道只接受使用 ​​tcp:​​ 协议。

当通道识别了指定的 URL 地址,它就会在客户端通道上调用 ​​CreateMessageSink()​​ 方法。

对于 HttpClientChannel 来说,随后在第一个通道提供器上调用 ​​CreatSink()​​ 方法 ( 如图 8-3 所示 )。不同的接收器提供器现在执行的操作,对应下面的代码:

public class SomeClientChannelSinkProvider: IClientChannelSinkProvider {
private IClientChannelSinkProvider next = null;
public IClientChannelSink CreateSink(IChannelSender channel,
string url, object remoteChannelData)
{
IClientChannelSink nextSink = null;

// checking for additional sink providers
if (next != null)
{
nextSink = next.CreateSink(channel,url,remoteChannelData);
}

// returning first entry of a sink chain
return new SomeClientChannelSink(nextSink);
}
}

每个接收器提供器首先在提供器链上的下一个条目上调用 ​​CreateSink()​​​ 方法,通过返回的结果,然后在其上返回自己的提供器。在调用链的最开始放置一个新的接收器的显式语法并没有指出。但是在此示例中,SomeClientSink 提供器的构造函数接受一个 ​​IClientChannelSink​​​ 对象作为参数,并设置到它的 ​​_nextChnlSink​​ 实例变量中,如下面代码片段所示 ( 重复一下,这里只有部分代码 )

public class SomeClientChannelSink: IClientChannelSink {

private IClientChannelSink _nextChnlSink;

public SomeClientChannelSink (IClientChannelSink next)
{
_nextChnlSink = next as IClientChannelSink;
}
}

通过调用 ​​ChannelServices.CreateMessageSink()​​ 方法返回的完整的连接器链,随后被连接到一对新的 TransparentProxy/RealProxy 成对标识对象上,如图 8-4 所示

Advanced .NET Remoting: 第 8 章 创建连接器_ide_04
图 8-4 连接到 TransparentProxy 的第一个 IMessageSink

8.1.2 创建服务器端连接器

创建服务器端的连接器与创建客户端连接器有一点不同。如你前面所见,在客户端,当引用一个要求的远程对象的时候,需要的连接器被创建。与此相反,服务器端的对象是在通道被注册的时候立即创建。

当服务器端的通道通过配置文件中的定义创建的时候,使用如下的构造函数:

public HttpServerChannel(IDictionary properties, IServerChannelSinkProvider sinkProvider)

第一个参数 IDictionary 来自 <channel> 配置节的定义。例如,当你的配置文件如下所示,包含下述内容:

<channel ref="http" port="1234" />

那么,properties 字典中将会包含一个键为 ​​port​​​ 值为 ​​1234​​ 的条目。

而在构造函数的 sinkProvider 参数,来自连接器链的第一个将会被传递给通道。该链使用配置文件中的 <serverProvider> 配置节构造。

在通道设置过程中,它从 HTTP 通道的构造函数开始,现在会发生两件事中的之一。如果没有 <serverProvider> 配置,默认的连接器链将被创建,如图 8-5 所示。

Advanced .NET Remoting: 第 8 章 创建连接器_客户端_05
图 8-5 HttpServerChannel 的默认连接器链

当在配置文件中指定了 <serverProviders> 的时候,连接器链将通过这些定义创建,不会使用默认的连接器提供器。


注意:有意思的是,在本场景下,你将不能通过在你的 SAO 对象上在 URL 上使用 ​​?WSDL​​ 参数来生成 WSDL 定义,除非在 ` 配置节中显式指定了 SdlChannelSinkProvider


在提供器链被创建之后,​​ChannelServices.CreateServerChannelSinkChain()​​​ 方法被调用。该方法将连接器提供器链作为参数。它会随后遍历该链条,并在调用 ​​CreateSink()​​ 方法之前,添加 DispatchChannelSinkProvider 对象到该链的最后。最后,它返回生成的连接器链条。当通过 ChannelServices 收到这个对象之后,HttpServerChannel 对象将添加一个 HttpServerTransportSink 作为第一个元素。最后的服务器端通道对象如图 8-6 所示。

Advanced .NET Remoting: 第 8 章 创建连接器_服务器端_06
图 8-6 完整的服务器端 HTTP 通道连接器栈

使用动态连接器

如你在上一章所见,客户端和服务器端的连接器链都可以调用动态连接器。在服务器端是通过 CrossContextChannel 而客户端是通过 ClientContextTerminatorSink 来完成的。

动态连接器关联到特定的 上下文 ( 你可以在第 11 章了解更多关于上下文内容 ),进而被传递了上下文边界的所有调用所调用。它们不能被赋予特定的通道,甚至可以被本地跨上下文,或者跨应用程序域所调用。连接器通过动态上下文属性创建,这是实现了 IDynamicProperty 和 IContributeDynamicSink 接口的类。


注意:IContributeDynamicSink 可以看作是动态连接器的连接器提供器。


相关的接口如下所示:

public interface IDynamicProperty {
string Name { get; }
}

public interface IContributeDynamicSink {
IDynamicMessageSink GetDynamicSink();
}

动态连接器本身,通过 GetDynamicSink() 方法返回,实现如下的 IDynamicMessageSink 接口:

public interface IDynamicMessageSink {
void ProcessMessageStart(IMessage reqMsg, bool bCliSide, bool bAsync);
void ProcessMessageFinish(IMessage replyMsg, bool bCliSide, bool bAsync);
}

如名称所暗示,在消息穿过接收器链之前,ProcessMessageStart() 方法将被调用,在消息被连接器处理之后,ProcessMessageFinish() 方法被调用。

下面的简化的动态连接器将会在控制台写出一行,只要消息穿越 remoting 边界。

public class MyDynamicSinkProvider: IDynamicProperty, IContributeDynamicSink
{
public string Name
{
get { return “MyDynamicSinkProvider"; }
}

public IDynamicMessageSink GetDynamicSink()
{
return new MyDynamicSink();
}
}

public class MyDynamicSink: IDynamicMessageSink
{
public void ProcessMessageStart(IMessage reqMsg, bool bCliSide, bool bAsync)
{
Console.WriteLine("--> MyDynamicSink: ProcessMessageStart");
}

public void ProcessMessageFinish(IMessage replyMsg, bool bCliSide, bool bAsync)
{
Console.WriteLine("--> MyDynamicSink: ProcessMessageFinish");
}
}

为了使用正确的上下文注册 IDynamicProperty,你可以使用如下代码:

Context ctx = Context.DefaultContext;
IDynamicProperty prp = new MyDynamicSinkProvider();
Context.RegisterDynamicProperty(prp, null, ctx);

小结

在本章中,你了解了创建各种类型的连接器。与上一章一起,你已经完全具备了实现你自己的连接器来扩展 .NET Remoting 功能。你还与动态上下文连接器有了第一次碰面,更详细内容将会在第 11 章中介绍。

我承认随后的两章内容有点多,但是在下一章,我将会为你的耐心奖励一些使用这里介绍的技术所创建的真实世界的连接器。

原文链接