在上篇文章中介绍了一下WCF中的客户端到服务器端的单向通知,在实际应用中,还经常使用服务器端到客户端的单向通知。例如,在聊天室里,我们需要把某人的发言广播给每一个人。对于这种单向通知,我们一般称为回调。本文就以一个简单的聊天室为例,介绍一下如何实现回调。
interface IMessageCallback
{
[OperationContract(IsOneWay = true)]
void OnMessageAdded(string message, DateTime timestamp);
}
这个接口并不是服务,因此没有ServiceContract标志,但其回调函数形式需要被公布,因此接口函数有OperationContract标志,另外,这个也是一个单向通知,因此有IsOneWay = true设置(注:这里的IsOneWay不是必须的,可以获取回调函数的返回值的)。
[ServiceContract(CallbackContract = typeof(IMessageCallback))]
public
interface
IService1
{
[OperationContract]
void AddMessage(string message);
[OperationContract]
void Subscribe();
[OperationContract]
void Unsubscribe();
}
除了客户端上报消息的接口外,这里也加了两个接口函数Subscribe和Unsubscribe,用来注册回调和删除回调,实现了一个典型的观察者模式。(PS:这两个接口并不是必须的,主要是为了方便演示回调的实现过程)
接口声明完成后,给了一个简单的实现(这个只是示例代码,线程安全,最佳实践神马的都没有考虑,不要用于实际项目中)。
public
class
Service1 : IService1
{
static List<IMessageCallback> subscribers = new
List<IMessageCallback>();
public
void AddMessage(string message)
{
subscribers.ForEach(callback =>
{
if (((ICommunicationObject)callback).State == CommunicationState.Opened)
callback.OnMessageAdded(message, DateTime.Now);
else
subscribers.Remove(callback);
});
}
public
void Subscribe()
{
var callback = OperationContext.Current.GetCallbackChannel<IMessageCallback>();
subscribers.Add(callback);
}
public
void Unsubscribe()
{
var callback = OperationContext.Current.GetCallbackChannel<IMessageCallback>();
subscribers.Remove(callback);
}
}
从上述代码中可以发现,回调的基本使用方式如下:
-
从OperationContext.GetCallbackChannel中获取回调通道对象,该对象实现了回调接口和ICommunicationObject接口。
-
根据IcommunicationObject.State属性查看其是否可用,也可以注册IcommunicationObject. Closed事件主动响应客户端退出。
- 调用回调对象的回调函数接口即可实现回调通知
编译并运行上述服务后,会发现一个服务无法启动的错误。
原因说得很清楚,要使用CallbackContract,则需要底层协议支持双工(因为需要从服务器端主动通知客户端)。但目前使用的BasicHttpBinding由于是基于Http协议,不支持双工(另外一个基于Http协议的WSHttpBinding也不支持双工),要改成支持双工的协议。目前WCF中支持双工的协议有如下几种:
-
基于TCP协议的NetTcpBinding
-
基于管道的NetNamedPipeBinding
-
基于Http协议的wsDualHttpBinding,虽然Http协议本身不支持双工,但是wsDualHttpBinding通过创建两个不同方向的Http连接来实现了双向传输。
为了简单,我这里就改成了wsDualHttpBinding。
由于WCF测试客户端不支持回调的测试,因此这里我们就需要手动实现客户端代码:
static
void Main(string[] args)
{
var context = new System.ServiceModel.InstanceContext(new
Callback());
var client = new WcfClient.Service.Service1Client(context);
client.Subscribe();
while (true)
{
var input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
break;
client.AddMessage(input);
}
client.Unsubscribe();
client.Close();
}
class
Callback:IService1Callback
{
public
void OnMessageAdded(string message, DateTime timestamp)
{
Console.WriteLine(">>> Receive Message {0} {1}", message, timestamp);
}
}
编写这个代码时就会发现:现在Client的构造函数需要传入一个参数了,在这个参数中就可以指定实现了回调接口的对象,从而响应回调通知。运行客户端多个实例,发送消息,每个消息都会通知到所有客户端,与我们预期结果一致。