请求-响应通信是最常规的用于客户端与服务之间信息交换的模式。通信由客户端发起,之后服务将响应消息发送给客户端。这种模式有一个弊端就是,服务总是被动的提供响应。假如服务需要给客户端发出一个通知或者警报,请求-响应这种单工模式就无法满足需求了。
这次要说的就是双工通信模式,该模式下服务可以向任一方发送未经请求的消息。当然双工通信也可以是单向的。
下面我们来看看其服务端是如何实现的。
//这里定义服务接口和回调函数接口
[ServiceContract(CallbackContract =typeof(IClientCallback))]
public interface IService1
{
[OperationContract(IsOneWay =true)]
void RegisterContract(string price);
}
public interface IClientCallback
{
[OperationContract(IsOneWay =true)]
void PriceUpdate(string ticker, double price);
}
//先实现服务接口
public class Service1 : IService1
{
public static Hashtable workers = new Hashtable();
public void RegisterContract(string ticker)
{
Worker w = null;
if (!workers.ContainsKey(ticker))
{
w = new Worker();
w.ticker = ticker;
w.workerProcess = new Update();
w.workerProcess.ticker = ticker;
workers[ticker] = w;
//通过线程定时调用回调函数
Thread t = new Thread(new ThreadStart(w.workerProcess.SendUpdateToClient));
t.IsBackground = true;
t.Start();
}
w = (Worker)workers[ticker];
IClientCallback c = OperationContext.Current.GetCallbackChannel<IClientCallback>();
lock (w.workerProcess.Callbacks)
{
//给回调函数列表增加任务
w.workerProcess.Callbacks.Add(c);
}
}
}
public class Worker
{
public string ticker;
public Update workerProcess;
}
public class Update
{
public string ticker;
public List<IClientCallback> Callbacks = new List<IClientCallback>();
public void SendUpdateToClient()
{
Random p = new Random();
while (true)
{
Thread.Sleep(5000);
lock (Callbacks)
{
foreach (IClientCallback c in Callbacks)
{
try
{
//在这里对列表中存在的客户端发起回调
c.PriceUpdate(ticker, 100.00 + p.NextDouble());
LogOp.WriteLogFile(ticker+":" + 100.00 + p.NextDouble());
}
catch (Exception ex)
{
LogOp.WriteLogFile("SendUpdateToClient出错:" + ex.Message);
}
}
}
}
}
}
当然在配置中也需要应用wsDualHttpBinding的模式,下面是完整的配置。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.6.1" />
<httpRuntime targetFramework="4.6.1"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="" name="双工操作.Service1">
<endpoint address="" binding="wsDualHttpBinding" contract="双工操作.IService1" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<protocolMapping>
<add binding="wsDualHttpBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。
在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
我这里是将服务托管在iis中。
下面我们来实现客户端:
static void Main(string[] args)
{
InstanceContext site = new InstanceContext(new CallbackHandler());
WCF.Service1Client service1Client = new WCF.Service1Client(site);
service1Client.RegisterContract("MSFT");
Console.WriteLine("启动成功");
Console.ReadKey();
}
public class CallbackHandler : WCF.IService1Callback
{
public void PriceUpdate(string ticker, double price)
{
Console.WriteLine("收到更新:"+ticker+price);
}
}
在客户端中需要继承IService1Callback接口,该接口可以供WCF服务端调用。以下是调用结果。