WCF中的服务self-host到console中,所以在文件夹/WCF service_2010.9.5/WcfServiceLibrary1/Host/bin/Debug下,先打开Host.exe,开启服务托管。
Client端通过Form中实现,思路是,在From的构造函数中,另开启一个线程hostThread,代理服务(m_proxy),用DuplexChannelFactory建立一个channel,实例化m_proxy,然后在UI线程中调用服务方法Add(),Add()方法(在WCF服务中实现了)又实例化一个回调对象,调用客户端的一个方法DisplayResult(double x, double y, double result),打印出计算结果。在线程hostThread中修改UI线程中的控件,又需要用到Invoke和委托,在我的上一篇文章中,有3篇文章链接,对Invoke和委托讲得很清晰。
大概流程图
代码结构如下
WcfServiceLibrary_ppz中Contracts是服务契约,里面有Add方法和回调方法,Services是契约的具体实现,代码分别如下
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceLibrary_ppz { // 注意: 如果更改此处的接口名称“IService1”,也必须更新 App.config 中对“IService1”的引用。 //[ServiceContract] [ServiceContract(CallbackContract=typeof(ICallback))] //该服务接口中包含了回调服务的接口 public interface Contracts { // 任务: 在此处添加服务操作 //[OperationContract] [OperationContract(IsOneWay=true)] //单向 void Add(double x, double y); } /// <summary> /// 回调服务的接口定义 /// 已经在上面的接口中,通过[ServiceContract(CallbackContract=typeof(ICallback))]指明ICallback是一个服务契约了, /// 所以ICallback不再需要添加ServiceContractAttribute特性 /// </summary> public interface ICallback { [OperationContract(IsOneWay=true)] void DisplayResult(double x, double y, double result); } }
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfServiceLibrary_ppz { // 注意: 如果更改此处的类名“IService1”,也必须更新 App.config 中对“IService1”的引用。 public class Services : Contracts { /// <summary> /// 添加了回调函数 /// </summary> /// <param name="x"></param> /// <param name="y"></param> public void Add(double x, double y) { double result = x + y; //回调对象 #region OperationContext的注释 // OperationContext在WCF中是一个非常重要、也是一个十分有用的对象, //它代表服务操作执行的上下文。我们可以通过静态属性Current(OperationContext.Current)得到当前的OperationContext。 //借助OperationContext,我们可以在服务端或者客户端获取或设置一些上下文, //比如在客户端可以通过它为出栈消息(outgoing message)添加SOAP报头,以及HTTP报头(比如Cookie)等。 //在服务端,则可以通过OperationContex获取在客户端设置的SOAP报头和HTTP报头。 //关于OperationContext的详细信息,可以参阅MSDN在线文档。 #endregion ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>(); callback.DisplayResult(x, y, result); //回调方法 } } }
之后是host文件的代码和配置文件信息
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Description; namespace Host { using (ServiceHost host = new ServiceHost(typeof(WcfServiceLibrary_ppz.Services))) { try { host.Open(); Console.WriteLine("service has opened, popozhu"); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } Console.Read(); } } } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <!--这里的service name要填写接口的实现方法--> <service name="WcfServiceLibrary_ppz.Services" behaviorConfiguration="mybehavior"> <endpoint address="net.tcp://localhost:9001/popozhu" binding="netTcpBinding" contract="WcfServiceLibrary_ppz.Contracts" /> <!--继续添加endpoint--> </service> </services> <behaviors> <serviceBehaviors> <behavior name="mybehavior"> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:9000/popozhu/metadata"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>
客户端的代码和配置文件信息(窗体控件布局看截图就可知道的)
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.ServiceModel; using System.Threading; namespace Client_form { public partial class HostFrom : Form, WcfServiceLibrary_ppz.ICallback//继承回调接口 { Thread hostThread; WcfServiceLibrary_ppz.Contracts m_proxy; private double x = 1; private double y = 2; public HostFrom() { InitializeComponent(); hostThread=new Thread(new ThreadStart(this.threadStart)); //在线程中托管服务 hostThread.Start(); } private void threadStart() { InstanceContext callbackContext = new InstanceContext(this); //参数为this,因为回调方法在form中 DuplexChannelFactory<WcfServiceLibrary_ppz.Contracts> channel = new DuplexChannelFactory<WcfServiceLibrary_ppz.Contracts>(callbackContext, "myCallBack"); //上面构造函数中的"myCallBack"在配置文件中定义的,跟配置文件一致 m_proxy = channel.CreateChannel(); } //开始服务请求, private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < 5; i++) { m_proxy.Add(x, y); //服务的调用,该调用中会回调客户端的一个回调函数,在textbox中显示计算结果 x += x; y += y; } } //客户端的回调函数 public void DisplayResult(double x, double y, double result) { //下面的代码属于跨线程修改UI线程,在下面方法中使用invoke和委托 this.Invoke(new playResultDelegate(m_displayResultForDelegate), new object[]{x,y,result}); Thread.Sleep(5000); //this.textBox1.AppendText(x.ToString() + " + " + y.ToString() + " = " + result.ToString() + Environment.NewLine); } private delegate void playResultDelegate(double x, double y, double result); private void m_displayResultForDelegate(double x, double y, double result) { this.textBox1.AppendText(x.ToString() + " + " + y.ToString() + " = " + result.ToString() + Environment.NewLine); this.textBox2.AppendText("callBack finished, hello." + Environment.NewLine); //提示回调方法执行完成 } private void button2_Click(object sender, EventArgs e) { this.textBox2.AppendText("interrupt here....." + Environment.NewLine); } } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <client> <!--name要在构造ChannelFactory中使用到,address和binding要和host中定义的一致--> <endpoint address="http://localhost:9000/popozhu/111" binding="wsHttpBinding" contract="WcfServiceLibrary_ppz.Contracts" name="myservice" /> <!--回调的endpoint--> <endpoint address="net.tcp://localhost:9001/popozhu" binding="netTcpBinding" contract="WcfServiceLibrary_ppz.Contracts" name="myCallBack"/> </client> </system.serviceModel> </configuration>
运行截图(先开host.exe,再调用Client端的窗体)运行过程中,UI线程基本不阻塞(阻塞时间点是:在文本框中显示计算结果和回调完成的提示信息而已,基本不算阻塞界面)
参考:我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
以及某位××微软××写的英文文章(确实很受用):Writing Smart Clients by Using Windows Communication Foundation 中的listing 6中的代码模型,摘录如下:
[ServiceContract] interface IFormManager { [OperationContract] void IncrementLabel(); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] partial class MyForm : Form,IFormManager { Label m_CounterLabel; ServiceHost m_Host; public MyForm() { InitializeComponent(); m_Host = new ServiceHost(this); m_Host.Open(); } void OnFormClosed(object sender,EventArgs args) { m_Host.Close(); } public void IncrementLabel() { Counter++; } public int Counter//Same as Listing 1 {...} }
源代码文件:WcfServiceLibrary_6+线程和invoke