远程处理绝对入门






4.82/5 (81投票s)
介绍 .NET Remoting 以及如何使用简单的代码片段来处理异步回调。
引言
Remoting 允许您远程处理对象。它比基于 SOAP 的 Web 服务效率更高,但显然只能在内网中使用。在互联网上,您会遇到防火墙问题,并且整体安全性不足。
用非常简单的话来说,您有一个在某台机器上运行的客户端程序,它调用在另一台机器上实例化的对象的函数。让我们来看一些非常简短而精炼的代码片段。通常,我认为通过查看一个小代码片段比阅读一篇 2000 字的文章来理解一个概念更容易,后者会为您提供各种复杂的理论。当然,任何额外的理论知识总会在很多方面有所帮助。
界面
我们首先创建一个名为 MyInterface
的接口。然后将其编译成一个 dll。稍后我们将从该接口派生我们的远程类。现在,这样做的好处是,在客户端机器上我们可以使用这个接口。否则,我们必须直接从客户端机器使用该类,这几乎使整个练习有点徒劳。
// csc /t:library MyInterface.cs
public interface MyInterface
{
int FunctionOne(string str);
}
对象
现在我们的接口已经准备好了,让我们创建将在远程机器上调用的类。我们从 MarshalByRefObject
和 MyInterface
派生此类。任何派生自 MarshalByRefObject
的类都允许远程客户端调用其方法,这正是我们在这里要做的。
// csc /t:library /r:MyInterface.dll RemoteObject.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
public class MyRemoteClass : MarshalByRefObject,MyInterface
{
public int FunctionOne(string str)
{
return str.Length;
}
}
服务器
现在,让我们编写远程服务器。
// csc /r:RemoteObject.dll RemoteServer.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
public class MyServer
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel(9999);
ChannelServices.RegisterChannel(m_TcpChan);
RemotingConfiguration.RegisterWellKnownServiceType(
Type.GetType("MyRemoteClass,RemoteObject"),
"FirstRemote",WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
}
}
首先,我们创建一个 TcpChannel
对象,用于在我们的 Remoting 边界上传输消息。正如您所见,我还选择了 9999 作为监听的 TCP 端口。现在您可以理解这为什么会导致防火墙遇到很多问题。我们使用 ChannelServices.RegisterChannel
将我们的 TcpChannel
注册到通道服务。不要问我那个函数里面具体是什么。我唯一能弄清楚的是,如果您不注册通道,Remoting 将无法工作。现在,我们使用 RemotingConfiguration.RegisterWellKnownServiceType
将我们的 MyRemoteClass
对象注册为一个已知类型。现在这个对象就可以被远程访问了。
我第一次看到这么长的函数名时,以为是个笑话。但 .NET 框架有成千上万的函数和类,所以我猜他们别无选择。我猜这比调用 __rwkst(...)
要好得多。我在这里使用了 FirstRemote 这个词,它将作为客户端访问远程对象时使用的 URL 的一部分。我在这里指定了 SingleCall
模式,这意味着每次远程调用都会创建一个新实例。我本可以使用 Singleton
,在这种情况下,将实例化一个对象并由所有连接的客户端使用。我想如果这是一篇高级文章(它不是)由一名高级程序员(我不是)撰写,会有一些关于这两种模式之间区别的解释。
客户端
好的,现在在一台机器上运行您的服务器。现在让我们看看客户端,它可以运行在同一台机器上或另一台机器上。我不得不在同一台机器上运行它,因为有一些复杂的技术原因。老实说,复杂的技术原因是我家里只有一台机器。
// csc /r:MyInterface.dll client.cs
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
class MyClient
{
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel();
ChannelServices.RegisterChannel(m_TcpChan);
MyInterface m_RemoteObject = (MyInterface)
Activator.GetObject(typeof(MyInterface),
"tcp://earth:9999/FirstRemote");
Console.WriteLine(m_RemoteObject.FunctionOne("Nish"));
}
}
与服务器一样,我们也有一个 TcpChannel
对象,但在这种情况下,我们不指定端口。我们还使用 ChannelServices.RegisterChannel
来注册通道。我们使用 Activator.GetObject
来获取 MyInterface
对象的实例。正如您所看到的,我在 URL 中使用了主机名“earth”,因为这是我的机器的主机名。在您的案例中,您需要将其替换为远程机器的名称或其 IP 地址。构成 URL 一部分的“FirstRemote”这个词是我在服务器上传递给 RemotingConfiguration.RegisterWellKnownServiceType
的内容。现在我已经得到了我的对象,我可以调用它的成员函数了。嗯,这并不难,不是吗?
异步回调
在客户端中,我们对远程对象使用了同步调用。这样做的缺点是,如果函数执行时间很长,我们的程序就会挂起,直到函数返回。当然,每次想要访问远程方法时,您可以启动一个工作线程,但这效率不高。而且,如果您碰巧将共享数据传递给方法,并且也从方法中获取一些共享数据,您就需要小心处理线程同步。好消息是,您不必做所有这些复杂的事情。您可以使用异步回调。
// csc asyncclient.cs /r:MyInterface.dll
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
class MyClient
{
public delegate int MyDelegate(string s);
public static void Main()
{
TcpChannel m_TcpChan = new TcpChannel();
ChannelServices.RegisterChannel(m_TcpChan);
MyInterface m_RemoteObject = (MyInterface)
Activator.GetObject(typeof(MyInterface),
"tcp://earth:9999/FirstRemote");
MyDelegate m_delegate =
new MyDelegate(m_RemoteObject.FunctionOne);
m_delegate.BeginInvoke("Amazing",
new AsyncCallback(MyCallBack),null);
System.Console.WriteLine("Press ENTER to quit");
System.Console.ReadLine();
}
public static void MyCallBack(IAsyncResult ar)
{
int l = ((MyDelegate)((AsyncResult)ar).AsyncDelegate).EndInvoke(ar);
Console.WriteLine(l);
}
}
这里我们有两个 委托
。委托是类型安全的函数指针。第一个委托是我们创建的 MyDelegate
。我们将它指向我们需要从远程对象访问的函数。现在我们调用 BeginInvoke
函数。第一个参数是我们传递给实际函数的参数。第二个参数是另一个类型为 AsyncCallback
的委托。现在发生的情况是,远程函数被调用,但控制立即返回,我们的程序不会被阻塞。稍后,当远程对象完成函数的执行时,我们的回调会被调用,并传入一个 IAsyncResult
参数,该参数封装了异步委托上异步操作的结果。我们使用 AsyncDelegate
属性来获取我们的委托,并将其转换为 MyDelegate
,然后对其调用 EndInvoke
来获取远程执行函数的結果。您必须记住,远程函数已经终止,它的终止与 EndInvoke
无关。EndInvoke
只是将操作的结果返回给我们。
结论
我真心希望阅读本文的初学者能从中受益。Remoting 还有很多内容,如果我学到更多东西,我会在这里发布更多文章。