65.9K
CodeProject 正在变化。 阅读更多。
Home

实现一个双工服务(使用 WCF)--- WCF 初学者教程

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2015年11月3日

CPOL

2分钟阅读

viewsIcon

16739

本博客记录了学习 WCF 和计算机网络过程中遇到的问题。

引言

在本文的开头,我们先来谈谈 WCF 中的消息传输模式。

消息传输模式

  • 请求-回复 - 默认情况下,所有 WCF 都会以请求-回复模式运行。这意味着当客户端向 WCF 服务发出请求时,客户端会等待从服务接收响应(直到收到超时,默认值为 1 分钟,可以通过 ReceivedTimeOut 对象更改)。如果服务在接收超时时间内没有响应,客户端将收到 TimeOutException

          Request-Reply

 

 

 

  • 单向 - 在单向传输模式下,客户端将请求发送到服务器,而不关心服务执行是否成功或失败。服务器端没有返回值,是单向通信。客户端仅在将其调用分派到服务时会被阻塞一小段时间。服务抛出的任何异常都不会到达客户端。

     ​     客户端在向服务器发出单向调用后,将继续执行其语句,无需等待服务器执行完毕。但有时,当单向调用到达服务时,它们可能不会一次分派,具体取决于服务的配置的 并发 模式行为。如果排队的消息数量超过了队列的容量,即使发出了单向调用,客户端也会被阻塞。

 

 可以通过在 Operation Contract 属性中将 IsOneWay 属性设置为 true 来启用单向操作。
​[ServiceContract]
public interface IMyService
{
    [OperationContract(IsOneWay=true)]
    void MyMethod(string parameter);
}

您还可以将单向通信与会话性服务一起使用。

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
    [OperationContract(IsOneWay = true)]
    void MyMethod();
}
  • 回调服务 - 到目前为止,我们看到所有客户端都会调用服务来完成任务。但 WCF 也提供了服务调用客户端的功能。因此,我们称之为双工传输模式。

回调服务

​       注意
  • HTTP 协议是无连接的,因此不支持回调操作。因此,BasicHttpBindingWSHttpBinding 不能用于回调服务。
  • WSDualHttpBinding 是为回调操作而设计的。
  • 所有这些绑定都包括 TCP 和 IPC 协议支持双工通信。

步骤 1:配置回调契约

实际上,它定义了客户端应该具有哪些操作。

//IPushCallback.cs
​namespace WCFTraining.DuplexStreamingService
{
    public interface IPushCallback
    {
        [OperationContract(IsOneWay = true)]
        void ReceiveData(string data);

        [OperationContract(IsOneWay = true)]
        void ReceiveStream(Stream stream);

        [OperationContract(IsOneWay = true)]
        void ReceiveLog(List<string> log);
    }
}

步骤 2:定义服务及其实现

可以通过在 ServiceContract 属性中使用 CallbackContract 属性来启用回调服务。让我们实现一个复杂的演示。
//IDuplexService.cs
namespace IISHost.DuplexStreamingService
{
    [ServiceContract(CallbackContract = typeof(IPushCallback))]
    public interface IDuplexService
    {
        //Duplex operation
        [OperationContract(IsOneWay = true)]
        void StartPushingData();

        [OperationContract(IsOneWay = true)]
        void StopPushingData();

        [OperationContract(IsOneWay = true)]
        void StartPushingStream();

        [OperationContract(IsOneWay = true)]
        void StopPushingStream();

        //Logging
        [OperationContract(IsOneWay = true)]
        void GetLog();
    }
}
实现服务中的操作。
//DuplexService.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Threading;

namespace WCFTrrainning.DuplexStreamingService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
    public class DuplexService : IDuplexService
    {
        List<string> log = new List<string>();
        static bool continuePushingData;

        #region Duplex operation
        public void StartPushingData()
        {
            log.Add("StartPushingData");
            continuePushingData = true;
            IPushCallback pushCallPackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
            ThreadPool.QueueUserWorkItem(new WaitCallback(PushData), pushCallPackChannel);
        }

        public void StopPushingData()
        {
            log.Add("StopPushingData");
            continuePushingData = false;
        }

        public void StartPushingStream()
        {
            log.Add("StartPushingStream");
            IPushCallback pushCallbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
            ThreadPool.QueueUserWorkItem(new WaitCallback(PushStream), pushCallbackChannel);
        }

        public void StopPushingStream()
        {
            log.Add("StopPushingStream");
            localStream.StopStreaming = true;
        }

        public void GetLog()
        {
            IPushCallback pushCallbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
            pushCallbackChannel.ReceiveLog(log);
        }

        private static void PushData(object state)
        {
            IPushCallback pushCallPackChannel = state as IPushCallback;

            do
            {
                pushCallPackChannel.ReceiveData(CreateInterestingString(rand.Next(4, 256)));
            } while (continuePushingData);

            pushCallPackChannel.ReceiveData("Last Message");
        }

        void PushStream(object state)
        {
            IPushCallback pushCallbackChannel = state as IPushCallback;
            localStream = new FlowControlledStream();
            localStream.ReadThrottle = TimeSpan.FromMilliseconds(800);

            pushCallbackChannel.ReceiveStream(localStream);
        }

        #endregion
    }
}

该服务将托管在 IIS 中。因此,我们需要编辑 web.config

<version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true">
    </compilation>
  </system.web>
    <system.serviceModel>
      <services>
        <service name="IISHost.DuplexStreamingService.DuplexService">
          <endpoint address="" binding="netTcpBinding" contract="IISHost.DuplexStreamingService.IDuplexService"></endpoint>
          <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint>
        </service>
      </services>
      <behaviors>
        <serviceBehaviors>
          <behavior name="">
            <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
            <serviceDebug includeExceptionDetailInFaults="false"/>
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>
    <system.webServer>
        <directoryBrowse enabled="true" />
    </system.webServer>
</configuration>

客户端

步骤 3:实现回调契约

//ClientReceiver.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using DuplexStreamingService;

namespace DuplexStreamingClient
{
    public class ClientReceiver : IPushCallback
    {
        public ManualResetEvent LogReceived { get; set; }
        public ManualResetEvent ReceiveDataInvoked { get; set; }
        public ManualResetEvent ReceiveDataCompleted { get; set; }
        public ManualResetEvent ReceiveStreamInvoked { get; set; }
        public ManualResetEvent ReceiveStreamCompleted { get; set; }
        public string Name { get; set; }

        public List<string> ServerLog { get; set; }

        public ClientReceiver()
        {
            LogReceived = new ManualResetEvent(false);
            ReceiveDataInvoked = new ManualResetEvent(false);
            ReceiveDataCompleted = new ManualResetEvent(false);
            ReceiveStreamInvoked = new ManualResetEvent(false);
            ReceiveStreamCompleted = new ManualResetEvent(false);
            Name = "ClientReceiver_" + DateTime.Now;
        }

        public void ReceiveData(string data)
        {
            Console.WriteLine("[{0}] ReceiveData received the following:", Name);
            Console.WriteLine(data);
            ReceiveDataInvoked.Set();
            if (data == "LastMessage")
            {
                ReceiveDataCompleted.Set();
            }
        }

        public void ReceiveStream(Stream stream)
        {
            ReceiveStreamInvoked.Set();

            int readResult;
            byte[] buffer = new byte[1000];
            do
            {
                try
                {
                    readResult = stream.Read(buffer, 0, buffer.Length);
                    Console.WriteLine("[{0}] just read {1} bytes.{2}stream.Position = {3}", this.Name, readResult, Environment.NewLine, stream.Position);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Caught exception when trying to read: {0}", ex);
                    throw;
                }
            }
            while (readResult != 0);

            stream.Close();

            Console.WriteLine("[{0}] ReceiveStream invoked.", Name);
            ReceiveStreamCompleted.Set();
        }

        public void ReceiveLog(List<string> log)
        {
            ServerLog = log;
            LogReceived.Set();
        }
    }
}

步骤 4:生成一个客户端来调用服务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace DuplexStreamingClient
{
    class Program
    {
        static void Main(string[] args)
        {
            RunTests();
        }

        private static void RunTests()
        {
            string address = "net.tcp:///IISHost/DuplexStreamService/DuplexService.svc";
            NetTcpBinding binding = new NetTcpBinding();
            ClientReceiver clientReceiver = new ClientReceiver();

            DuplexChannelFactory<IDuplexService> channelFactory = new DuplexChannelFactory<IDuplexService>(new InstanceContext(clientReceiver), binding, address);
            IDuplexService client = channelFactory.CreateChannel();

            Console.WriteLine(client.DownloadData());

            // Test Duplex Operation
            Console.WriteLine("Invoking StartPushingStream - get the server to push a stream to the client.");
            client.StartPushingStream();

            Console.WriteLine("Waiting on ReceiveStreamInvoked from the ClientReceiver.");
            clientReceiver.ReceiveStreamInvoked.WaitOne();
            clientReceiver.ReceiveStreamInvoked.Reset();

            Console.WriteLine("Invoking StopPushingStream");
            client.StopPushingStream();
            Console.WriteLine("Waiting on ReceiveStreamCompleted from the ClientReceiver.");
            clientReceiver.ReceiveStreamCompleted.WaitOne();
            clientReceiver.ReceiveStreamCompleted.Reset();

            Console.WriteLine("Getting results from server via callback.");
            client.GetLog();
            clientReceiver.LogReceived.WaitOne();

            Console.WriteLine("----Following are the logs from the server:-----");
            foreach (string serverLogItem in clientReceiver.ServerLog)
            {
                Console.WriteLine(serverLogItem);
            }
            Console.WriteLine("---------------- End server log. ---------------");

            ((IChannel)client).Close();

            Console.WriteLine("Test passed.");
        }
    }
}
这是 WCF 中双工传输模式的基本用法。以后我们将讨论双工的一些高级场景,例如使用 WCF 构建 WebSocket 和使用 WCF 构建广播服务。
© . All rights reserved.