勇哥注:
wcf的双工通讯,有双工通讯和发布订阅两种方式。
(一)勇哥先来演示“双工通讯”
程序还是使用简单的add计算的例子。
下面是程序的解决方案,为了简单就只有两个项目。
我们把契约、服务、hosting合在一起,变成下面的WcfService项目。
ContractAndService.cs
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; namespace WcfService { [ServiceContract(Namespace = "http://47.98.154.65/", CallbackContract =typeof(ICallback))] public interface ICal { [OperationContract(IsOneWay =true)] void Add(int d1, int d2); } public class Cal : ICal { public void Add(int d1, int d2) { ICallback back=OperationContext.Current.GetCallbackChannel<ICallback>(); back.DisplayCalRes(d1, d2, d1 + d2); } } public interface ICallback { [OperationContract(IsOneWay = true)] void DisplayCalRes(int d1, int d2,int res); } }
program.cs
using System; using System.Collections.Generic; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; namespace WcfService { internal class Program { static void Main(string[] args) { using (ServiceHost host = new ServiceHost(typeof(WcfService.Cal))) { host.Opened += delegate { Console.WriteLine("Cal服务已经启动"); }; host.Open(); Console.ReadKey(); } } } }
服务端app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="NewBehavior0"> <serviceDebug includeExceptionDetailInFaults="true" /> <serviceMetadata httpGetEnabled="true" httpGetUrl="http://127.0.0.1:9997/Cal" /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration="NewBehavior0" name="WcfService.Cal"> <endpoint address="net.tcp://127.0.0.1:9998/Cal" binding="netTcpBinding" bindingConfiguration="" contract="WcfService.ICal" /> </service> </services> </system.serviceModel> </configuration>
客户端app.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <system.serviceModel> <bindings> <netTcpBinding> <binding name="NetTcpBinding_ICal"> <security> <transport sslProtocols="None" /> </security> </binding> </netTcpBinding> </bindings> <client> <endpoint address="net.tcp://127.0.0.1:9998/Cal" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_ICal" contract="calfun.ICal" name="NetTcpBinding_ICal"> <identity> <userPrincipalName value="CYE8GDOEVM74V9Y\Administrator" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.ServiceModel; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace WinFormClient { public partial class Form1 : Form { InstanceContext instance = new InstanceContext(new CallbackFun()); calfun.CalClient proxy; public Form1() { InitializeComponent(); proxy =new calfun.CalClient(instance, "NetTcpBinding_ICal"); } private void button1_Click(object sender, EventArgs e) { proxy.Add(1, 1); } } public class CallbackFun : calfun.ICalCallback { public void DisplayCalRes(int d1, int d2, int res) { MessageBox.Show($"{d1}+{d2}={res}"); } } }
运行效果:
那个弹窗 1+1=2,实际上是由服务端回调的,回调的是客户端定义的这个CallbalFun类的DisplayCalRes函数。
几个重点:
(1) 注意客户端回调这个类的基类,应该使用“服务引用”的ICalCallback
public class CallbackFun : calfun.ICalCallback
而不应该使用下面的:
public class CallbackFun : WcfService.ICallback
这个搞错了,下场就是下面的报错:
InstanceContext 包含未实现 CallbackContractType“WinFormClient.calfun.ICalCallback”的 UserObject。”
(2)ICal的Add函数、ICallback的DisplayCalRes函数都设置了操作契约类型为IsOneWay = true。这个不是多余的,是有意义的。
[OperationContract(IsOneWay =true)]
void Add(int d1, int d2);
[OperationContract(IsOneWay = true)]
void DisplayCalRes(int d1, int d2,int res);
对于IsOneWay,勇哥解释一下:
我们知道WCF可以采用三种不同的Message Exchange Pattern(消息交互模式,MEP)——One-way,Request/Response,Duplex。
其实从本质上讲,One-way,Request/Response是两种基本的MEP, Duplex可以看成是这两种MEP的组合——两个One-way,两个Request/Response或者是一个One-way和一个Request/Response。
在定义Service Contract的时候,如果我们没有为某个Operation显式指定为One-way (IsOneWay = true),
那么默认采用Request/Response方式。
例如下面的函数DisplayResult,它指定了IsOneWay=true,所以它的消息交换模式就是单向。
如果不设置IsOneWay属性,则就是默认的消息交换方式:请求/响应。
public interface ICallback { [OperationContract(IsOneWay =true)] void DisplayResult(double x, double y, double result); }
我们把上面ICal的Add函数、ICallback的DisplayCalRes函数的IsOneWay = true都去掉。
这样就变成默认的Request/Response方式(请求/响应) 方式。
然后做两件事:
1。重新编译服务端,运行服务端
2。更新客户端的服务引用,启动客户端。
点Add,然后你就看到下面的报错:
这个错误的原因是:
由于Callback Operation是采用Request/Response方式调用的,所以它必须要收到来自Client端Reply来确定操作正常结束。这实际上形成了一个Deadlock(死锁)。
在蒋金楠的文章:https://www.cnblogs.com/artech/archive/2007/03/29/692032.html
里,讲到了这个问题产生的原因。
但是他当年的演示代码是跑出了弹窗结果后,再超时1分钟后才抛出一种超时的异常。
这种超时异常实际上没有把核心的原因显示清楚的。
蒋金楠他当年用的系统、C#版本、wcf版本、异常配置等许多因素跟勇哥现在的不一样。
现在是2024年,勇哥用的是win10, vs2022环境下,程序直接异常中止了,而且这个异常精准的显示清楚了核心原因是什么。并且还不允许你继续跑下去。
(这说明开发环境的异常检测能力变强了)
演示代码下载:
链接:https://pan.baidu.com/s/1ElJqlCCY_TaGAYwIQQeFEw
提取码:93c0
--来自百度网盘超级会员V6的分享

