C# NetRemoting实现双向通信
闲来无事想玩玩双向通信,实现类似QQ的互发消息的功能。于是乎开始学习.NetRemoting.
.NetRemoting是由客户端通过Remoting,访问通道以获得服务端对象,再通过代理解析为客户端对象来实现通信的。也就是说对象是由服务端创建的。
先上代码
首先是ICommand库
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; namespaceICommand { publicinterfaceIRemotingObject { eventSendHandlerClientToServer; eventReceiveHandlerServerToClient; eventUserChangedHandlerLogin; eventUserChangedHandlerExit; //////加法运算 /// ///参数1 /// 参数2 /// stringSUM(intx1,intx2); /// ///获取服务端事件列表 /// Delegate[]GetServerEventList(); //////发送消息 /// ////// voidToServer(objectinfo,stringtoName); /// ///接受信息 /// ////// voidToClient(objectinfo,stringtoName); voidToLogin(stringname); voidToExit(stringname); } /// ///客户端发送消息 /// ///信息 /// 发送给谁,""表示所有人,null表示没有接收服务器自己接收,其他表示指定某人 publicdelegatevoidSendHandler(objectinfo,stringtoName); /// ///客户端接收消息 /// ///信息 /// 发送给谁,""表示所有人,null表示没有接收服务器自己接收,其他表示指定某人 publicdelegatevoidReceiveHandler(objectinfo,stringtoName); /// ///用户信息事件 /// ///用户名 publicdelegatevoidUserChangedHandler(stringname); }
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; namespaceICommand { publicclassSwapObject:MarshalByRefObject { publiceventReceiveHandlerSwapServerToClient { add{_receive+=value;} remove{_receive-=value;} } //////接受信息 /// ////// publicvoidToClient(objectinfo,stringtoName) { if(_receive!=null) _receive(info,toName); } //无限生命周期 publicoverrideobjectInitializeLifetimeService() { returnnull; } privateReceiveHandler_receive; } }
第一个类就是定义一些接口,和一些委托,没有实质性的东西。
第二个类是定义了上一个接口类中的ToClient的事件和方法,作用之后会讲到。
然后就是集成ICommand接口的实质性的数据类
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; usingICommand; namespaceNetRemoting { publicclassRemotingObject:MarshalByRefObject,IRemotingObject { //////发送事件 /// publiceventSendHandlerClientToServer { add{_send+=value;} remove{_send-=value;} } //////接收消息事件 /// publiceventReceiveHandlerServerToClient; //////发送事件 /// publiceventUserChangedHandlerLogin { add{_login+=value;} remove{_login-=value;} } //////发送事件 /// publiceventUserChangedHandlerExit { add{_exit+=value;} remove{_exit-=value;} } //////加法运算 /// ///参数1 /// 参数2 /// publicstringSUM(intx1,intx2) { returnx1+"+"+x2+"="+(x1+x2); } /// ///绑定服务端向客户端发送消息的事件方法 /// ///接收事件 publicDelegate[]GetServerEventList() { returnthis.ServerToClient.GetInvocationList(); } /// ///发送消息 /// ////// publicvoidToServer(objectinfo,stringtoName) { if(_send!=null) _send(info,toName); } /// ///接收消息 /// ////// publicvoidToClient(objectinfo,stringtoName) { if(_receive!=null) _receive(info,toName); } /// ///登录 /// ///用户名 publicvoidToLogin(stringname) { if(!_nameHash.Contains(name)) { _nameHash.Add(name); if(_login!=null) _login(name); } else {thrownewException("用户已存在");} } /// ///退出 /// ///用户名 publicvoidToExit(stringname) { if(_nameHash.Contains(name)) { _nameHash.Remove(name); if(_exit!=null) _exit(name); } } privateSendHandler_send; privateReceiveHandler_receive; privateUserChangedHandler_login; privateUserChangedHandler_exit; privateHashSet _nameHash=newHashSet (); } }
该类集成了MarshalByRefObject
由于Remoting传递的对象是以引用的方式,因此所传递的远程对象类必须继承MarshalByRefObject。MSDN对MarshalByRefObject的说明是:MarshalByRefObject是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类。不是从MarshalByRefObject继承的对象会以隐式方式按值封送。当远程应用程序引用一个按值封送的对象时,将跨越远程处理边界传递该对象的副本。因为您希望使用代理方法而不是副本方法进行通信,因此需要继承MarshallByRefObject。
该类主要是定义了一些方法用于客户端触发事件,ToServer,ToClient,ToLogin,ToExit以及一些事件,客户端发向服务端的事件,和服务端发向客户端的事件。
_nameHash只是记录有哪些用户登录了。
接下去就是客户端和服务端了。
首先服务端:
usingSystem; usingSystem.Collections.Generic; usingSystem.ComponentModel; usingSystem.Data; usingSystem.Drawing; usingSystem.Linq; usingSystem.Text; usingSystem.Windows.Forms; usingSystem.Runtime.Remoting; usingSystem.Runtime.Remoting.Channels; usingSystem.Runtime.Remoting.Channels.Http; usingNetRemoting; usingSystem.Collections; usingSystem.Runtime.Serialization.Formatters; usingICommand; namespaceNetRemotingServer { publicpartialclassServer:Form { publicServer() { InitializeComponent(); Initialize(); } //////注册通道 /// ////// privatevoidServer_Load(objectsender,EventArgse) { ChannelServices.RegisterChannel(_channel,false); //RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject),"SumMessage",WellKnownObjectMode.Singleton);//a方案 /*将给定的System.MarshalByRefObject转换为具有指定URI的System.Runtime.Remoting.ObjRef类的实例。 ObjRef:存储生成代理以与远程对象通信所需要的所有信息。*/ ObjRefobjRef=RemotingServices.Marshal(_remotingObject,"SumMessage");//b方案 _remotingObject.ClientToServer+=(info,toName)=> { rxtInfo.Invoke((MethodInvoker)(()=>{rxtInfo.AppendText(info.ToString()+"\r\n");})); SendToClient(info,toName); }; _remotingObject.Login+=(name)=> { rxtInfo.Invoke((MethodInvoker)(()=>{rxtInfo.AppendText(name+"登录"+"\r\n");})); }; _remotingObject.Exit+=(name)=> { rxtInfo.Invoke((MethodInvoker)(()=>{rxtInfo.AppendText(name+"退出"+"\r\n");})); }; } /// ///注销通道 /// ////// privatevoidServer_FormClosing(objectsender,FormClosingEventArgse) { if(_channel!=null) { _channel.StopListening(null); ChannelServices.UnregisterChannel(_channel); } } /// ///广播消息 /// ////// privatevoidbtnSend_Click(objectsender,EventArgse) { SendToClient(txtSend.Text,txtName.Text); } /// ///发送消息到客户端 /// ////// privatevoidSendToClient(objectinfo,stringtoName) { //foreach(varvin_remotingObject.GetServerEventList()) //{ //try //{ //ReceiveHandlerreceive=(ReceiveHandler)v; //receive.BeginInvoke(info,toName,null,null); //} //catch //{} //} _remotingObject.ToClient(txtSend.Text,txtName.Text); } /// ///初始化 /// privatevoidInitialize() { //设置反序列化级别 BinaryServerFormatterSinkProviderserverProvider=newBinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProviderclientProvider=newBinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel=TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 IDictionaryidic=newDictionary(); idic["name"]="serverHttp"; idic["port"]="8022"; _channel=newHttpChannel(idic,clientProvider,serverProvider); _remotingObject=newRemotingObject(); } HttpChannel_channel; privateRemotingObject_remotingObject; } }
然后客户端:
usingSystem; usingSystem.Collections.Generic; usingSystem.ComponentModel; usingSystem.Data; usingSystem.Drawing; usingSystem.Linq; usingSystem.Text; usingSystem.Windows.Forms; usingSystem.Runtime.Remoting; usingSystem.Runtime.Remoting.Channels; usingSystem.Runtime.Remoting.Channels.Http; usingICommand; usingSystem.Runtime.Serialization.Formatters; usingSystem.Collections; namespaceNetRemotingClient { publicpartialclassClient:Form { publicClient() { InitializeComponent(); } //////注册通道 /// ////// privatevoidClient_Load(objectsender,EventArgse) { try { //设置反序列化级别 BinaryServerFormatterSinkProviderserverProvider=newBinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProviderclientProvider=newBinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel=TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionaryidic=newDictionary (); idic["name"]="clientHttp"; idic["port"]="0"; HttpChannelchannel=newHttpChannel(idic,clientProvider,serverProvider); ChannelServices.RegisterChannel(channel,false); _remotingObject=(IRemotingObject)Activator.GetObject(typeof(IRemotingObject),"http://localhost:8022/SumMessage"); //_remotingObject.ServerToClient+=(info,toName)=>{rtxMessage.AppendText(info+"\r\n");}; SwapObjectswap=newSwapObject(); _remotingObject.ServerToClient+=swap.ToClient; swap.SwapServerToClient+=(info,toName)=> { rtxMessage.Invoke((MethodInvoker)(()=> { if(toName==txtLogin.Text||toName=="") rtxMessage.AppendText(info+"\r\n"); })); }; } catch(Exceptionex) {MessageBox.Show(ex.Message);} } /// ///登录 /// ////// privatevoidbtnLogin_Click(objectsender,EventArgse) { try { if(txtLogin.Text=="") thrownewException("用户名不得为空"); _remotingObject.ToLogin(txtLogin.Text); } catch(Exceptionex) { MessageBox.Show(ex.Message); } } /// ///退出 /// ////// privatevoidClient_FormClosing(objectsender,FormClosingEventArgse) { try { _remotingObject.ToExit(txtLogin.Text); } catch {} } /// ///发送 /// ////// privatevoidbtnSend_Click(objectsender,EventArgse) { //rtxMessage.AppendText(_remotingObject.SUM(2,4)+"\r\n"); _remotingObject.ToServer(txtSend.Text,txtName.Text); } privateIRemotingObject_remotingObject; } }
服务端实现步骤:
1、注册通道
要跨越应用程序域进行通信,必须实现通道。如前所述,Remoting提供了IChannel接口,分别包含TcpChannel和HttpChannel两种类型的通道。这两种类型除了性能和序列化数据的格式不同外,实现的方式完全一致,因此下面我们就以TcpChannel为例。
注册TcpChannel,首先要在项目中添加引用“System.Runtime.Remoting”,然后using名字空间:System.Runtime.Remoting.Channel.Tcp。代码如下:
TcpChannelchannel=newTcpChannel(8022); ChannelServices.RegisterChannel(channel);
在实例化通道对象时,将端口号作为参数传递。然后再调用静态方法RegisterChannel()来注册该通道对象即可。
2、注册远程对象
注册了通道后,要能激活远程对象,必须在通道中注册该对象。根据激活模式的不同,注册对象的方法也不同。
(1)SingleTon模式
对于WellKnown对象,可以通过静态方法RemotingConfiguration.RegisterWellKnownServiceType()来实现:
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleTon);
(2)SingleCall模式
注册对象的方法基本上和SingleTon模式相同,只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了。
RemotingConfiguration.RegisterWellKnownServiceType( typeof(ServerRemoteObject.ServerObject), "ServiceMessage",WellKnownObjectMode.SingleCall);
客户端实现步骤:
1、注册通道:
TcpChannelchannel=newTcpChannel(); ChannelServices.RegisterChannel(channel);
注意在客户端实例化通道时,是调用的默认构造函数,即没有传递端口号。事实上,这个端口号是缺一不可的,只不过它的指定被放在后面作为了Uri的一部分。
2、获得远程对象。
与服务器端相同,不同的激活模式决定了客户端的实现方式也将不同。不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别,而对于SingleTon和SingleCall模式,客户端的实现完全相同。
(1)WellKnown激活模式
要获得服务器端的知名远程对象,可通过Activator进程的GetObject()方法来获得:
ServerRemoteObject.ServerObjectserverObj=(ServerRemoteObject.ServerObject)Activator.GetObject( typeof(ServerRemoteObject.ServerObject),"tcp://localhost:8080/ServiceMessage");
首先以WellKnown模式激活,客户端获得对象的方法是使用GetObject()。其中参数第一个是远程对象的类型。第二个参数就是服务器端的uri。如果是http通道,自然是用http://localhost:8022/ServiceMessage了。因为我是用本地机,所以这里是localhost,你可以用具体的服务器IP地址来代替它。端口必须和服务器端的端口一致。后面则是服务器定义的远程对象服务名,即ApplicationName属性的内容。
//设置反序列化级别 BinaryServerFormatterSinkProviderserverProvider=newBinaryServerFormatterSinkProvider(); BinaryClientFormatterSinkProviderclientProvider=newBinaryClientFormatterSinkProvider(); serverProvider.TypeFilterLevel=TypeFilterLevel.Full;//支持所有类型的反序列化,级别很高 //信道端口 IDictionaryidic=newDictionary(); idic["name"]="clientHttp"; idic["port"]="0"; HttpChannelchannel=newHttpChannel(idic,clientProvider,serverProvider);
从上述代码中可以看到注册方式有所变化,那是因为客户端注册服务端的事件时会报错“不允许类型反序列化”。
还有一个需要注意的是:
ObjRefobjRef=RemotingServices.Marshal(_remotingObject,"SumMessage"); //RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingObject),"SumMessage",WellKnownObjectMode.Singleton); //调用系统自动创建,导致拿不到_remotingObject对象的实例化,这样后期绑定事件就无法操作下去了,当然也可以直接静态事件绑定,这样就不需要手动实例化对象了
通过该方法手动创建_remotingObject这个对象的实例化。
然后之前讲到了一个SwapObject这个类,这个类的作用是事件交换。
_remotingObject.ServerToClient+=方法(); //这样因为这个方法是客户端的,服务端无法调用,所以需要一个中间转换的 SwapObjectswap=newSwapObject();//先创建一个Swap对象 _remotingObject.ServerToClient+=swap.ToClient;//然后服务端事件发信息给swap,然后swap再通过事件发消息给客户端,swap是客户端创建的所以可以发送,而swap是服务端的类,所以服务端也能识别,swap起到了中间过渡的作用 swap.SwapServerToClient+=方法();
以上是两天.NetRemoting的学习结果。
最后附上源码:NetRemoting_jb51.rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。