上一篇文章中我们学习了 HTTP 的双工模式,我们今天就学习一下 TCP 的双工模式。
在一个基于面向服务的分布式环境中,借助一个标准的、平台无关的通信协议,使各个服务通过 SOAP Message 实现相互之间的交互。这个交互的过程实际上就是信息交换的过程。WCF 支持不同形式的信息交换,我们把这称之为信息交换模式(Message Exchange Pattern(简称 MEP),下同), 常见的 MEP 包括: 请求 / 答复,单向模式和双工模式。通过采用双工的 MEP,我们可以实现在服务端调用客户端的操作。虽然 WCF 为我们实现底层的通信细节,使得我们把精力转移到业务逻辑的实现,进行与通信协议无关的编程,但是对通信协议的理解有利于我们根据所处的具体环境选择一个合适的通信协议。说到通信协议, WCF 经常使用的是以下 4 个:Http,TCP,Named Pipe,MSMQ。
我们用上一文章()中的示例,进行一下修改,变成一个 TCP 双向通信的 WCF 服务应用程序。下面直接上代码。
1.Contract
- using Contracts;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
- namespace Contracts {
- // 注意: 使用"重构"菜单上的"重命名"命令,可以同时更改代码和配置文件中的接口名"IBookService"。
- [ServiceContract(CallbackContract = typeof(ICallback))] public interface IBookService {
- /// <summary>
- /// 请求与答复模式,默认模式
- /// </summary>
- /// <param name="Id">书籍ID</param>
- /// <returns></returns>
- [OperationContract] string GetBook(string Id);
- /// <summary>
- /// 单工模式,显示名称
- /// </summary>
- /// <param name="name">书籍名称</param>
- [OperationContract(IsOneWay = true)] void ShowName(string name);
- /// <summary>
- /// 双工模式,显示名称
- /// </summary>
- /// <param name="name">书籍名称</param>
- [OperationContract(IsOneWay = true)] void DisplayName(string name);
- }
- }
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
- namespace Contracts {
- public interface ICallback { [OperationContract(IsOneWay = true)] void DisplayResult(string result);
- }
- }
2.WcfServiceLib
- using Contracts;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Runtime.Serialization;
- using System.ServiceModel;
- using System.Text;
- namespace WcfServiceLib {
- // 注意: 使用"重构"菜单上的"重命名"命令,可以同时更改代码、svc 和配置文件中的类名"BookService"。
- // 注意: 为了启动 WCF 测试客户端以测试此服务,请在解决方案资源管理器中选择 BookService.svc 或 BookService.svc.cs,然后开始调试。
- public class BookService: IBookService {
- /// <summary>
- /// 请求与答复模式,默认模式
- /// </summary>
- /// <param name="Id">书籍ID</param>
- /// <returns></returns>
- public string GetBook(string Id) {
- System.Threading.Thread.Sleep(20000);
- int bookId = Convert.ToInt32(Id);
- Books book = SetBook(bookId);
- string xml = XMLHelper.ToXML(book);
- return xml;
- }
- public Books SetBook(int Id) {
- Books book = new Books();
- book.BookID = Id;
- book.AuthorID = 1;
- book.Category = "IBM";
- book.Price = 39.99M;
- book.Numberofcopies = 25;
- book.Name = "DB2数据库性能调整和优";
- book.PublishDate = new DateTime(2015, 2, 23);
- return book;
- }
- /// <summary>
- /// 单工模式,显示名称
- /// </summary>
- /// <param name="name">名称</param>
- public void ShowName(string name) {
- string result = string.Format("书籍名称:{0},日期时间{1}", name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
- Console.WriteLine("\r\n" + result);
- }
- /// <summary>
- /// 双工模式,回调显示结果
- /// </summary>
- /// <param name="name">名称</param>
- public void DisplayName(string name) {
- string result = string.Format("书籍名称:{0},日期时间{1}", name, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
- Console.WriteLine("\r\n" + result);
- ICallback call = OperationContext.Current.GetCallbackChannel();
- call.DisplayResult("回调客户端---" + result);
- }
- }
- }
在服务端,通过 OperationContext.Current.GetCallbackChannel 来获得客户端指定的 CallbackContext 实例,进而调用客户端的操作。
3.Hosting:
宿主配置文件:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <startup>
- <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
- </startup>
- <system.serviceModel>
- <diagnostics>
- <messageLogging logEntireMessage="true" logKnownPii="false" logMalformedMessages="true"
- logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" />
- <endToEndTracing propagateActivity="true" activityTracing="true" messageFlowTracing="true"
- />
- </diagnostics>
- <services>
- <service name="WcfServiceLib.BookService">
- <endpoint address="net.tcp://127.0.0.1:9999/BookService" binding="netTcpBinding"
- contract="Contracts.IBookService" />
- </service>
- </services>
- </system.serviceModel>
- </configuration>
我们通过 netTcpBinding 来模拟基于 TCP 的双向通信。代码如下:
- using Contracts;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.ServiceModel;
- using System.ServiceModel.Description;
- using System.Text;
- using System.Threading.Tasks;
- using WcfServiceLib;
- namespace ConsoleHosting {
- class Program {
- static void Main(string[] args) {
- Console.WriteLine("输入启动方式,C--Code A -- App.config方式!");
- string key = Console.ReadLine();
- switch (key) {
- case "C":
- StartByCode();
- break;
- case "A":
- StartByConfig();
- break;
- default:
- Console.WriteLine("没有选择启动方式,使用默认方式");
- StartByCode();
- break;
- }
- }
- private static void StartByCode() {
- //创建宿主的基地址
- Uri baseAddress = new Uri("http://localhost:8080/BookService");
- //创建宿主
- using(ServiceHost host = new ServiceHost(typeof(BookService), baseAddress)) {
- //向宿主中添加终结点
- host.AddServiceEndpoint(typeof(IBookService), new WSDualHttpBinding(), baseAddress);
- if (host.Description.Behaviors.Find() == null) {
- //将HttpGetEnabled属性设置为true
- ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
- behavior.HttpGetEnabled = true;
- behavior.HttpGetUrl = baseAddress;
- //将行为添加到Behaviors中
- host.Description.Behaviors.Add(behavior);
- //打开宿主
- host.Opened += delegate {
- Console.WriteLine("BookService控制台程序寄宿已经启动,HTTP监听已启动....,按任意键终止服务!");
- };
- host.Open();
- //print endpoint information
- Console.ForegroundColor = ConsoleColor.Yellow;
- foreach(ServiceEndpoint se in host.Description.Endpoints) {
- Console.WriteLine("[终结点]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-绑定]: {2} \r\n\t [C-协定]: {3}", se.Name, se.Address, se.Binding.Name, se.Contract.Name);
- }
- Console.Read();
- }
- }
- }
- private static void StartByConfig() {
- using(ServiceHost host = new ServiceHost(typeof(BookService))) {
- host.Opened += delegate {
- Console.WriteLine("BookService控制台程序寄宿已经启动,TCP监听已启动....,按任意键终止服务!");
- };
- host.Open();
- //print endpoint information
- Console.ForegroundColor = ConsoleColor.Yellow;
- foreach(ServiceEndpoint se in host.Description.Endpoints) {
- Console.WriteLine("[终结点]: {0}\r\n\t[A-地址]: {1} \r\n\t [B-绑定]: {2} \r\n\t [C-协定]: {3}", se.Name, se.Address, se.Binding.Name, se.Contract.Name);
- }
- Console.Read();
- }
- }
- }
- }
4.客户端:
配置文件中的信息进行修改:
- <system.serviceModel>
- <client>
- <endpoint address="net.tcp://localhost:9999/BookService" binding="netTcpBinding"
- bindingConfiguration="" contract="Contracts.IBookService" name="BookServiceEndpoint"
- />
- </client>
- </system.serviceModel>
接下来实现对双工服务的调用,下面是相关的配置和托管程序。在服务调用程序中,通过 DuplexChannelFactory<TChannel> 创建服务代理对 象,DuplexChannelFactory<TChannel> 和 ChannelFactory<TChannel> 的功能 都是一个服务代理对象的创建工厂,不过 DuplexChannelFactory<TChannel> 专门用于基于双工通信的服务代理的创 建。在创建 DuplexChannelFactory<TChannel> 之前,先创建回调对象,并通过 InstanceContext 对回 调对象进行包装。代码如下:
- private void btnTcpDuplex_Click(object sender, EventArgs e) {
- DuplexChannelFactory channelFactory = new DuplexChannelFactory(instanceContext, "BookServiceEndpoint");
- IBookService client = channelFactory.CreateChannel();
- //在BookCallBack对象的mainThread(委托)对象上搭载两个方法,在线程中调用mainThread对象时相当于调用了这两个方法。
- textBox1.Text += string.Format("开始调用wcf服务:{0}\r\n\r\n", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
- client.DisplayName("TCP---科学可以这样看丛书");
- textBox1.Text += string.Format("\r\n\r\n调用结束:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
- }
在创建 DuplexChannelFactory<IBookService> 中,指定了 Callback Context Instance: 一个实现了 Callback Contract 的 BookCallBack 对象。该对象在 Service 中通过 OperationContext.Current.GetCallbackChannel<ICallback>() 获得。
通过运行程序之后的结果如下图:
2. 基于 Http 的双向通讯 V.S. 基于 TCP 的双向通讯
由于 Http 和 TCP 在各自协议上的差异,他们实现双向通信的发式是不同的。
Http 是一个应用层的协议,它的主要特征就是无连接和无状态。它采用传统的 "请求 / 回复" 的方式进行通信,客户端发送 Http Request 请求服务端的某个资源,服务端接收到该 Http Request, 回发对应的 Http Response。当客户端接收到对应的 Response,该连接就会关闭。也就是说客户端和服务端的 连接仅仅维持在发送 Request 到接收到 Response 这一段时间内。同时,每次基于 Http 的 连接是相互独立,互不相干的,当前连接无法获得上一次连接的状态。为了保存调用的的状态信 息,ASP.NET 通过把状态信息保存在服务端的 Session 之中,具体的做法是:ASP.NET 为每个 Session 创建一个 Unique ID,与之关联一个 HttpSessionState 对象,并把状态信息保存在内存中或者持久的存储介质(比如 SQL Server)中。而 WCF 则采用另外的方式实现对 Session 的支持:每个 Session 关联到某个 Service Instance 上。
我们来讲一下 HTTP 双向通信的过程,当客户端通过 HTTP 请求调用 WCF 服务之前,会有一个终结点在客户端被创建,用于监听服务端对它的 Request。客户端对 WCF 服务的调用会建立一个客户端到服务端的连接,当 WCF 服务在执行操作过程中需要回调对应的客户端,实际上会建立另一个服务端到客户端的 Http 连接。虽然我们时候说 WCF 为支持双向通信提供一个双工通道,实际上这个双工通道是由两个 HTTP 连接组成的。
再来看一下 TCP 的双向通信的过程,对于 TCP 传输层协议,它则是一个基于连接的协议,在正式进行数据传输的之前,必须要在客户端和服务端之间建立一个连接,连接的建立通过经典的 "3 次握手" 来实现。TCP 天生就具有双工的特性,也就是说当连接 被创建之后,从客户端到服务端,和从服务端到客户端的数据传递都可以利用同一个连接来实现。对于 WCF 中的双向通信,客户端调用服务端,服务端回调客户端的操作使用的都是同一个连接、同一个通道。所以基于 TCP 的双工通信模式才是真正意义上的双工通信模式。
来源: http://www.cnblogs.com/chillsrc/p/5776049.html