在前面一节中我们有演示了WCF操作模式-单向操作(单工通信)。这节我们接着演示双向操作(双工通信)模式。
在单向操作模式中,客户端向服务器发送请求,然后服务器回应。但服务器却不能主动向客户端发送信息。但在双向操作模式中,不但客户端可以向服务器发送请求,服务器也可以主动向客户端广播消息(也就是回调客户端中的方法)。在WCF中,不是所有的绑定都可以实现双向操作模式的,比如http协议,它本身就是基于请求-回复的传输模式,所以本质上是实现不了双向操作的。但WCF提供了WSDualHttpBinding协议让我们在http上实现了双向操作。其实WSDualHttpBinding并没有违反http单向传输的本质,它实际上是创建两个了通道,一个用于客户端向服务器请求,一个用于服务器向客户端广播,间接实现了双向操作。但《WCF服务编程》书上有说,WSDualHttpBinding无法穿越客户端与服务器的重重障碍,所以不赞成使用WSDualHttpBinding来实现双向操作。
那么除了WSDualHttpBinding协议外,还有那些协议支持双向操作呢?就是NetTcpBinding与NetNamedPipeBinding了。这两个协议都是从本质上支持双向操作的。但我们这节的示例使用的是WSDualHttpBinding绑定。
下面开始示例:
首先还是先定义服务契约:
[ServiceContract(CallbackContract = typeof(ICallBack))]
public
interface IMessageService
{
[OperationContract]
void RegisterMes();
}
和单向操作相比,我们会发现服务契约上多了一行代码:
[ServiceContract(CallbackContract = typeof(ICallBack))]
这是因为我们在定义契约的时候,就要事先约定好向客户端回调的方法。比如上面的代码就说明了该契约只能回调继续自ICallBack 接口的客户端方法。
ICallBack 接口当然也是自己定义的,本示例ICallBack 接口如下:
public interface ICallBack
{
[OperationContract(IsOneWay = true)]
void SayHello(string mes);
}
注意,ICallBack 接口不需要声明ServiceContract 特性,但SayHello()方法却必须声明OperationContract 特性,而且必须指定IsOneWay = true ,如果不指定它,我们在运行时服务端会引发InvalidOperationException异常。
下面实现wcf服务类:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class MessageService : IMessageService, IDisposable
{
//代码段一
public static List<ICallBack > CallBackList
{
get;
set;
}
//代码段二
public MessageService()
{
CallBackList = new List<ICallBack >();
}
//代码段三
public void RegisterMes()
{
ICallBack callback = OperationContext.Current.GetCallbackChannel<ICallBack >();
CallBackList.Add(callback);
string
Sessionid = OperationContext.Current.SessionId;
Console.WriteLine("{0} is register", Sessionid);
OperationContext.Current.Channel.Closing +=
delegate
{
lock (CallBackList)
{
CallBackList.Remove(callback);
Console.WriteLine("{0} is remove", Sessionid);
}
};
}
//代码段四
public void Dispose()
{
CallBackList.Clear();
}
}
在上面代码中首先我们定义了一个静态的List<ICallBack >属性CallBackList(代码段一)。我们用这个属性来装载所有注册到服务上面的客户端实例,以便后面我们向所有的客户端广播消息。
然后在服务的构造函数中实例化CallBackList静态属性(代码段二)。
重点是服务器端获取到所有的客户端实例,即(代码段三)。在服务器端我们要使用OperationContext.Current.GetCallbackChannel< >()方法来获取客户端实例,如代码段三中的下面代码:
ICallBack callback = OperationContext.Current.GetCallbackChannel<ICallBack >();
将callback装载到CallBackList 属性中后,注册实际就已经完成了,代码段三中后面的代码是在注册时输出一些信息方便我们查看效果,没有也没有关系。
OperationContext.Current.SessionId获取到当前注册的客户端会话ID,这个ID是唯一的。注册时服务器端输出”会话 is register”,然后客户端在关闭的时候服务器端输出”会话is remove”。
代码段四是在服务关闭时清空静态属性CallBackList中的所有值。
完成了服务类,就开始我们服务的托管了。我们在控制台程序中寄宿服务,因为控制台程序实现起来简单,代码如下:
class Program
{
static void Main(string[] args)
{
using ()ServiceHost host = new ServiceHost(typeof(MessageService))
{
host.AddServiceEndpoint(typeof(IMessageService), new WSDualHttpBinding(), "http://localhost:8011");
host.Opening += delegate { Console.WriteLine("服务开启:{0}", DateTime.Now.ToString()); };
host.Open();
start:
string command = Console.ReadLine();
switch (command)
{
case "send":
lock (MessageService.CallBackList)
{
foreach (ICallBack callback in MessageService.CallBackList)
{
callback.SayHello(string.Format("hello,我是服务器{0}", DateTime.Now.ToString()));
}
}
goto start;
case "close":
host.Close();
break;
default:
Console.WriteLine("no command!");
goto start;
}
}
}
}
程序一开始,我们声明了服务ServiceHost host = new ServiceHost(typeof(MessageService),
然后给服务添加终结点信息,定义Opening事件,直至服务打开。
在这段代码中我使用了goto语句,大家不要计较在程序使用goto是否妥当,因为这仅仅是一个演示。Switch语句处理了我们在服务端控制台输入的命令,如果我们输入了send命令,那么服务就会轮环调用所有已经在服务端注册了的客户端的SayHello 方法。输入了close命令,就会关闭服务,其它的命令不执行。
好了,所有服务端的代码全部完成,下面开始客户端的代码:
我们是使用编程的方法来调用服务,所以请在客户端项目中添加对服务端项目的引用。
在客户端我们首先需要实现服务端定义的回调方法ICallBack ,代码如下:
public class MyCallBack : Host.ICallBack
{
public void SayHello(string mes)
{
Console.WriteLine(mes);
Console.WriteLine("2秒种后显示客户端信息!");
Thread.Sleep(2000);
Console.WriteLine("hello,我是客户端{0}", DateTime.Now.ToString());
}
}
然后是实现对服务的调用:
class Program
{
static void Main(string[] args)
{
Host.ICallBack callback = new MyCallBack();
InstanceContext context = new InstanceContext(callback);
WSDualHttpBinding binding = new WSDualHttpBinding();
binding.ClientBaseAddress = new Uri("http://localhost:8010");
using (DuplexChannelFactory<Host.IMessageService > proxy = new DuplexChannelFactory<Host.IMessageService >(context, binding))
{
Host.IMessageService client = proxy.CreateChannel(new EndpointAddress("http://localhost:8011"));
client.RegisterMes();
Console.ReadLine();
}
}
}
注意代码binding.ClientBaseAddress = new Uri("http://localhost:8010");如果我们用的是win7或者以上的系统,这段代码并不需要。但如果是xp系统,就需要了,因为xp系统下的iis 5.x默认的回调监听端口是80端口,如果如果不重新ClientBaseAddress,就会报出80端口正在使用的错误。而且xp系统下即使我们重写了ClientBaseAddress 属性,也只能同时运行一个客户端窗口,但如果是win7系统,则可以同时运行多个客户端窗口。
下面我们看一下运行的效果。
首先是运行服务端窗口,效果如下:
图1
然后运行客户端窗口,服务端窗口会打印出客户端的注册时间,如下:
图2
然后我们在服务端窗口输入send命令,客户端会打印信息如下:
图3
至此,这节WCF操作模式-单向操作(单工通信)演示就完成了,全部代码下载链接如下:
WCF双工通信演示实例下载T7h
ghV沚鶴剉_8^_N
NO O?R7b飠0購蛓蚫\O剉}YY1\/f7b飠裇愾婤lTOl?N鐍韣gbL?Tb梽v鉔x
NO\P?NegI{卂0<