代码改变世界

WCF 第二章 契约 实现一个双向契约的服务端部分

2011-06-23 21:07  DanielWise  阅读(1099)  评论(4编辑  收藏  举报

一个双向契约包含服务终结点和客户端终结点的接口实现。在契约类型中,服务端契约在客户端实现。

列表2.6为一个提供stock price更新的服务定义一个服务契约。它使用双工通信以便于一个客户端可以注册更新,服务将周期性的发送更新消息给客户端。客户端通过调用服务端的RegisterForUpdates操作来初始化通信。服务然后会创建一个线程来周期性的通过调用客户端的PriceUpdate操作来发送更新消息。

列表2.6 双工服务契约:服务端实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Contract
{
[ServiceContract(CallbackContract
=typeof(IClientCallback))]
public interface IServerStock
{
[OperationContract(IsOneWay
= true)]
void RegisterForUpdates(string ticker);
}

public interface IClientCallback
{
[OperationContract(IsOneWay
= true)]
void PriceUpdate(string ticker, double price);
}

public class ServerStock : IServerStock
{
//This is NOT a good notification algorithm as it's creating
//one thread per client. It should be inverted so it's creating
//one thread per ticker instead.
public void RegisterForUpdates(string ticker)
{
Update bgWorker
= new Update();
bgWorker.callback
= OperationContext.Current.GetCallbackChannel<IClientCallback>();
Thread t
= new Thread(new ThreadStart(bgWorker.SendUpdateToClient));
t.IsBackground
= true;
t.Start();
}
}
public class Update
{
public IClientCallback callback = null;
public void SendUpdateToClient()
{
Random p
= new Random();
for (int i = 0; i < 10; i++)
{
Thread.Sleep(
5000); //updates occurs somewhere
try
{
callback.PriceUpdate(
"msft", 100.00 + p.NextDouble());
}
catch (Exception ex)
{
Console.WriteLine(
"Error sendinig cache to client:{0}", ex.Message);
}
}
}
}
}

为了完整性,相关配置文件在列表2.7中显示。注意这里使用了双向绑定。

列表2.7 双工服务契约:服务端配置

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="MEXServiceTypeBehavior" name="WCFTest.ServerStock">
        <endpoint address="" binding="wsDualHttpBinding" contract="WCFTest.IServerStock" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://DL00549-990:8000" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MEXServiceTypeBehavior">
          <serviceMetadata httpGetEnabled="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

列表2.6中的代码有一个问题:它为每个客户端创建一个线程。对这个情况来说,可能有不可预测的客户端数目(可能是上百万个),但是stock tickers是有限的(以千记)。因此,为每个stock ticke创建一个线程比为每个客户端创建一个线程会更好。

  列表2.8显示了另一种方法。在这个例子中,使用一个哈希表来维护并跟踪每个客户端请求更新的stock tickers.Update 类存储在哈希表中而且每个Update类在自己的线程上执行。客户端回调列表存储在Update类的本地线程存储区中,所以Update类可以通知所有的客户端关于一个特殊的stock ticker可用。注意当访问客户端列表集合时会设置一个锁,在主StockService类中的RegisterForUpdates方法和Update类本身都有锁。当列表集合被Update类修改时保证列表同时不会被StockeService类更新是必要的。

列表2.8 双工服务契约:服务端实现(更好的线程利用)

public class ServerStock : IServerStock
{
    public class Worker
    {
        public string ticker;
        public Update workerProcess;
    }
    public static Hashtable workers = new Hashtable();

    public void RegisterForUpdates(string ticker)
    {
        Worker w = null;

        //if needed, create a new worker, add it to the hashtable
        //and start it on a new thread
        if (!workers.ContainsKey(ticker))
        {
            w = new Worker();
            w.ticker = ticker;
            w.workerProcess = new Update();
            w.workerProcess.ticker = ticker;
            w.workerProcess.callbacks.Add(OperationContext.Current.GetCallbackChannel<IClientCallback>());
            workers[ticker] = w;

            Thread t = new Thread(new ThreadStart(w.workerProcess.SendUpdateToClient));
            t.IsBackground = true;
            t.Start();
        }
    }
}
public class Update
{
    public string ticker;
    public List<IClientCallback> callbacks = new List<IClientCallback>();
    public void SendUpdateToClient()
    {
        Random w = new Random();
        Random p = new Random();
        while (true)
        {
            Thread.Sleep(w.Next(5000)); //updates occurs somewhere
            lock (callbacks)
            {
                foreach (IClientCallback c in callbacks)
                {
                    try
                    {
                        c.PriceUpdate(ticker, 100.00 + p.NextDouble() * 10);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error sendinig cache to client:{0}", ex.Message);
                    }
                }
            }
        }
    }
}
   

  对于在列表2.7中显示的每客户端一个线程的实现或者列表2.8中显示的每个ticker一个线程的实现方式来说,仍然有可靠性问题。比如,如果服务的不能调用客户端的回调方法,它打印一条日志到控制台,但是它从不会重试。服务端应该重试吗?如果是的话,多久重试一次?什么时候应该停止呢?或者,客户端知道有一个窗口期,在这个期间它将不会接收到更新消息,那么客户端应该从哪里查询更新以便于在接下来的时间可以继续查询?有很多需要解决的重要问题,通过使用代理,比如微软BizTalk服务或者其他类似产品可以解决。消息代理一般有持久的存储介质(数据库,文件系统或者消息队列)在核心系统上而且包括鲁棒性配置工具来确定传输和重试协议。但是它们也要承担性能,复杂度和费用的开销,所以解决方案将很大程度上依赖需求。