WCF客户端与wcf 服务端发送客户端之间通信方式有哪有几...

.Net Remoting与WCF实现Server与Client通讯比较
.Net Remoting是微软早前推出的一项分布式通讯技术框架,在.Net架构的程序中有着比较广泛的应用。在WCF中,已经集成了Remoting的技术。不过,他们有着很多相同的概念,如:信道(Channel)、代理(Proxy)、寄宿(host)等。在如今仍有一些分布式应用中运行着由Remoting技术构建的系统。本文将描述在服务端与客户端的交互中,他们各自的实现方式。
  1、Remoting的实现。&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
  在Remoting中,远程对象是一个重要的概念。服务端通过将它注册到制定的信道中,客户端服务端公布的服务端注册的远程对象的URI,通过代理来使用它。在这种架构下的服务端与客户端要实现相互之间的通讯一般是使用事件的方式进行。
  Remoting的通讯模型中分三块,即:1、客户端、2、远程对象、3、寄宿程序(服务端寄宿的应用程序域)。
  注:远程对象一般通过继承自MarshalByRefObject,而继承自MarshalByRefObject的对象是不会离开它的应用程序域的。并且为了安全,一般我们都是通过将远程对象实现的接口提供给客户端,而不是远程对象。
  一、客户端发送消息到服务端。客户端获取来远程对象的代理,通过使用它的提供的接口以后,通过信道的传输便到了远程对象。远程对象在收到消息后,用事件的方式通过服务端以执行相应的操作。这时,事件是定义在远程对象中的,客户端进行操作以后,服务端可以直接通过订阅远程对象的事件而获取消息。
  二、服务端发送消息到客户端。在服务端,由于它是远程对象注册的应用程序域,服务端可以直接使用它。同样,服务端发送消息给客户端,也是通过事件来实现的。与客户端主动发送消息给服务端不同的是,定义在远程对象的事件运行在服务端,无法序列化到客户端。这种情况下,一般我们可以通过一种类似&中介者&的方式来进行操作。
  Remoting的程序结构如下图:
&&&&&&&&&&&&&&&&
  说明:
 Client:为客户端程序。为了可视性,为将他建成一个Winform的项目
 CommandAssembly:为公共程序集。它是客户端与服务端共享的一个类库,它包括远程对象实现的接口定义。
&& Host:为服务端程序,即远程对象寄宿的应用程序。同样它是一个Winform的项目
&& RemoteObject:为远程对象。
  首先,看看远程对象实现的接口定义:
01 /// &summary&&
02 /// 服务端通知客户端时的事件&
03 /// &/summary&&
04 event DataChangeCallBack DataChangeEventH&
06 /// &summary&&
07 /// 服务端促触发事件函数&
08 /// &/summary&&
09 /// ¶m name=&entityName&&&/param&&
10 void ServerSideDataChange(string entityName);&
12 /// &summary&&
13 /// 客户端调用服务端使用的接口函数&
14 /// &/summary&&
15 /// ¶m name=&entityName&&&/param&&
16 void ClientSideDataChange(string entityName);
&  其次:远程对象的定义:
public class RemoteObject : MarshalByRefObject, IDataChange&
&&&&&&& public event DataChangeCallBack DataChangeEventH&
&&&&&&& public event DataChangeCallBack ClientDataChangeEventH&
&&&&&&& #region IDataChange 成员&
&&&&&&& public void ServerSideDataChange(string entityName)&
&&&&&&& {&
&&&&&&&&&&& if (DataChangeEventHandler == null)&
&&&&&&&&&&& foreach (Delegate datachange in DataChangeEventHandler.GetInvocationList())&
&&&&&&&&&&& {&
&&&&&&&&&&&&&&& DataChangeCallBack temp = datachange as DataChangeCallB&
&&&&&&&&&&&&&&& try
&&&&&&&&&&&&&&& {&&&&&&&&&&&&&&&&&&&&&
&&&&& &&&&&&&&&&&&&&temp(entityName);&
&&&&&&&&&&&&&&& }&
&&&&&&&&&&&&&&& catch
&&&&&&&&&&&&&&& {&
&&&&&&&&&&&&&&&&&&& DataChangeEventHandler -=&
&&&&&&&&&&&&&&& }&
&&&&&&&&&&& }&
&&&&&&& }&
&&&&&&& public void ClientSideDataChange(string entityName)&
&&&&&&& {&
&&&&&&&&&&& if (ClientDataChangeEventHandler != null)&
&&&&&&&&&&& {&
&&&&&&&&&&&&&&& ClientDataChangeEventHandler(entityName);&
&&&&&&&&&&& }&
&&&&&&& }&
&&&&&&& #endregion&
&&&&&&& public override object InitializeLifetimeService()&
&&&&&&& {&
&&&&&&&&&&&&
&&&&&&& }&
&  注意:在远程对象的显示中,重写来基类的InitializeLifetimeService函数,目的是为了远程对象永不过期。这涉及到远程对象生命周期的问题。有兴趣的同学可以了解一下(为记忆中,远程对象生存期为6分钟)。如果不重写此函数,在远程对象被GC回收后,如果再有客户端想通过URI获取它的代理将会出错。因为它早已被GC回收了。
  再次,主要看看服务端通知客户端时,使用的&中介者&对象的定义:
public class EventWrapper : MarshalByRefObject,IDataChange&
&&&&&&& public event DataChangeCallBack DataChangeEventH&
&&&&&&& #region IDataChange 成员&
&&&&&&& public void ServerSideDataChange(string entityName)&
&&&&&&& {&
&&&&&&&&&&& if (DataChangeEventHandler!=null)&
&&&&&&&&&&& {&
&&&&&&&&&&&&&&& DataChangeEventHandler(entityName);&
&&&&&&&&&&& }&
&&&&&&& }&
&&&&&&& public void ClientSideDataChange(string entityName)&
&&&&&&& {&&
&&&&&&& }&
&&&&&&& #endregion&
&  最后就是服务端与客户端信道、事件注册,以及消息的发送问题了。这里主要给出客户端实现(因为涉及服务端事件通知客户端的问题)。服务端类似。
show sourceprivate void ClientForm_Load(object sender, EventArgs e)&
&&&&&&&& UnregisterChannels();&
&&&&&&&& RegisterChannel();&
&&&&&&&& RegisterEvent();&
&&&& void UnregisterChannels()&
&&&&&&&& if (ChannelServices.RegisteredChannels.Length == 0)&
&&&&&&&& {&
&&&&&&&&&&&&&
&&&&&&&& }&
&&&&&&&& foreach (IChannel channel in ChannelServices.RegisteredChannels)&
&&& &&&&&{&
&&&&&&&&&&&& ChannelServices.UnregisterChannel(channel);&
&&&&&&&& }&
&&&& void RegisterChannel()&
&&&&&&&& IDictionary hashtable = new Hashtable();&
&&&&&&&& hashtable[&port&] = 0;&
&&&&&&&& BinaryClientFormatterSinkProvider provider = new BinaryClientFormatterSinkProvider();&
&&&&&&&& TcpChannel tcpChannel = new TcpChannel(hashtable, provider, null);&
&&&&&&&& ChannelServices.RegisterChannel(tcpChannel, true);&
& &&&void RegisterEvent()&
&&&&&&&& instance = Activator.GetObject(typeof(IDataChange), &tcp://127.0.0.1:8888/DataService&) as IDataC&
&&&&&&&& wrapper = new EventWrapper();&
&&&&&&&& wrapper.DataChangeEventHandler+=new DataChangeCallBack(wrapper_DataChangeEventHandler);&
&&&&&&&& instance.DataChangeEventHandler += wrapper.ServerSideDataC&&&&&&&&&&&&&
&&&& void wrapper_DataChangeEventHandler(string entityName)&
&&&&&&&& if (txtReceiveMsg.InvokeRequired)&
&&&&&&&& {&
&&&&&&&&&&&& txtReceiveMsg.Invoke(new ShowMessageCallBack(wrapper_DataChangeEventHandler), entityName);&
&&&&&&&& }&
&&&&&&&& else
&&&&&&&& {&
&&&&&&&&&&&& txtReceiveMsg.Text = entityN&
&&&&&&&& }&
&&&& private void btnSend_Click(object sender, EventArgs e)&
&&&&&&&& if (!string.IsNullOrEmpty(txtSendMsg.Text))&
&&&&&&&& {&
&&&&&&&&&&&& instance.ClientSideDataChange(txtSendMsg.Text);&
&&&&&&&& }&
&  程序运行界面如下图:
  服务端:
  客户端:
&  注:在信道的定义中,应该使用与之对应的格式化标识符。
  2、WCF的实现&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
  在WCF中,实现服务端与客户端的通讯一般通过双工的模式进行。在WCF中支持双工的绑定协议有:netTcpBinding、wsDualHttpBingding。在WCF实现的服务端与客户端通讯的示例中,为选择来前一种协议。
  程序的结构图如下:
  同样,为了可视化,Host与Client也为Winform程序。
  在WCF的实现中,实现的功能为Remoting完全相同。
  首先看看服务契约定义:
[ServiceContract(CallbackContract=typeof(ICallBack))]&
public interface IDataChange&
&&& [OperationContract(IsOneWay=true)]&
&&& void Register();&
&&& [OperationContract(IsOneWay=true)]&
&&& void ServerSideDataChange(string entityName);&
&&& [OperationContract(IsOneWay = true)]&
&&& void ClientSideDataChange(string entityName);&
   回调契约定义:
public interface ICallBack&
&&& [OperationContract(IsOneWay=true)]&
&&& void DataChange(string entityName);&
&  服务的实现代码:
show source[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Single)]&
& public class DataService : IDataChange&
&&&&& public event DataChangeCallBack ReceiveClientDataChangeH&
&&&&& ICallBack callB&
&&&&& #region IDataChange 成员&
&&&&& public void Register()&
&&&&&&&&& callBack = OperationContext.Current.GetCallbackChannel&ICallBack&();&
&&&&& public void ServerSideDataChange(string entityName)&
&&&&&&&&& callBack.DataChange(entityName);&
&&&&& public void ClientSideDataChange(string entityName)&
&&&&&&&&& if (ReceiveClientDataChangeHandler != null)&
&&&&&&&&& {&
&&&&&&&&&&&&& ReceiveClientDataChangeHandler(entityName);&
&&&&&&&&& }&
&&&&& #endregion&
    服务在Winform中的寄宿代码如下:
private void ServerFrom_Shown(object sender, EventArgs e)&
&&& Thread thread = new Thread(ServiceStart);&
&&& thread.Start();&
void ServiceStart()&
&&& Uri address = new Uri(&net.tcp://127.0.0.1:8806/DataService&);&
&&& using (ServiceHost host = new ServiceHost(service, address))&
&&&&&&& host.Open();&
&&&&&&& tipStatus.Text = &服务已经启动&;&
&&&&&&& while (true)&
&&&&&&& {&
&&&&&&&&&&& Thread.Sleep(100);&
&&&&&&& }&
  而在客户端,直接通过窗体实现了回调接口,将窗体封装在InstanceContext里,让窗体对象成为服务端回调操作的对象,服务端回调客户端时直接将信息发送给窗体。在客户端窗体的FormLoad事件里做如下定义:
InstanceContext context = new InstanceContext(this);&
dataChange = new ClientProxy(context);&
dataChange.Register();
&  客户端通知服务端的方式比较简单,也就是主动去调用服务接口。代码如下:
      if (!string.IsNullOrEmpty(txtSendMsg.Text))&
&&&&&&&&&& {&
&&&&&&&&&&&&&& dataChange.ClientSideDataChange(txtSendMsg.Text);&
&&&&&&&&&& }
&  这样,在客户端调用服务端接口时候,触发服务端事件来通知窗体做数据显示。
  服务通知客户端时通过回调客户端,将消息发送给客户端。代码如下:
Form.cs if (!string.IsNullOrEmpty(txtSendMsg.Text))&&&&&&&&&&
& {&&&&&&&&&&&&&&&
service.ServerSideDataChange(txtSendMsg.Text);&&&&&&&&&&&
}//DataSerive.cs public void ServerSideDataChange(string entityName)&&&&&&
& {&&&&&&&&&&& callBack.DataChange(entityName);&&&&&&& } 
 如此这般,便实现了在WCF架构下实现服务端与客户端的通讯。
  运行界面与上面类似。就不重复给出了。
  代码下载地址:
作者 tyb1222
(window.slotbydup=window.slotbydup || []).push({
id: '2467140',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467141',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467142',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467143',
container: s,
size: '1000,90',
display: 'inlay-fix'
(window.slotbydup=window.slotbydup || []).push({
id: '2467148',
container: s,
size: '1000,90',
display: 'inlay-fix'废话先:最近事情比较多,有些不知所措了。那就写写博客,让自己冷静下来。
在WCF中与服务器通信,可以通过共享WSDL-契约,当然在非常松散耦合的项目里,对客户端和服务端的代码有绝对的控制权,可以使用channelFactory类而不是自动创建的代理与服务器进行通信,怎么说呢,这个channelFactory是用在中间层的,它的好处就是提高了系统的性能,channelFactory对象只是为每个客户端打开一个独立的通道。
下面我们先来说说通过WSDL,客户端和服务端进行通信
在SOA的世界中呢,使用某一个特定的服务提供的服务,通常是得不到dll的,取而代之的是WSDL文档,这个文档被共享,也就是说,所有的客户端和服务都可以得到它,在WSDL里包含了所有的信息,比如:服务的操作,交换数据的结构,安全等等。客户端是通过WSDL文档与服务端交流的,至于服务端内部是怎么实现的,客户端没有必要知道,客户端也与服务端不同享任何的代码。那么我们怎么生成这个代理呢?
首先了我们先来晚上下我们服务端的代码的编写。这是我的服务端的项目结构:
在这里呢,我们先不管IEmployee.cs和Employee.cs。在这个项目中我们还用不到,这是关于在WCF中使用REST技术的Demo.具体的下次我会发布在我的博客园上来与大家共享。在我们的IprintStr.cs接口中呢,我们来定义如下
namespace serviceHost
[ServiceContract]
public interface IprintStr
[OperationContract]
string printStr(string name);
与之对应的printStr.cs中也就是实现上面的接口
namespace serviceHost
public class service:IprintStr
public string printStr(string name)
因为只是一个简单的测试,这里没有写得那么的复杂。
好了,到了这步,我们应该就要写配置文件了,当然了,你也可以通过代码实现配置文件中的配置信息,不过我个人觉得,这样做,有些浪费时间,有更好的方法,就用更好的方法呗。
这是我在App.config文件里的配置信息
&?xml version="1.0" encoding="utf-8" ?&
&configuration&
&system.serviceModel&
&service name="serviceHost.service" behaviorConfiguration="messageBehavior"&
&baseAddresses&
&add baseAddress="http://localhost:4040/getInfo/"/&
&/baseAddresses&
&endpoint address="" binding="wsHttpBinding" contract="serviceHost.IprintStr"/&
&endpoint address="net.tcp://localhost:5050/" binding="netTcpBinding" contract="serviceHost.IprintStr"/&
&/service&
&/services&
&behaviors&
&serviceBehaviors&
&behavior name="messageBehavior"&
&serviceMetadata httpGetEnabled="true"/&
&serviceDebug includeExceptionDetailInFaults="true"/&
&/behavior&
&/serviceBehaviors&
&/behaviors&
&/system.serviceModel&
&/configuration&
在配置文件中的&service&中的name属性的命名规范是:命名空间+类名,而在&endpoint&中的contract的命规范是:命名空间+接口名。这一点一定要搞清楚,娶她的也没有什么好说的,当然如果你想查看元数据,可以在&serviceMetadata&中进行配置。这里我不在多说了。最后一点就是在&service&&/service&中&baseAddress&&/baseAddress&里面可以添加多个&add&&/add&也就是基地址,这里要说的就是一个服务可以有多个基地址,但是呢,一个协议只能有一个基地址。使用基地址的好处就是多个终结点可以使用同一个基地址,当你定义了一个基地址比如是,这是你可以在&endpoint&中的address属性使用相对的地址比如&endpoint address="first" binding="" contract="&这样配置了后当访问这个终结点的时候的地址就是.
那么我们现在就启动我们的服务吧。
using (ServiceHost host = new ServiceHost(typeof(serviceHost.service)))
host.Open();
Console.Write("service has opened !");
Console.ReadLine();
成功运行后就是这样了:
恩,就让这服务开着吧,暂时不管他。
在客户端,我们可是使用vs为我们生成代理的功能,操作如图所示:
在弹出的对话框里,我们把刚刚配置文件的地址填进去。只要服务开着,他会自动搜索。
等他完成后你会看到在你的项目里多出了一个配置文件。我这边得到的是这样的:
&?xml version="1.0" encoding="utf-8" ?&
&configuration&
&system.serviceModel&
&bindings&
&netTcpBinding&
&binding name="NetTcpBinding_IprintStr" /&
&/netTcpBinding&
&wsHttpBinding&
&binding name="WSHttpBinding_IprintStr" /&
&/wsHttpBinding&
&/bindings&
&endpoint address="http://localhost:4040/getInfo/" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IprintStr" contract="ServiceReference1.IprintStr"
name="WSHttpBinding_IprintStr"&
&identity&
&userPrincipalName value="A-Smart\.NET-Smart" /&
&/identity&
&/endpoint&
&endpoint address="net.tcp://localhost:5050/" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IprintStr" contract="ServiceReference1.IprintStr"
name="NetTcpBinding_IprintStr"&
&identity&
&userPrincipalName value="A-Smart\.NET-Smart" /&
&/identity&
&/endpoint&
&/system.serviceModel&
&/configuration&
好了,我们写到这里,这个HOW?就快实现了!细心的读者可能发现了,我这边的服务端和客户端的配置文件里有两个&endpoint&,而且使用的binding方式也不一样,我们为什么要这样写?在这里我是想对比下这两个绑定方式的速度。稍后大家就可以看到具体的效果了。
这是我客户端的项目结构:
我们在P_Client中这样写:
public static void getNameByWsHttpBinding()
/*这是第二个办法,也就是通过客户端代理与服务端进行通信*/
Console.WriteLine("======线程getNameByWsHttpBinding开始========");
ServiceReference1.IprintStrClient cl = new ServiceReference1.IprintStrClient("WSHttpBinding_IprintStr");
Stopwatch watch = new Stopwatch();
watch.Start();
string result = cl.printStr("This is about getName by wsHttpBinding");
watch.Stop();
Console.WriteLine(result + "耗时:" + watch.ElapsedMilliseconds);
Console.WriteLine("============================================\n");
还有一个就是这样
public static void getNameByNetTcpBinding()
Console.WriteLine("======线程getNameByNetTcpBinding开始========");
ServiceReference1.IprintStrClient c2 = new ServiceReference1.IprintStrClient("NetTcpBinding_IprintStr");
Stopwatch watch = new Stopwatch();
watch.Start();
string result = c2.printStr("This is about getName by nettcpBinding");
watch.Stop();
Console.WriteLine(result + "耗时:" + watch.ElapsedMilliseconds);
Console.WriteLine("============================================");
通过方法名大家可以看出了,我们通过代理来与服务端进行通讯,在ServiceReference1.IprintStrClient()中的字符串是客户端配置文件中的&endpoint&中的name属性。好了,配置到这,我们也该进行最后的运行了。
在program.cs中的代码如下
static void Main(string[] args)
Thread th1 = new Thread(new ThreadStart(p_Client.getNameByNetTcpBinding));
Thread th2 = new Thread(new ThreadStart(p_Client.getNameByWsHttpBinding));
th1.Start();
th2.Start();
Console.ReadKey();
运行后得到的结果如图:
通过时间的对比大家可以看出来了吧。
好,到此我们将通过代理与服务端通信的说完了,下面我们来说说通过 channelFactory来与服务器经行通信。
其它的没有什么大的变化,就是要改一点,在客户端对应的改成如下:
public static void getNameByWsHttpBinding()
ChannelFactory&IprintStr& printName = new ChannelFactory&IprintStr&(new WSHttpBinding(), new EndpointAddress("http://localhost:4040/getInfo/"));
var client = printName.CreateChannel();
Console.WriteLine("=========线程getNameByWsHttpBinding开始========");
Stopwatch watch = new Stopwatch();
watch.Start();
string result = client.printStr("This function is about getName by wsHttpBinding");
watch.Stop();
Console.WriteLine(result);
Console.WriteLine("The time is:{0} ms", watch.ElapsedMilliseconds);
Console.WriteLine("======================================================");
与之对应的也就是这样了:
public static void getNameByNetTcpBinding()
ChannelFactory&IprintStr& printName = new ChannelFactory&IprintStr&(new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:5050/"));
var client = printName.CreateChannel();
Console.WriteLine("=========线程getNameByNetTcpBinding开始==========");
Stopwatch watch = new Stopwatch();
watch.Start();
string result = client.printStr("This function is about getName by netTcpBinding");
watch.Stop();
Console.WriteLine(result);
Console.WriteLine("The time is :{0} ms", watch.ElapsedMilliseconds);
Console.WriteLine("======================================================");
其它的不需要进行变化,运行后的结果我这里也不再贴了,总之,通过这样的方法,所耗的时间比上面的方法要少,不管是wsHttpBinding还是netTcpBinding.最后的运行留给读者你们把。
本篇博文属于原创,允许转载,但是请保留原始的连接。博文中可能有写的不对的地方,欢迎点评。
阅读(...) 评论()2011年8月 总版技术专家分月排行榜第三
2012年10月 荣获微软MVP称号2011年10月 荣获微软MVP称号
本帖子已过去太久远了,不再提供回复功能。

参考资料

 

随机推荐