没有见过地狱的眼睛,永远也看不到光明
IChatCallback.cs的代码:
//由于回调契约本质也是一个服务契约,所以定义方式和一般意义上的服务契约基本一样。有一点不同的是,由于定义IChatService的时候已经通过[ServiceContract(CallbackContract=typeof(IChatCallback))]指明ICallback是一个服务契约了,所以ICallback不再需要添加ServiceContractAttribute特性。IChatCallback定义了一个服务操作MessageBroadcast用于消息发送,由于服务端不需要回调的返回值,索性将回调操作也设为单向方法。
public interface IChatCallback
{
[OperationContract(IsOneWay=true)]
void MessageBroadcast(string msg);
}
IChatService.cs代码:
//定义服务契约和回调契约
//首先进行服务契约的定义,我们照例通过接口(IChatService)的方式定义服务契约,作用于指定Send操作,我们通过OperationContractAttribute特性的IsOneway属性将操作定义成单向的操作,这意味着客户端仅仅是向服务端发送一个请求,并不会通过回复消息得到任何结果。
[ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(IChatCallback))]
public interface IChatService
{
[OperationContract(IsOneWay=true,IsInitiating=true)]
void Register();
[OperationContract(IsOneWay=true,IsInitiating=false)]
void Send(string message);
}
ChatService.cs代码:
//实现服务
//在实现了上面定义的服务契约IChatService的服务ChatService中,实现了Send操作,完成工作。
//结果显示是通过回调的方式实现的,所以需要借助于客户端提供的回调对象(该对象在客户端调用ChatService的时候指定,在介绍客户端代码的实现的时候会讲到)。
//在WCF中,回调对象通过当前OperationContext的GetCallback<T>方法获得(T代表回调契约的类型)。
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single //, ConcurrencyMode=ConcurrencyMode.Reentrant
)]
public class ChatService : IChatService
{
List<string> messages = new List<string>();
List<IChatCallback> clients = new List<IChatCallback>();
public void Send(string message)
{
messages.Add(message);
foreach(var client in clients)
{
if (client != Callback)
{
client.MessageBroadcast(message);
}
//如果服务正在调用调用者,那么服务必须具有ConcurrencyMode.Reentrant 或者 Multiple,否则将发生死锁。
//else
//{
// client.MessageBroadcast(message);
//}
}
}
IChatCallback Callback
{
get
{
//注: OperationContext在WCF中是一个非常重要、也是一个十分有用的对象,它代表服务操作执行的上下文。
//我们可以通过静态属性Current(OperationContext.Current)得到当前的OperationContext。
//借助OperationContext,我们可以在服务端或者客户端获取或设置一些上下文,比如在客户端可以通过它为出栈消息(outgoing message)添加SOAP报头,以及HTTP报头(比如Cookie)等。
//在服务端,则可以通过OperationContex获取在客户端设置的SOAP报头和HTTP报头。关于OperationContext的详细信息,可以参阅MSDN在线文档。
return OperationContext.Current.GetCallbackChannel<IChatCallback>();
}
}
public void Register()
{
clients.Add(Callback);
}
}
class Program
{
static void Main(string[] args)
{
//启动新的聊天服务器。
using(var host = new ServiceHost(typeof(ChatService)))
{
host.Open();
Console.WriteLine("聊天服务器运行 " + string.Join(", ", host.Description.Endpoints.Select(s => s.Address)));
Console.WriteLine("按任何一个键退出。");
Console.ReadKey();
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfDuplexChat.ChatService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:12000/"/>
</baseAddresses>
</host>
<endpoint address="ChatServer" binding="wsDualHttpBinding" contract="Contract.IChatService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
ChatCallbackHandler.cs代码:
//服务回调的实现
public class ChatCallbackHandler : Contract.IChatCallback, ChatService.IChatServiceCallback
{
public void MessageBroadcast(string msg)
{
Console.WriteLine(msg);
}
}
app.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<!--Not used configured in code-->
<!--<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IChatService" ></binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:12000/ChatServer" binding="wsDualHttpBinding"
bindingConfiguration="WSDualHttpBinding_IChatService" contract="ChatService.IChatService"
name="WSDualHttpBinding_IChatService">
<identity>
<userPrincipalName value="simtex\Simon Pedersen" />
</identity>
</endpoint>
</client>-->
<!--服务寄宿-->
<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IChatService" />
</wsDualHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:12000/ChatServer" binding="wsDualHttpBinding"
bindingConfiguration="WSDualHttpBinding_IChatService" contract="ChatService.IChatService"
name="WSDualHttpBinding_IChatService">
<identity>
<userPrincipalName value="simtex\Simon Pedersen" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Program.cs代码:
internal class Program
{
//基于双工通信的WCF应用
private static void Main(string[] args)
{
//让用户输入端口,使多个聊天客户端可以在同一台机器上运行。
int port = 12001;
if (args.Length < 1)
{
Console.WriteLine("输入端口号:");
port = int.Parse(Console.ReadLine());
}
else
{
port = int.Parse(args[0]);
}
var instanceContext = new InstanceContext(new ChatCallbackHandler());
//注:在WCF预定义绑定类型中,WSDualHttpBinding和NetTcpBinding均提供了对双工通信的支持,但是两者在对双工通信的实现机制上却有本质的区别。WSDualHttpBinding是基于HTTP传输协议的;而HTTP协议本身是基于请求-回复的传输协议,基于HTTP的通道本质上都是单向的。WSDualHttpBinding实际上创建了两个通道,一个用于客户端向服务端的通信,而另一个则用于服务端到客户端的通信,从而间接地提供了双工通信的实现。而NetTcpBinding完全基于支持双工通信的TCP协议。
var binding = new WSDualHttpBinding();
binding.ClientBaseAddress = new Uri(string.Format("http://localhost:{0}/ChatClient", port));
var endpoint = new EndpointAddress("http://localhost:12000/ChatServer");
//using(var client = new ChatService.ChatServiceClient(instanceContext,binding, endpoint)) //Visual Studio生成的客户端代理。
//双工(Duplex)模式的消息交换方式体现在消息交换过程中,参与的双方均可以向对方发送消息。基于双工MEP消息交换可以看成是多个基本模式下(比如请求-回复模式和单项模式)消息交换的组合。双工MEP又具有一些变体,比如典型的订阅-发布模式就可以看成是双工模式的一种表现形式。双工消息交换模式使服务端回调(Callback)客户端操作成为可能。
using (var factory = new DuplexChannelFactory<Contract.IChatService>(instanceContext, binding, endpoint))
//手工创造Channelfactory
{
var client = factory.CreateChannel();
try
{
//client.Open();
client.Register();
Console.WriteLine("聊天客户端准备运行: " + binding.ClientBaseAddress);
do
{
var message = Console.ReadLine();
client.Send(message);
} while (true);
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
Console.ReadKey();
}
}