远程事件(适用于企业解决方案的简单高效)






4.84/5 (23投票s)
本文包含最简单的解决方案,用于解决以下问题:DelegateSerializationHolder 的安全问题、IO 问题和消息传递速度问题。 注意:消息传递速度问题将在应用程序运行很长时间后出现。
背景
我一直对套接字编程感兴趣。 我创建了几个聊天应用程序和基于复杂套接字的应用程序。 当我了解 Remoting 中的事件时,我很高兴,我想到的第一件事就是创建一个聊天应用程序。 但在测试中,我遇到了一些问题。
问题 1
第一个问题是安全异常
System.Security.SecurityException: Type System.DelegateSerializationHolder
and the types derived from it (such as System.DelegateSerializationHolder)
are not permitted to be deserialized at this security level.
解决方案 1
通过将 typeFilterLevel
属性(值为 Full
)添加到两个配置文件的 formatter
元素,可以解决此问题。
<formatter ref="soap" typeFilterLevel="Full" />
问题 2
但是,它再次不起作用,并发生 IO 异常
System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
System.IO.FileNotFoundException: Could not load file or assembly
'Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
or one of its dependencies. The system cannot find the file specified.
当在服务器端反序列化请求时,会发生此异常。 服务器尝试调用事件处理程序,而事件处理程序存在于客户端程序集中。 因为服务器上没有客户端程序集,所以会引发该异常。
解决方案 2
一个中间包装器类 MarshalByRefObject
将解决这个问题。 这个包装器类位于共享程序集中,客户端和服务器都可以访问;因此,委托可以解析方法的签名。 在客户端应用程序中,我们将共享对象事件与包装器类的 WrapperMessageReceivedHandler
方法关联,并将客户端上的事件处理程序与包装器类的 MessageReceived
事件关联。
为什么使用 [OneWay] 属性
如果不将远程方法定义为 [OneWay]
,则当客户端无法访问或已断开连接而未取消关联事件处理程序时,将发生异常。 通过使用 [OneWay]
,客户端上不会发生异常,但它将位于服务器的调用列表中,并且长时间运行会使您的服务器响应速度变慢。
解决方案 3
您必须自己调用每个委托,而不是使用正常的事件调用机制,并且如果发生异常,您必须从事件的调用列表中取消关联该委托。 最后,您可以删除 [OneWay]
属性。
共享类
/// <summary>
/// Represents the method that will handle
/// the Remotable.RemoteClass.MessageReceived event.
/// </summary>
/// <param name="message">Received message</param>
[Serializable]
public delegate void MessageHandler(string message);
/// <summary>
/// Shared remoting class that orchestrate messaging tasks
/// </summary>
public class RemoteClass:MarshalByRefObject
{
/// <summary>
/// Occurs when a broadcast message received.
/// </summary>
public event MessageHandler MessageReceived;
/// <summary>
/// Initializes a new instance of the Remotable.RemoteClass class.
/// </summary>
public RemoteClass()
{
}
/// <summary>
/// Obtains a lifetime service object to control the lifetime policy for this
/// instance.
/// </summary>
/// <returns>
///An object of type
///System.Runtime.Remoting.Lifetime.ILease used to control
///the lifetime policy for this instance. This is the current lifetime service
///object for this instance if one exists; otherwise, a new lifetime service
///object initialized to the value of the
///System.Runtime.Remoting.Lifetime.LifetimeServices.LeaseManagerPollTime
///property.
///null value means this object has to live forever.
/// </returns>
public override object InitializeLifetimeService()
{
return null;
}
/// <summary>
/// Broadcast message to all clients
/// </summary>
/// <param name="message">message string</param>
public void Send(string message)
{
if (MessageReceived != null)
{
MessageHandler messageDelegate = null;
Delegate[] invocationList_ = null;
try
{
invocationList_ = MessageReceived.GetInvocationList();
}
catch (MemberAccessException ex)
{
throw ex;
}
if (invocationList_ != null)
{
lock (this)
{
foreach (Delegate del in invocationList_)
{
try
{
messageDelegate = (MessageHandler)del;
messageDelegate(message);
}
catch (Exception e)
{
MessageReceived -= messageDelegate;
}
}
}
}
}
}
客户端应用程序
RemoteClass remoteClass;
WrapperClass wrapperClass;
private void Form1_Load(object sender, EventArgs e)
{
//Configure remoting.
RemotingConfiguration.Configure(Application.StartupPath +
"\\Client.exe.config",false);
// Create a proxy from remote object.
remoteClass = (RemoteClass)Activator.GetObject(typeof(RemoteClass),
"https://:8080/Chat");
//Create an instance of wrapper class.
wrapperClass = new WrapperClass();
//Associate remote object event with wrapper method.
remoteClass.MessageReceived += new
MessageHandler(wrapperClass.WrapperMessageReceivedHandler);
//Associate wrapper event with current form event handler.
wrapperClass.WrapperMessageReceived += new
MessageHandler(MessageReceivedHandler);
}
包装器类
/// <summary>
/// Occurs when a broadcast message received.
/// </summary>
public event MessageHandler WrapperMessageReceived;
/// <summary>
/// Wrapper method for sending message to the clients.
/// </summary>
/// <param name="sender">Caller object</param>
/// <param name="args">Message data</param>
public void WrapperMessageReceivedHandler(string message)
{
// forward the message to the client
if(WrapperMessageReceived != null)
WrapperMessageReceived(message);
}