最近研究了一下.Net Remoting技术,做了个实例,简单记录下来,以便参考。
概念性的东西不多说了,前面也转载了几篇文章,写的都不错,这里主要说说我的这个实例的实现过程。
这个实例包含三个项目:Chat_Server,Chat_Client,Chat_CommonLib,即服务端,客户端和公共程序集,一般.Net Remoting都包含这三块。
先来看看Chat_CommonLib项目,该项目是一个类库程序,包含两个接口IRemoteObjectFactory,IRemoteObject,一个类EventWrapper和一个公共委托MessagedEventHandler。
namespace Chat_CommonLib
{
public interface IRemoteObjectFactory
{
IRemoteObject CreateInstance(string user);
}
}
namespace Chat_CommonLib
{
public delegate void MessagedEventHandler(string msg);
public interface IRemoteObject
{
string User { get; set; }
void Connect();
void Disconnect();
void SendMessage(string msg);
void SubMessage(string msg);
event MessagedEventHandler Messaged;
}
}
namespace Chat_CommonLib
{
public class EventWrapper:MarshalByRefObject
{
public event MessagedEventHandler Messaged;
public void SubMessage(string msg)
{
if (Messaged != null)
{
MessagedEventHandler temp = null;
foreach (Delegate del in Messaged.GetInvocationList())
{
try
{
temp = (MessagedEventHandler)del;
temp(msg);
}
catch (Exception ex)
{
//RemoteObjectFactory.SubLogger("Send event message fail.\r\n" + ex.ToString());
}
}
}
}
public override object InitializeLifetimeService()
{
return null;
}
}
}
在服务端实现远程对象,注意远程对象及其工厂都要继承MarshalByRefObject,并实现先前定义的接口
namespace Chat_Server.Codes
{
class RemoteObjectFactory : MarshalByRefObject, IRemoteObjectFactory
{
public static event Action<string> Logger;
public static List<IRemoteObject> RemoteObjects = new List<IRemoteObject>();
public IRemoteObject CreateInstance(string user)
{
IRemoteObject remoteObject = new RemoteObject(user);
RemoteObjects.Add(remoteObject);
return remoteObject;
}
public static void SubLogger(string msg)
{
if (Logger != null)
{
Logger(msg);
}
}
public static void SendMessage(IRemoteObject user, string msg)
{
user.SubMessage(msg);
}
public static void SendMessage(IRemoteObject user, string msg, bool self)
{
foreach (IRemoteObject _user in RemoteObjects)
{
if (_user != user || self)
{
SendMessage(_user, msg);
}
}
}
public override object InitializeLifetimeService()
{
return null;
}
}
}
namespace Chat_Server.Codes
{
class RemoteObject : MarshalByRefObject, IRemoteObject
{
public event MessagedEventHandler Messaged;
private string mUser;
public string User
{
get
{
return mUser;
}
set
{
mUser = value;
}
}
public RemoteObject(string user)
{
mUser = user;
}
public void Connect()
{
string msg = string.Format("{0}\tConnected.", mUser);
RemoteObjectFactory.SubLogger(msg);
RemoteObjectFactory.SendMessage(this, "Server connected.");
RemoteObjectFactory.SendMessage(this, msg, false);
}
public void Disconnect()
{
string msg = string.Format("{0}\tDisconnected.", mUser);
RemoteObjectFactory.RemoteObjects.Remove(this);
}
public void SubMessage(string msg)
{
if (Messaged != null)
{
MessagedEventHandler temp = null;
foreach (Delegate del in Messaged.GetInvocationList())
{
try
{
temp = (MessagedEventHandler)del;
temp(msg);
}
catch (Exception ex)
{
RemoteObjectFactory.SubLogger("Send event message fail.\r\n" + ex.ToString());
}
}
}
}
public override object InitializeLifetimeService()
{
return null;
}
public void SendMessage(string msg)
{
RemoteObjectFactory.SendMessage(this, this.User + "\t" + msg, true);
}
}
}
注册通道,我这里采用服务端SingleTon激活方式,但是功能上看类似于客户端激活方式,因为远程对象实在客户端实例化的,并且每个客户端独立使用一个远程对象。
服务端通道注册:
private bool CreateRemoteObject()
{
try
{
SoapServerFormatterSinkProvider soap = new SoapServerFormatterSinkProvider();
BinaryServerFormatterSinkProvider binary = new BinaryServerFormatterSinkProvider();
soap.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
binary.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
soap.Next = binary;
Hashtable table = new Hashtable();
table.Add("port", II_ServerPort);
TcpChannel channel = new TcpChannel(table, null, soap);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.ApplicationName = "RemotingChat";
RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObjectFactory), "RemotingChat", WellKnownObjectMode.Singleton);
}
catch (Exception ex)
{
IS_Error = ex.ToString();
return false;
}
return true;
}
客户端通道注册及远程对象的激活:
private bool MakeConnection()
{
try
{
SoapServerFormatterSinkProvider soap = new SoapServerFormatterSinkProvider();
BinaryServerFormatterSinkProvider binary = new BinaryServerFormatterSinkProvider();
soap.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
binary.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
soap.Next = binary;
Hashtable table = new Hashtable();
table.Add("port", "0");
TcpChannel channel = new TcpChannel(table, null, soap);
ChannelServices.RegisterChannel(channel, false);
string url = string.Format("tcp://{0}:{1}/RemotingChat", IS_ServerIP, II_ServerPort);
IRemoteObjectFactory objectFactory = (IRemoteObjectFactory)Activator.GetObject(typeof(IRemoteObjectFactory), url);
I_RemoteObject = objectFactory.CreateInstance(GetCurrentIP());
EventWrapper eventWrapper = new EventWrapper();
eventWrapper.Messaged += new MessagedEventHandler(eventWrapper_Messaged);
I_RemoteObject.Messaged += new MessagedEventHandler(eventWrapper.SubMessage);
I_RemoteObject.Connect();
}
catch (Exception ex)
{
IS_Error = ex.ToString();
return false;
}
return true;
}
主要代码就如以上所述,下面重点说说其中要注意的几点:
1、工厂模式,工厂模式是一种极为常见的编程方式,其核心就一个CreateInstance方式,能够返回指定类型的实例,在这个实例中RemoteObject就工厂RemoteObjectFactory返回的一个对象,可以在客户端调用这个方法。
2、代码组织,由于远程对象在服务端和客户端都要引用,一个好的办法就是利用接口,在接口中定义远程对象的各个方法事件等,在服务端具体实现,而客户端只需引用对应的接口就行,并且接口和其他可能需要封送的的类都放在公共程序集中,这样代码更加清晰,引用也方便。
3、远程对象事件及事件的订阅,这是最难也是最容易出错的地方,尤其客户端订阅服务端事件。客户端是通过代理引用远程对象的,因而客户端引用的并不是真正的远程对象,而是其代理,所以对其订阅事件并不能这真订阅到远程对象本身。一个好的办法就是包装事件,即设计一个事件包装器类EventWrapper,其中的事件和远程对象的定义样,这个类还必须继承MarshalByRefObject以实现远程封送,通过这个中间类可以实现客户端订阅服务端事件。