WCF服务问题之-回调
在使用服务时虽然一般是客户端向服务端请求服务,但有些时候也需要服务端向客户端进行通知(Notify),在CS的程序中尤为常见,在出现WCF之前,Remoting中使用回调是大费周章的一件事情,需要建立单独的侦听类,并且要处于独立的程序集中才行,在WCF中大大简化了回调过程,但是也有一些需要特别注意的地方,如果不注意回调也不是那么容易的,先看一个简单的回调代码:
一:配置属性声明:
服务接口:
[ServiceContract(CallbackContract = typeof(IChartCallBack))] public interface IChart
{
[OperationContract]
void Join(string name);
[OperationContract]
void Leave(string name);
[OperationContract]
void Speek(string name, string desname, string message );
}
服务端实现:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,
InstanceContextMode = InstanceContextMode.PerCall)]
public abstract class Chart: IChart
{
public void Join(string name)
{
//……code
IChartCallBack callback = OperationContext.Current.GetCallbackChannel<IChartCallBack>();
if (callback != null)
{
callback.NotifyJoin(name); //通知客户端
}
}
//剩余代码
}
回调接口:
[ServiceContract]
public interface IChartCallBack
{
[OperationContract(IsOneWay = true)]
void NotifyJoin(string name);
[OperationContract(IsOneWay = true)]
void NotifyLeave(string name);
[OperationContract(IsOneWay = true)]
void SpeekTo(string name, string message);
}
客户端实现:
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant,UseSynchronizationContext=false)] public class Client: IChartCallBack //实现了聊天回调窗口 {
public void NotifyJoin(string name)
{
Console.WriteLine(name + ” 已加入”);
}
}
上面的代码都很简单,用起来也极为简便,但是要注意的地方还是很多,如果不注意这些问题就会出差错,如上面的代码所示回调基本上是在声明式代码中完成的,属性又有不同的选项,不同的选项用处也不一样,要使的服务端能够回调客户端代码,有以下几种方式:
1. 配置服务多线程访问
即设置ServiceBehavior属性的ConcurrencyMode值为Mutiple,这样服务类就允许多线程访问了,但是多线程访问会代码同步问题,所以需要处理好同步问题;
2. 回调重入
配置ServiceBehavior属性的ConcurrencyMode值为Reentrant,服务成为单线程访问,在回调发生时可冲入;
3. 单向回调
在回调接口中的操作契约中设置IsOneWay为true,设置为单向调用,也可进行回调
如果在使用回调时不遵循这3种方式,回调时会出现异常,无法完成回调工作。
二:客户端处理回调的问题:
由于回调是服务端异步调用,因此会出现调用超时,跨线程访问的问题,如果不认清这些问题的本质,就开始编写代码,往往回调会不成功的,如果使用控制台工程,不使用UI,则跨线程访问UI的问题不会出现,但是超时依然会出现。
1. 超时问题
如将
这行代码改成
MessageBox.Show(name);
如果长时间不响应MessageBox,则会抛出异常,所以在回调处理中如果不标记IsOneWay为true,则尽量不要在处理中采用耗时代码,以免超时。
2. WinFrom中回调代码访问UI的问题
由于回调线程和UI的线程并非同一线程,因此在访问UI时就会出现问题,要解决这一问题,和多线程访问UI是一致的,将访问代码封送到UI线程中来处理,如使用Control.Invoke()函数,或者使用SynchronizationContext同步上下文来完成UI访问任务。
3. WPF中访问UIElement的问题
WPF已经不采用Windows消息循环系统了,而采用自行设计的消息系统,因此WPF中处理这种问题和WinForm并不相同,UIElement不在提供Invoke函数来封送代码了,而且也没有SynchronizationContext上下文,但是WPF提供了Dispatcher对象,通过Dispatcher.CurrentDispatcher来获取当前线程(UI线程)的消息分发器对象,Dispatcher有一个Invoke函数,该函数可以实现将另一线程中的代码封送到当前线程中来执行,这样就可以解决跨线程访问UI的问题。
三:安全问题
上面的代码在单机中运行是没有任何问题的,但是将客户端服务端分到两台PC机中,问题就会出来了,无论调用服务也好回调也好,都会有安全问题,因此需要对安全问题进行配置,安全问题是一个大话题,这里就不细说,为了代码能在多台机器中运行,最简单的就是不使用安全验证,配置文件如下所示:
在配置文件中增加bindings节:
<bindings>
<netTcpBinding>
<binding name="TcpSecurity">
<security mode="None"> <!--不采用安全措施-->
<transport clientCredentialType="None" protectionLevel="None"/>
</security>
</binding>
</netTcpBinding>
</bindings>
这里使用的是netTcpBinding, 如果使用别的绑定,则增加特定绑定的配置代码,在终结点中增加bindingConfiguration属性,并将值指向binding的名称,如
<endpoint address="net.tcp://localhost:8000/chart"
binding="netTcpBinding"
contract="fzgk.IChart"
bindingConfiguration="TcpSecurity"
name="TcpBinding"/>
客户端也一样增加这些配置项,再次运行程序就不会出现安全问题了。