WCF后传系列(9):深度通道编程模型Part 2—实例篇

引言

从本质上说,WCF是一个通信服务框架,它允许我们使用不同的传输协议,使用不同的消息编码形式,跟不同的WS-*系列规范交互,而所有这些细节都是由通道堆栈来处理的。在《WCF专题系列(8):深度通道编程模型Part 1—设计篇》中,对于WCF中的通道模型有了深入的认识,本文中,我将通过实例来说明在通道模型中,服务端是如何接收消息,客户端是如何发送消息的。

服务端通道

本文将不使用WCF的编程模型,而直接利用通道模型来进行通信,这样有助于我们更进一步加深对服务端处理消息的认识,在服务端侦听并接收消息的第一步需要创建绑定,我们既可以使用WCF中内置的绑定或者使用自定义的绑定,如下代码所示,创建一个CustomBinding:

// 创建自定义绑定
BindingElement[] bindingElements = new BindingElement[2];
bindingElements[0] = new TextMessageEncodingBindingElement();
bindingElements[1] = new HttpTransportBindingElement();

CustomBinding binding = new CustomBinding(bindingElements);

此处添加了HttpTransportBindingElement,所以生成的通道堆栈具有HTTP传输通道,另外采用了文本消息编码器。接下来调用刚才创建的CustomBinding的BuildChannelListener方法来构造通道侦听器,需要指定侦听基地址以及绑定参数,另外调用Open()方法打开通道监听器,相信大家一定还记得Open()方法是在接口ICommunicationObject中定义的,如下代码所示:

// 使用自定义绑定创建通道侦听器         
IChannelListener<IReplyChannel> listener =
      binding.BuildChannelListener<IReplyChannel>(
         new Uri("http://localhost:8887/StringService"),
         new BindingParameterCollection());

// 监听消息
listener.Open();
Console.WriteLine("Listening for incoming channel connections");

现在侦听传入的消息,由于我们使用请求了响应消息交换模式,此处侦听器返回一个实现了IReplyChannel的通道,为了在此通道上接收消息,我们首先对其调用Open()方法(该方法仍然是在ICommunicationObject中定义),以便将其置于一个准备进行通信的状态。 然后,我们调用ReceiveRequest()方法,它会处于阻止状态,直到消息达到,如下代码所示:

// 创建Reply通道
IReplyChannel channel = listener.AcceptChannel();
Console.WriteLine("Channel accepted. Listening for messages");
channel.Open();

RequestContext request = channel.ReceiveRequest();

当ReceiveRequest()方法返回一个RequestContext时,再使用其RequestMessage属性获取接收到的消息。输出消息的操作(Action)和内容。为了发送答复,在此例中创建一个新的答复消息,它会将我们在请求中接收到的字符串数据,添加一段字符后再传递回去。然后,调用Reply()方法以发送答复消息,如下代码所示:

// 读取请求的消息
Message message = request.RequestMessage;
Console.WriteLine("Message Received");
Console.WriteLine("Message Action: {0}", message.Headers.Action);
string body = message.GetBody<string>();
Console.WriteLine("Message Content: {0}", body);

// 发送响应消息
Message replymessage = Message.CreateMessage(
    binding.MessageVersion,
    "http://www.cnblogs.com/TerryLee/Encode",
     "Hello : " + body);

request.Reply(replymessage);

最后别忘了做资源释放工作,关闭通道侦听器、通道、请求消息、请求上下文等,如下代码所示:

// 释放对象
message.Close();
request.Close();
channel.Close();
listener.Close();

完整的代码如下所示:

/// <summary>
/// Author:TerryLee
/// Url:http://www.cnblogs.com/terrylee
/// </summary>
static void Main()
{
    // 创建自定义绑定
    BindingElement[] bindingElements = new BindingElement[2];
    bindingElements[0] = new TextMessageEncodingBindingElement();
    bindingElements[1] = new HttpTransportBindingElement();

    CustomBinding binding = new CustomBinding(bindingElements);

    // 使用自定义绑定创建通道侦听器         
    IChannelListener<IReplyChannel> listener =
          binding.BuildChannelListener<IReplyChannel>(
             new Uri("http://localhost:8887/StringService"),
             new BindingParameterCollection());

    // 监听消息
    listener.Open();
    Console.WriteLine("Listening for incoming channel connections");

    // 创建Reply通道
    IReplyChannel channel = listener.AcceptChannel();
    Console.WriteLine("Channel accepted. Listening for messages");
    channel.Open();

    RequestContext request = channel.ReceiveRequest();

    // 读取请求的消息
    Message message = request.RequestMessage;
    Console.WriteLine("Message Received");
    Console.WriteLine("Message Action: {0}", message.Headers.Action);
    string body = message.GetBody<string>();
    Console.WriteLine("Message Content: {0}", body);

    // 发送响应消息
    Message replymessage = Message.CreateMessage(
        binding.MessageVersion,
        "http://www.cnblogs.com/TerryLee/Encode",
         "Hello : " + body);

    request.Reply(replymessage);

    // 释放对象
    message.Close();
    request.Close();
    channel.Close();
    listener.Close();

    Console.WriteLine("Press Enter to exit");
    Console.ReadLine();
}

现在运行服务端如图1所示,由于没有消息到达,所以ReceiveRequest()方法会阻塞:

TerryLee_WCF_31

图 1

客户端通道

前面完成了服务端的工作,接下来我们看看如何在客户端直接使用通道模型进行通信。与服务端一致,请求消息的第一步是创建绑定,因为双方需要通过绑定就通信的细节达成一致。创建自定义绑定与服务端一致,如下代码所示:

// 创建绑定
BindingElement[] bindingElements = new BindingElement[2];
bindingElements[0] = new TextMessageEncodingBindingElement();
bindingElements[1] = new HttpTransportBindingElement();

CustomBinding binding = new CustomBinding(bindingElements);

接下来需要使用刚才创建的绑定来构造通道工厂,在上一篇中我们提到,消息的接收方使用通道侦听器,而消息的请求方使用通道工厂,这次使用BuildChannelFactory()方法构造通道工厂并打开,如下代码所示:

// 使用绑定创建通道工厂
IChannelFactory<IRequestChannel> factory =
binding.BuildChannelFactory<IRequestChannel>(
                 new BindingParameterCollection());
// 打开通道工厂
factory.Open();
Console.WriteLine("Channel factory opened");

现在使用通道工厂的CreateChannel()方法来创建IRequestChannel,得到通道后,调用它的Open()方法以使其处于通信就绪状态,如下代码所示:

// 创建Request通道
IRequestChannel channel = factory.CreateChannel(
   new EndpointAddress("http://localhost:8887/StringService"));
channel.Open();
Console.WriteLine("Request channel opened");

打开通道之后,就可以创建消息并使用通道的 Request()方法发送请求并等待响应,这里我们发送的消息内容是“TerryLee”,当此方法返回时,我们将能够得到回复消息,可以读取该消息以发现终结点回复的内容,如下代码所示:

// 创建请求消息
Message requestmessage = Message.CreateMessage(
    binding.MessageVersion,
    "http://www.cnblogs.com/TerryLee/Encode",
     "TerryLee");

// 发送请求消息并接收响应消息
Message replymessage = channel.Request(requestmessage);
Console.WriteLine("Reply message received");
Console.WriteLine("Reply action: {0}",
                      replymessage.Headers.Action);
string data = replymessage.GetBody<string>();
Console.WriteLine("Reply content: {0}", data);

最后仍然是资源释放工作,关闭通道工厂、通道以及请求消息,如下代码所示:

replymessage.Close();
channel.Close();
factory.Close();

完整的客户端代码为:

/// <summary>
/// Author:TerryLee
/// Url:http://www.cnblogs.com/terrylee
/// </summary>
public static void Main()
{
    // 创建绑定
    BindingElement[] bindingElements = new BindingElement[2];
    bindingElements[0] = new TextMessageEncodingBindingElement();
    bindingElements[1] = new HttpTransportBindingElement();

    CustomBinding binding = new CustomBinding(bindingElements);

    // 使用绑定创建通道工厂
    IChannelFactory<IRequestChannel> factory =
    binding.BuildChannelFactory<IRequestChannel>(
                     new BindingParameterCollection());
    // 打开通道工厂
    factory.Open();
    Console.WriteLine("Channel factory opened");

    // 创建Request通道
    IRequestChannel channel = factory.CreateChannel(
       new EndpointAddress("http://localhost:8887/StringService"));
    channel.Open();
    Console.WriteLine("Request channel opened");

    // 创建请求消息
    Message requestmessage = Message.CreateMessage(
        binding.MessageVersion,
        "http://www.cnblogs.com/TerryLee/Encode",
         "TerryLee");

    // 发送请求消息并接收响应消息
    Message replymessage = channel.Request(requestmessage);
    Console.WriteLine("Reply message received");
    Console.WriteLine("Reply action: {0}",
                          replymessage.Headers.Action);
    string data = replymessage.GetBody<string>();
    Console.WriteLine("Reply content: {0}", data);

    replymessage.Close();
    channel.Close();
    factory.Close();

    Console.WriteLine("Press Enter to exit");
    Console.ReadLine();
}

最后运行时服务端如图2所示:

TerryLee_WCF_33 

图 2

客户端如图3所示:

TerryLee_WCF_32

图 3

温故知新

现在我们再回顾一下上一篇中所讲的知识,通道对象模型是实现通道、通道侦听器和通道工厂所必需的一组核心接口。还提供一些基类以辅助自定义实现。可以看到通道模型中最重要的有三组接口:通道、通道侦听器和通道工厂。每个通道均实现一个或多个接口,称为通道形状接口或通道形状;通道侦听器负责侦听传入消息,即在消息的接收端,然后通过由通道侦听器创建的通道将这些消息传送到上面的层;通道工厂负责创建通道用于发送消息,即在消息的发送方,并在通道工厂关闭时,关闭通道工厂创建的所有通道。如图4所示:

TerryLee_WCF_24

图 4

对照本文的示例代码,相信大家对于图4能够有更深的认识。

总结

本文我们通过一个简单的示例介绍了在通道模型中服务端是如何接收消息以及客户端是如何发送消息的,希望对大家有所帮助。

posted @ 2008-11-14 00:22 TerryLee 阅读(...) 评论(...) 编辑 收藏