远程处理示例: 委托和事件
症状
.NET 框架 SDK 文档中提供的“远程处理示例: 委托和事件”示例不能按照预期方式工作。 该示例是一个聊天应用程序,它演示如何使用远程 委托和事件。 客户端应用程序通过委托将回调函数提交给 Remoting 服务器对象事件。 该服务器对象然后调用该事件,从而导致对客户端进行回调。只有当客户端、可远程处理的对象和远程处理主机程序集驻留在同一个目录中时,该示例才按照所写方式工作。 如果客户端应用程序和服务器应用程序在不同的目录中或不同的计算机上(这是典型的情况),则在客户端尝试将委托提交给该服务器对象时将会失败并出现 FileNotFoundException 错误。
原因
此行为发生的原因在于,对于其函数正被委托包装的类,委托要求接收对象能够获取该类的类型信息。 在本示例中,委托要求客户端程序集对于服务器可用。 如果客户端程序集对于服务器不可用,则不能加载该类型信息。若要使远程委托有效,必须在客户端和服务器都可以访问的一个公共程序集中定义一个包含回调函数的抽象类(在 Visual Basic .NET 中为 MustInherit,在 C# 中为 abstract)。 客户端然后才能从该抽象类派生出定义类以便在回调中实现该逻辑。 抽象类需要具有特定的结构。 要用作回调的函数必须是不能被重写的公共函数。 此函数必须将所有调用转发到一个受保护的抽象函数,该抽象类派生于已继承的客户端类。 采用此体系结构的原因在于,委托需要能够绑定到回调函数的具体实现中,而此实现必须不能被重写。
解决方案
以下代码是 ChatCoordinator.cs 的纠正后的示例代码。公共抽象类是 RemotelyDelegatableObject。 请注意,客户端应用程序需要定义一个从 RemotelyDelegatableObject 派生的类,并在那里实现其回调逻辑。// ChatCoordinator.cs using System; using System.Runtime.Remoting; using System.Collections; // Define the class that contains the information for a Submission event. [Serializable] public class SubmitEventArgs : EventArgs { private string _string = null; private string _alias = null; public SubmitEventArgs(string contribution, string contributor) { this._string = contribution; this._alias = contributor; } public string Contribution { get { return _string; } } public string Contributor { get { return _alias; } } } // The delegate declares the structure of the method that the event will call when it occurs. // Clients implement a method with this structure, create a delegate that wraps it, and then // pass that delegate to the event. The runtime implements events as a pair of methods, // add_Submission and remove_Submission, and both take an instance of this type of delegate // (which really means a reference to the method on the client that the event will call). public delegate void SubmissionEventHandler(object sender, SubmitEventArgs submitArgs); // Define the service. public class ChatCoordinator :MarshalByRefObject { public ChatCoordinator() { Console.WriteLine("ChatCoordinator created. Instance: "请注意,ChatCentral.cs 文件没有发生更改:
+ this.GetHashCode().ToString()); } // This is to insure that when created as a Singleton, the first instance never dies, // regardless of the time between chat users. public override object InitializeLifetimeService() { return null; } // The client will subscribe and unsubscribe to this event. public event SubmissionEventHandler Submission; // Method called remotely by any client. This simple chat server merely forwards // all messages to any clients that are listening to the Submission event, including // whoever made the contribution. public void Submit(string contribution, string contributor) { Console.WriteLine("{1}.", contributor, contribution); // Package String in SubmitEventArgs, which will be sent as an argument // to all event "sinks", or listeners. SubmitEventArgs e = new SubmitEventArgs(contribution, contributor); if (Submission != null) { Console.WriteLine("Broadcasting..."); // Raise Event. This calls the remote listening method on
// all clients of this object. Submission(this, e); } } } // Class to be used by clients to submit delegates to the ChatCoordinator object. // Clients must derive a custom class from this and override the InternalSubmissionCallback // function. InternalSubmissionCallback is where they need to implement their callback // logic. They must use the SubmissionCallback function in their remotable delegates. public abstract class RemotelyDelegatableObject :MarshalByRefObject { public void SubmissionCallback (object sender, SubmitEventArgs submitArgs) { InternalSubmissionCallback (sender, submitArgs) ; } protected abstract void InternalSubmissionCallback (object sender,
SubmitEventArgs submitArgs) ; }
// ChatCentral.cs using System; using System.Runtime.Remoting; public class ServerProcess { // This simply keeps the ChatCoordinator application domain alive. public static void Main(string[] Args) { RemotingConfiguration.Configure("ChatCentral.exe.config"); Console.WriteLine("The host application domain is running. Press Enter必须重写 ChatClient.cs 文件,其内容应如下所示。 请注意,MyCallbackClass 现在包含回调函数。 它是从 RemotelyDelegatableObject 派生来的。
again to stop the application domain."); Console.ReadLine(); } } }
// ChatClient.cs using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; public class ChatClient { private string _alias = null; public ChatClient(string alias) { this._alias = alias; } public void Run() { RemotingConfiguration.Configure("ChatClient.exe.config"); // Create a proxy to the remote object. ChatCoordinator chatcenter = new ChatCoordinator(); // Create a remotely delegatable object MyCallbackClass callback = new MyCallbackClass (_alias) ; // Create a new delegate for the method that you want the event to call // when it occurs on the server. The SubmissionReceiver method will // then be a remote call for the server object; therefore, you must // register a channel to listen for this call back from the server. chatcenter.Submission += new SubmissionEventHandlerChatCentral.exe.config 文件未发生更改。 它仍按如下方式进行定义:
(callback.SubmissionCallback); String keyState = ""; while (true) { Console.WriteLine("Press 0 (zero) and ENTER to Exit:\r\n"); keyState = Console.ReadLine(); if (String.Compare(keyState,"0", true) == 0) break; // Call the server with the string you submitted and your alias. chatcenter.Submit(keyState, _alias); } chatcenter.Submission -= new SubmissionEventHandler
(callback.SubmissionCallback); } // Args[0] is the alias that will be used. public static void Main(string[] Args) { if (Args.Length != 1) { Console.WriteLine("You need to type an alias."); return; } ChatClient client = new ChatClient(Args[0]); client.Run(); } } // MyCallbackClass is the class that contains the callback function to // which ChatClient will submit a delegate to the server. // To to pass a reference to this method (that is, a delegate) // across an application domain boundary, this class must extend // MarshalByRefObject or a class that extends MarshallByRefObject like all // other remotable types. MyCallbackClass extends RemotelyDelegatableObject because // RemotelyDelegatableObject is a class that the server can obtain type information // for. class MyCallbackClass : RemotelyDelegatableObject { private string _alias = null; public MyCallbackClass () {} public MyCallbackClass (string alias) { _alias = alias ; } // InternalSubmissionCallback is the method that is called by // RemotelyDelegatableObject.SubmissionCallback(). SubmissionCallback() is // sent to the server via a delegate. You want the chat server to call // when the Submission event occurs -- even if the submission is yours. // The SubmissionEventHandler delegate wraps this function and is passed // to the Add_Submission (SubmissionEventHandler delegate) member of // the ChatCoordinator object. The .NET Remoting system handles the transfer // of all information about the client object and channel necessary to // make a return remote call when the event occurs. protected override void InternalSubmissionCallback (object sender,
SubmitEventArgs submitArgs) { // Block out your own submission. // This simple chat server does not filter anything. if (String.Compare(submitArgs.Contributor, _alias, true) == 0) { Console.WriteLine("Your message was broadcast."); } else Console.WriteLine(submitArgs.Contributor + " says:\r\n" + new String('-', 80) + submitArgs.Contribution + "\r\n" + new String('-', 80) + "\r\n"); } // This override ensures that if the object is idle for an extended // period, waiting for messages, it won't lose its lease. Without this // override (or an alternative, such as implementation of a lease // sponsor), an idle object that inherits from MarshalByRefObject // may be collected even though references to it still exist. public override object InitializeLifetimeService() { return null; } }
<configuration> <system.runtime.remoting> <application> <service> <wellknown mode="Singleton" type="ChatCoordinator, ChatCoordinator" objectUri="Chat" /> </service> <channels> <channel ref="http" port="8080" /> </channels> </application> </system.runtime.remoting> </configuration>ChatClient.exe.config 文件也未发生更改。 它按照如下方式进行定义:
<configuration> <system.runtime.remoting> <application> <client> <wellknown type="ChatCoordinator, ChatCoordinator" url="http://localhost:8080/Chat" /> </client> <channels> <!-- The "0" port is declared to allow remoting to choose --> <!-- the most appropriate channel. You must specify a channel --> <!-- here, however; if you do not do so, your ChatClient --> <!-- will not be listening for the call back from the --> <!-- ChatCoordinator object. --> <channel ref="http" port="0" /> </channels> </application> </system.runtime.remoting> </configuration>此示例的生成指令也未发生更改。 .NET SDK 中的指令使用命令行编译器。