。NET Remoting 转发服务器






4.60/5 (2投票s)
一篇关于借助转发服务器重新部署远程服务的文章。
引言
试想一下,您已经开发了一个公开了多个服务的 .NET remoting 服务器。并且有许多客户端依赖于您的服务在特定终结点上可用。例如,您的客户端习惯于像这样访问您的服务:
string url1 = "tcp://phaestos:1234/Service1.rem";
string url2 = "tcp://phaestos:1234/Service2.rem";
您的服务器通道在机器 PHAESTOS 上监听端口 1234。有两个(或更多)知名服务可用。出于某种好原因,您决定将您的服务重新部署到另外两台机器上,命名为 KNOSSOS 和 ZAKROS,这样,一个远程服务可以部署在 KNOSSOS 上,而另一个服务可以部署在 ZAKROS 上。因此,您的服务的新的终结点变为:
string url1 = "tcp://knossos:1234/Service1.rem";
string url2 = "tcp://zakros:1234/Service2.rem";
这似乎很容易做到。但只有一个问题。您的客户端不知道服务已被重新部署到不同的主机上。它们继续访问以前的主机 PHAESTOS 上的服务。这就需要将来自 PHAESTOS 的每个请求分别重定向到 KNOSSOS 和 ZAKROS。以前的服务主机现在必须充当转发服务器。
我将在本文中解释如何创建这样的转发服务器。
。NET Remoting 转发服务器
转发服务器需要一个特殊的 ServerChannelSink
,我们可以将其构建并插入到服务器的入站通信通道中。不需要其他补充的 ClientChannelSink
。ServerChannelSink
为我们提供了一个拦截客户端通信调用的机会。
当客户端通信时,消息会通过通道传输。通道由多个通道接收器组成,传输接收器、格式化器接收器以及任何可能的附加自定义接收器。我们的 ServerChannelSink
是那个关键的自定义接收器,它将帮助我们将入站消息转发到最终目标服务器。
我们可以在通道中拦截传输的消息,当它作为请求流可用时,或者在格式化器将其反序列化为 Message
对象后进行拦截。一旦拦截,消息必须传输到出站通道,以便传输到最终目标。
以下图表描绘了两种不同的配置。第一种配置将自定义通道接收器 C 放置在转发服务器入站侧的格式化器接收器 F 之后。第二种配置将自定义通道 C 放置在传输接收器 T 和格式化器接收器 F 之间。这两种配置本质上都是旁路系统。
为了使这个旁路系统工作,我们识别入站调用并选择一个相应的出站通道。每个入站调用都可以通过检查请求传输头中的 __RequestUri
属性值来识别。转发服务器维护一个请求 URI 到特殊 ChannelObject
的映射,该 ChannelObject
封装了对出站 MessageSink
和目标 URI 的引用。
以下代码片段简要说明了其工作原理:
public ServerProcessing ProcessMessage(
IServerChannelSinkStack sinkStack,
IMessage requestMsg,
ITransportHeaders requestHeaders,
Stream requestStream,
out IMessage responseMsg,
out ITransportHeaders responseHeaders,
out Stream responseStream)
{
// inspect the __RequestUri
string requestUri = (string)requestHeaders["__RequestUri"];
// use the __RequestUri to lookup a ChannelObject
ChannelObject channelObject =
ChannelManager.ChannelObjects[requestUri];
// assign the destination uri to the requestMesg
requestMsg.Properties["__Uri"] = channelObject.Uri;
// call the outbound MessageSink to dispatch the requestMsg
return channelObject.MessageSink.SyncProcessMessage(requestMsg);
}
ChannelObject
是转发服务器提供并从远程服务的 URL 构建的。正如我们之前所见:
string url1 = "tcp://knossos:1234/Service1.rem";
string url2 = "tcp://zakros:1234/Service2.rem";
转发服务器最初如何获取这些 URL 并不重要。本文包含的示例项目只是从 App.config 文件中读取它们。
// simply read the application services endpoints from the config file
string services = ConfigurationManager.AppSettings["services"];
// create the connection objects and add them to the list of connections
foreach(string service in services.Split(new char[] {','}))
{
Uri uri = new Uri(service.Trim());
ChannelManager.ChannelObjects.Add(uri.LocalPath,
new ChannelObject(uri));
}
有趣的是 ChannelObject
如何获得对出站 MessageSink
的引用。这是 ChannelObject
的完整类定义:
class ChannelObject :
MarshalByRefObject,
IDisposable
{
string absoluteUri;
IMessageSink messageSink;
public ChannelObject(Uri uri)
{
// save the absolute uri for later
// assignment to the request message
this.absoluteUri = uri.AbsoluteUri;
// loop through all sender channels
// to find one that fits the scheme, e.g. 'tcp'
foreach (IChannel channel in ChannelServices.RegisteredChannels)
{
if (channel is IChannelSender)
{
// create and save a MessageSink if possible
IChannelSender sender = (IChannelSender)channel;
if (string.Compare(sender.ChannelName, uri.Scheme) == 0)
{
string objectUri;
this.messageSink = sender.CreateMessageSink(
this.absoluteUri, null, out objectUri);
if (this.messageSink != null)
break;
}
}
}
if (this.messageSink == null)
throw new ApplicationException("No channel found for " +
uri.Scheme);
// strip the '/' character from the objectUri
RemotingServices.Marshal(this, uri.LocalPath.Substring(1));
}
public string Uri
{
get { return this.absoluteUri; }
}
public IMessageSink MessageSink
{
get { return this.messageSink; }
}
public void Dispose()
{
RemotingServices.Disconnect(this);
}
}
在构造函数中,发生了三件重要的事情。首先,我们保留了远程服务的绝对 URI。其次,我们创建了一个出站 MessageSink
。第三,我们将 ChannelObject
在转发服务器的域中进行封送,作为远程服务的替身。客户端的调用方式就像转发服务器是远程服务的宿主一样,实际上它不是。进行 ChannelObject
的封送似乎有些多余。我们这样做是为了满足 .NET remoting 基础设施的条件。没有它,就会出现一个异常,指示找不到远程服务。
第二种旁路配置以类似的方式工作。区别在于格式化器接收器被旁路了。ChannelObject
引用的是出站 TransportSink
,而不是出站 MessageSink
。另一个重要的区别是,通过旁路格式化器接收器,我们不再需要为转发服务器提供包含远程服务通用接口和数据结构的共享程序集。第一种配置需要一个共享程序集。否则,格式化器接收器将不知道如何反序列化/序列化消息。通过不使用格式化器,我们将请求流从入站通道传输到出站通道。
结论
在撰写本文时,我想尽可能地简洁。我希望您能下载源代码以获得对这些技术的完整理解。至于使用这些技术的优点,我承认一个人必须参与到分布式计算环境的正确工程中。一个好的分布式计算环境必须是动态的。您永远不能坚持认为应用程序或服务将始终驻留在同一主机上。
如果您正在尝试解决类似的问题,请告诉我。我一直很乐意向您学习替代或更好的方法。
使用示例项目
示例项目是一个 Visual Studio 2005 解决方案。下载它,编译它,然后启动各种组件。有一个 ApplicationServer
,一个 ApplicationClient
,以及两个转发服务器,RelayServer1
和 RelayServer2
。首先,启动 ApplicationServer
,然后启动其中一个转发服务器,例如 RelayServer1
,最后启动 ApplicationClient
。