勇哥注:
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的分享


少有人走的路

















