为了账号安全,请及时绑定邮箱和手机立即绑定

使命必达: 深入剖析WCF的可靠会话

标签:
架构

通过前面一系列的博文(《WCF 并发(Concurrency)的本质》、《并发中的同步》、《实践重于理论》、《并发与实例上下文模式》、《回调与并发》、《ConcurrencyMode.Multiple 模式下的WCF服务就一定是并发执行的吗[上篇]》、《ConcurrencyMode.Multiple 模式下的WCF服务就一定是并发执行的吗[下篇]》、《控制并发访问的三道屏障[上篇]》和《控制并发访问的三道屏障[下篇]》),我对WCF的并发体系进行了深入的剖析,在接下来的博文中,我只要专注于WCF的可靠会话机制。

作为一个通信基础平台,WCF必须保证通信的可靠性。由于消息交换是WCF采用的通信手段,通信可靠性的保障体现在确保消息的可靠传输。WCF本质上是一个消息处理框架,作为整个消息交换系统的两个终端,即发送端和接收端。换句话说,WCF仅仅负责对消息的发送和接收,一旦消息通过WCF的信道层进入了网络,就脱离了WCF的控制范围。但是,由于网络环境的限制,网络层不能百分之百地确保对消息的有效交付。如何克服中间环节的制约,确保从一端发送的消息能够被有效地交付给另一端,这就是可靠消息传输(Reliable Messaging)需要解决的问题。WCF通过可靠会话(Reliable Sessions)实现了种种端到端(End to End)的可靠消息传输。源代码从这里下载。

为了让读者对可靠会话的作用现有一个直观的认识,我们先来做一个很有意思的实例演示。接下来我们将要演示的实例是对可靠会话确保WCF消息传输的可靠性的一个直观的反应,也是早年微软推广WCF技术频繁使用的案例:图片传输。在客户端,我们选择一张图片,并对它进行切片,最后通过调用WCF服务将每一个切片依次传输到服务端。服务端则按照切片被接收到的顺序重新组装成一张完整的图片。如果中间有任何一张切片丢失,服务端最终组装图片将不会完整;如果服务端切片接收的次序和发送顺序不一致,将会造成组装后的图片并不能还原其发送前的模样。在这里,我们充分利用了WCF中的可靠会话提供了可靠而有序的消息交付。

不稳定的网络是造成消息丢失最主要的因素,但是在本机环境下模拟不稳定的网络是一件比较困难的事情。但是,虽然我们不能让消息在网络传输层中丢失,但是我们可以让它在WCF的信道层中丢失。如何实现这样的目的呢,相应阅读过《WCF技术剖析(卷1)》第3章的读者会很快想到可以采用自定义信道的方式。

步骤一:通过自定义信道模拟不稳定的网络

为了对网络传输过程中的丢包率能够进行动态控制,我特意创建一个特殊的类型MessageInspector。MessageInspector定义如下,只读属性DropRate表示丢包率,ProcessMessage对传入的消息进行处理,如果返回为Null,意味着消息的丢失。MessageInspector定义如下。

using System;

   

using System.ServiceModel.Channels;

   

namespace Artech.ImageTransfer.Extensions

   

{

   

    public class MessageInspector

   

    {

   

        public int DropRate{ get; private set; }

   

        public Random Randomizer{ get; private set; }

   

        public MessageInspector(int dropRate)

   

        {

   

            this.DropRate = dropRate;

   

            this.Randomizer = new Random();

   

        }

   

        public virtual void ProcessMessage(ref Message message)

   

        {

   

            int randomNumber = this.Randomizer.Next(100);

   

            if (randomNumber <= this.DropRate)

   

            {

   

                message = null;

   

            }

   

        }

   

    }

   

}

接下来我们就来创建这个用于模拟不稳定网络环境的自定义信道UnreliableNetworkSimulateChannel。由于我们即将演示的实例采用TCP传输方式,所以我们让UnreliableNetworkSimulateChannel实现了IDuplexSessionChannel接口。UnreliableNetworkSimulateChannel通过MessageInspector对象对传入的消息进行加工(根据丢包率随即地丢弃)。MessageInspector在构造函数中创建,而丢包率通过参数传入。除了Send方法,几乎所有的成员都是调用InnerChannel相应的方法或者返回同名的属性。由于在《WCF技术剖析(卷1)》我们有过对如何自定义信道的专门介绍,在这里我们就不在多做重复的讲述了。

using System;

   

using System.ServiceModel.Channels;

   

namespace Artech.ImageTransfer.Extensions

   

{

   

    public class UnreliableNetworkSimulateChannel : IDuplexSessionChannel

   

    {

   

        public IDuplexSessionChannel InnerChannel{ get; private set; }

   

        public MessageInspector MessageInspector{ get; private set; }

   

        public UnreliableNetworkSimulateChannel(IDuplexSessionChannel innerChannel, int dropRate)

   

        {

   

            this.InnerChannel = innerChannel;

   

            this.MessageInspector = new MessageInspector(dropRate);

   

        }

   

        public IAsyncResult BeginReceive(TimeSpan timeout, AsyncCallback callback, object state)

   

        {

   

            return this.InnerChannel.BeginReceive(timeout, callback, state);

   

        }

   

        public void Send(Message message, TimeSpan timeout)

   

        {

   

            this.MessageInspector.ProcessMessage(ref message);

   

            if(null != message)

   

            {

   

                this.InnerChannel.Send(message, timeout);

   

            }

   

        }

   

        public void Send(Message message)

   

        {

   

            this.MessageInspector.ProcessMessage(ref message);

   

            if (null != message)

   

            {

   

                this.InnerChannel.Send(message);

   

            }

   

        }

   

        //其他成员:直接调用InnerChannel的相应的方法或者返回同名属性

   

    }

   

}

通过上面的代码我们可以看到,在Send方法中,消息对象会先传入MessageInspector的ProcessMessage方法中,如果返回值不为空,将其递交给InnerChannel,反之意味着消息在信道层中丢失。接下来我们为该自定义信道创建信道管理器,由于该信道只在客户端使用,我们只需要为之创建信道工厂即可(Channel Factory)。UnreliableNetworkSimulateChannel对应的信道工厂UnreliableNetworkSimulateChannelFactory<TChannel>定义如下。

using System;

   

using System.ServiceModel.Channels;

   

using System.ServiceModel;

   

namespace Artech.ImageTransfer.Extensions

   

{

   

    public class UnreliableNetworkSimulateChannelFactory<TChannel> : ChannelFactoryBase<IDuplexSessionChannel>

   

    {

   

        public int DropRate {get; private set;}

   

        public IChannelFactory<TChannel> InnerChannelFactory{ get; private set; }

   

        public UnreliableNetworkSimulateChannelFactory(BindingContext context, int dropRate):base(context.Binding)

   

        {

   

            this.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();

   

            this.DropRate = dropRate;

   

        }

   

        protected override IDuplexSessionChannel OnCreateChannel(EndpointAddress address, Uri via)

   

        {

   

            var innerChannel = (IDuplexSessionChannel)this.InnerChannelFactory.CreateChannel(address, via);

   

            return new UnreliableNetworkSimulateChannel(innerChannel,this.DropRate);

   

        }

   

        protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)

   

        {

   

            return this.InnerChannelFactory.BeginOpen(timeout, callback, state);

   

        }

   

        protected override void OnEndOpen(IAsyncResult result)

   

        {

   

            this.InnerChannelFactory.EndOpen(result);

   

        }

   

        protected override void OnOpen(TimeSpan timeout)

   

        {

   

            this.InnerChannelFactory.Open(timeout);

   

        }

   

    }

   

}

由于WCF信道栈的缔造者为绑定,而信道管理器(信道工厂或者信道监听器)最终借助于绑定元素而发送作用。为此,我们为我们创建的信道工厂创建了如下一个绑定元素:UnreliableNetworkSimulateBindingElement。

using System.ServiceModel.Channels;

   

namespace Artech.ImageTransfer.Extensions

   

{

   

    public class UnreliableNetworkSimulateBindingElement : BindingElement

   

    {

   

        public int DropRate { get; set; }

   

        public UnreliableNetworkSimulateBindingElement(int dropRate)

   

        {

   

            this.DropRate = dropRate;

   

        }

   

        public override BindingElement Clone()

   

        {

   

            return new UnreliableNetworkSimulateBindingElement(this.DropRate);

   

        }

   

        public override T GetProperty<T>(BindingContext context)

   

        {

   

            return context.GetInnerProperty<T>();

   

        }

   

        public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)

   

        {

   

            return (IChannelFactory<TChannel>)new UnreliableNetworkSimulateChannelFactory<TChannel>(context, this.DropRate);

   

        }

   

    }

   

}

为了使上面的绑定元素具有可配制性,我们还需要为之创建相应的配置元素。在WCF编程模型下,我们只需要集成BindingElementExtensionElement类即可。在下面定义的UnreliableNetworkSimulateExtensionElement,我们将丢包率定义成配置属性,该属性默认值为20(20%丢包率)。

using System;

   

using System.Configuration;

   

using System.ServiceModel.Channels;

   

using System.ServiceModel.Configuration;

   

namespace Artech.ImageTransfer.Extensions

   

{

   

    public class UnreliableNetworkSimulateExtensionElement:BindingElementExtensionElement

   

    {

   

        [ConfigurationProperty("dropRate", IsRequired = false, DefaultValue = 20)]

   

        public int DropRate

   

        {

   

            get

   

            {

   

                return (int)this["dropRate"];

   

            }

   

            set

   

            {

   

                this["dropRate"] = value;

   

            }

   

        }

   

        public override Type BindingElementType

   

        {

   

            get { return typeof(UnreliableNetworkSimulateBindingElement); }

   

        }

   

        protected override BindingElement CreateBindingElement()

   

        {

   

            return new UnreliableNetworkSimulateBindingElement(this.DropRate);

   

        }

   

    }

   

}

步骤二:创建图片传输服务

解决了对不稳定网络环境的模拟问题,我们现在正式来创建我们用于图片传输的WCF服务。先来看看服务契约的定义。服务契约IImageTransfer具有两个单向(One-Way)服务操作。Transfer方法用于对图片切片(以字节数组的形式)的传输,而Erase则用于通知接收端将之前接收的图片删除。

using System.ServiceModel;

   

namespace Artech.ImageTransfer.Service.Interface

   

{

   

    [ServiceContract(Namespace="http://www.artech.com/")]

   

    public interface IImageTransfer

   

    {

   

        [OperationContract(IsOneWay = true)]

   

        void Transfer(byte[] imageSlice);

   

        [OperationContract(IsOneWay = true)]

   

        void Erase();

   

    }

   

}

服务端需要将接收到的图片切片组装成一个完整的图片,我将图片组装的功能通过如下一个叫做ImageAssembler的静态类来提供。对应于服务契约定义的两个服务操作,ImageAssembler中定义两个静态事件ImageSliceReceived和ImageErasing。这两个事件分别通过静态方法ReceiveImageSlice和Erase出发。事件ImageSliceReceived的事件参数类型为ImageReceivedEventArgs ,它和ImageAssembler定义如下。

using System;

   

namespace Artech.ImageTransfer.Service

   

{

   

    public static class ImageAssembler

   

    {

   

        public static void ReceiveImageSlice(byte[] imageSlice)

   

        {

   

            if (null != ImageSliceReceived)

   

            {

   

                ImageSliceReceived(null, new ImageReceivedEventArgs(imageSlice));

   

            }

   

        }

   

 

   

        public static void Erase()

   

        {

   

            if (null != ImageErasing)

   

            {

   

                ImageErasing(null,  EventArgs.Empty);

   

            }

   

        }

   

 

   

        public static event EventHandler<ImageReceivedEventArgs> ImageSliceReceived;

   

        public static event EventHandler ImageErasing;

   

    }

   

 

   

    public class ImageReceivedEventArgs : EventArgs

   

    {

   

        public byte[] ImageSlice

   

        { get; private set; }

   

 

   

        public ImageReceivedEventArgs(byte[] imageSlice)

   

        {

   

            this.ImageSlice = imageSlice;

   

        }

   

    }

   

}

接下来是服务传输服务的实现,该服务定义在如下的ImageTransferService类中。对于两个服务操作,我们分别调用ImageAssembler的两个对应的静态方法提供实现。

using System.ServiceModel;

   

using Artech.ImageTransfer.Service.Interface;

   

namespace Artech.ImageTransfer.Service

   

{

   

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

   

    public class ImageTransferService : IImageTransfer

   

    {

   

        public void Transfer(byte[] imageSlice)

   

        {

   

            ImageAssembler.ReceiveImageSlice(imageSlice);

   

        }

   

        public void Erase()

   

        {

   

            ImageAssembler.Erase();

   

        }

   

    }

   

}

步骤三:服务寄宿和图片接收程序实现

图片传输服务ImageTransferService最终被寄宿于一个Windows Forms应用中,该应用同时作为图片接收程序使用。我们先来看看服务寄宿端的配置:

<?xml version="1.0" encoding="utf-8" ?>

   

<configuration>

   

    <system.serviceModel>

   

        <bindings>

   

            <customBinding>

   

                <binding name="nonReliableSession">

   

                  <binaryMessageEncoding>

   

                    <readerQuotas maxArrayLength="2147483647 "/>

   

                  </binaryMessageEncoding>

   

                  <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

                </binding>

   

              <binding name="reliableSession">

   

                <reliableSession ordered="false"/>

   

                <binaryMessageEncoding>

   

                  <readerQuotas maxArrayLength="2147483647 "/>

   

                </binaryMessageEncoding>

   

                <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

              </binding>

   

              <binding name="orderedDelivery">

   

                <reliableSession ordered="true" />

   

                <binaryMessageEncoding>

   

                  <readerQuotas maxArrayLength="2147483647 "/>

   

                </binaryMessageEncoding>

   

                <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

              </binding>

   

            </customBinding>

   

        </bindings>

   

        <services>

   

            <service name="Artech.ImageTransfer.Service.ImageTransferService">

   

                <endpoint address="net.tcp://127.0.0.1:7777/imagetransferservice" binding="customBinding" bindingConfiguration="nonReliableSession" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer" />

   

                <endpoint address="net.tcp://127.0.0.1:8888/imagetransferservice" binding="customBinding" bindingConfiguration="reliableSession" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer" />

   

                <endpoint address="net.tcp://127.0.0.1:9999/imagetransferservice" binding="customBinding" bindingConfiguration="orderedDelivery" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer" />

   

            </service>

   

        </services>

   

    </system.serviceModel>

   

</configuration>

通过上面的配置我们会发现,我们为ImageTransferService配置了三个终结点,它们均采用自定义绑定,并且采用TCP传输方式和二进制消息编码。考虑到对较大尺寸图片的支持,我们将BinaryMessageEncodingElement的MaxArrayLength属性,以及TcpTransportElement的MaxBufferSize和MaxReceivedMessageSize都设置成最大。对于这三个终结点的绑定配置,具有如下不一致的地方。reliableSession和orderedDelivery终结点对应的绑定比nonReliableSession多了一个ReliableSessionElement绑定元素。相信你已经猜到了,ReliableSessionElement是为了实现可靠会话而存在的。进一步地,oreliableSession和orderedDelivery终结点绑定的ReliableSessionElement的Ordered属性分别为False和True。也就是意味着orderedDelivery终结点能够实现对消息的有序交付,而reliableSession终结点则不能。

图片的接收窗口如图1所示,其中每一个方格是一个PictureBox,用户显示接收到的图片切片。对于这些PictureBox的ID,从上到下,从左到右依次是pictureBox11、pictureBox12、...、pictureBox15、...、pictureBox55。整个服务寄宿和图片接收实现在如下的代码中。值得注意的一点是,ImageAssembler_ImageCliceReceived方法将接收到的字节数组转化成位图,依次显示到上述的25个PictureBox上。在方法上面应用了一个MethodImplAttribute特性并指定MethodImplOptions.Synchronized作为参数,所以该方法是同步执行的。也就是说,该方法处理的消息次序就是消息被交付的次序。

using System;

   

using System.Drawing;

   

using System.IO;

   

using System.Runtime.CompilerServices;

   

using System.ServiceModel;

   

using System.Threading;

   

using System.Windows.Forms;

   

namespace Artech.ImageTransfer.Service

   

{

   

    public partial class Recevier : Form

   

    {

   

        private PictureBox[] _pictureBoxes;

   

        private SynchronizationContext _synchronizationContext = null;

   

        private int _index = 0;

   

        private ServiceHost _serviceHost = null;

   

        public Recevier()

   

        {

   

            InitializeComponent();

   

            _pictureBoxes = new PictureBox[]{

   

                this.pictureBox11,this.pictureBox12,this.pictureBox13,this.pictureBox14,this.pictureBox15,                this.pictureBox21,this.pictureBox22,this.pictureBox23,this.pictureBox24,this.pictureBox25,                this.pictureBox31,this.pictureBox32,this.pictureBox33,this.pictureBox34,this.pictureBox35,                this.pictureBox41,this.pictureBox42,this.pictureBox43,this.pictureBox44,this.pictureBox45,                this.pictureBox51,this.pictureBox52,this.pictureBox53,this.pictureBox54,this.pictureBox55};

   

            ImageAssembler.ImageSliceReceived += ImageAssembler_ImageCliceReceived;

   

            ImageAssembler.ImageErasing += ImageAssembler_ImageErasing;

   

        }

   

        [MethodImpl(MethodImplOptions.Synchronized)]

   

        private void ImageAssembler_ImageCliceReceived(object sender, ImageReceivedEventArgs args)

   

        {

   

            Bitmap bitmap = null;

   

            using (MemoryStream stream = new MemoryStream(args.ImageSlice))

   

            {

   

                bitmap = new Bitmap(stream);

   

                _synchronizationContext.Send(state => _pictureBoxes[_index++].Image = bitmap, null);

   

            }

   

        }

   

        private void ImageAssembler_ImageErasing(object sender, EventArgs args)

   

        {

   

            _index = 0;

   

            foreach (var pictureBox in _pictureBoxes)

   

            {

   

                pictureBox.Image = null;

   

            }

   

        }

   

        private void Recevier_Load(object sender, EventArgs e)

   

        {

   

            _synchronizationContext = SynchronizationContext.Current;

   

            _serviceHost = new ServiceHost(typeof(ImageTransferService));

   

            _serviceHost.Open();

   

        }

   

    }

   

}

步骤四:创建图片发送程序

最后我们来编写我们的图片发送端程序,即如果对图片进行切片,并通过调用图片传输服务对切片进行发送。我们照例先来看看WCF在客户端的配置:

<?xml version="1.0" encoding="utf-8" ?>

   

<configuration>

   

    <system.serviceModel>

   

        <bindings>

   

            <customBinding>

   

              <binding name="nonReliableSession">

   

                <binaryMessageEncoding>

   

                  <readerQuotas maxArrayLength="2147483647 "/>

   

                </binaryMessageEncoding>

   

                <unreliabeNetworkSimulate dropRate ="10"/>

   

                <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

              </binding>

   

              <binding name="reliableSession">

   

                <reliableSession ordered="false" />

   

                <binaryMessageEncoding>

   

                  <readerQuotas maxArrayLength="2147483647 "/>

   

                </binaryMessageEncoding>

   

                <unreliabeNetworkSimulate dropRate ="10"/>

   

                <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

              </binding>

   

              <binding name="orderedDelivery">

   

                <reliableSession ordered="true" />

   

                <binaryMessageEncoding>

   

                  <readerQuotas maxArrayLength="2147483647 "/>

   

                </binaryMessageEncoding>

   

                <unreliabeNetworkSimulate dropRate ="10"/>

   

                <tcpTransport maxBufferSize="2147483647 " maxReceivedMessageSize="2147483647" />

   

              </binding>

   

            </customBinding>

   

        </bindings>

   

        <client>

   

            <endpoint name="nonReliableSession" address="net.tcp://127.0.0.1:7777/imagetransferservice" binding="customBinding" bindingConfiguration="nonReliableSession" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer"/>

   

            <endpoint name="reliableSession" address="net.tcp://127.0.0.1:8888/imagetransferservice" binding="customBinding" bindingConfiguration="reliableSession" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer"/>

   

            <endpoint name="orderedDelivery" address="net.tcp://127.0.0.1:9999/imagetransferservice" binding="customBinding" bindingConfiguration="orderedDelivery" contract="Artech.ImageTransfer.Service.Interface.IImageTransfer"/>

   

        </client>

   

        <extensions>

   

            <bindingElementExtensions>

   

                <add name="unreliabeNetworkSimulate" type="Artech.ImageTransfer.Extensions.UnreliableNetworkSimulateExtensionElement, Artech.ImageTransfer.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

   

            </bindingElementExtensions>

   

        </extensions>

   

    </system.serviceModel>

   

</configuration>

同服务寄宿端的配置一样,客户端也配置了三种自定义绑定。所不同的是,它们均多了一个额外的绑定元素UnreliableNetworkSimulateBindingElement,即我们之前创建的用于模拟不稳定网络环境的邦定元素。

图2是图片发送窗口,上边部分一个Picture,会显示通过点击Browse按钮选择的图片。当成功选择某一张用于发送的图片后,点击Send按钮将其发送。Reliable Session和Ordered Delivery两个CheckBox供用户决定是否采用可靠会话,以及有序交付机制进行图片的发送。默认情况下,并不采用可靠会话机制进行图片发送。图片的选择、切片和发送通过下面的代码实现。

using System;

   

using System.Collections.Generic;

   

using System.Drawing;

   

using System.Drawing.Imaging;

   

using System.IO;

   

using System.ServiceModel;

   

using System.Windows.Forms;

   

using Artech.ImageTransfer.Service.Interface;

   

namespace Artech.ImageTransfer.Client

   

{

   

    public partial class Sender : Form

   

    {

   

        private string _imageSource = string.Empty;

   

        private IImageTransfer _nonReliableSessionProxy = null;

   

        private IImageTransfer _reliableSessionProxy = null;

   

        private IImageTransfer _orderedDeliveryProxy = null;

   

        ChannelFactory<IImageTransfer> _nonReliableSessionFactory = new ChannelFactory<IImageTransfer>("nonReliableSession");

   

        ChannelFactory<IImageTransfer> _reliableSessionFactory = new ChannelFactory<IImageTransfer>("reliableSession");

   

        ChannelFactory<IImageTransfer> _orderedDeliveryFactory = new ChannelFactory<IImageTransfer>("orderedDelivery");

   

 

   

        public Sender()

   

        {

   

            InitializeComponent();

   

        }

   

        private IImageTransfer GetProxy()

   

        {

   

            if(null != _nonReliableSessionProxy)

   

            {

   

                (_nonReliableSessionProxy as ICommunicationObject).Close();

   

            }

   

             if(null != _reliableSessionProxy)

   

            {

   

                (_reliableSessionProxy as ICommunicationObject).Close();

   

            }

   

             if(null != _orderedDeliveryProxy)

   

            {

   

                (_orderedDeliveryProxy as ICommunicationObject).Close();

   

            }

   

             if (!this.checkBoxReliableSession.Checked)

   

             {

   

                 _nonReliableSessionProxy = _nonReliableSessionFactory.CreateChannel();

   

                 (_nonReliableSessionProxy as ICommunicationObject).Open();

   

                 return _nonReliableSessionProxy;

   

             }

   

             else if (!this.checkBoxOrdered.Checked)

   

             {

   

                 _reliableSessionProxy = _reliableSessionFactory.CreateChannel();

   

                 (_reliableSessionProxy as ICommunicationObject).Open();

   

                 return _reliableSessionProxy;

   

             }

   

             else

   

             {

   

                 _orderedDeliveryProxy = _orderedDeliveryFactory.CreateChannel();

   

                 (_orderedDeliveryProxy as ICommunicationObject).Open();

   

                 return _orderedDeliveryProxy;

   

             }

   

        }

   

        private void buttonOpen_Click(object sender, EventArgs e)

   

        {

   

            OpenFileDialog openFileDialog = new OpenFileDialog();

   

            if (openFileDialog.ShowDialog()== DialogResult.OK)

   

            {

   

                _imageSource = openFileDialog.FileName;

   

                this.pictureBox1.Load(_imageSource);

   

            }

   

            this.buttonSend.Enabled = true;

   

        }

   

        private byte[] BitmapToBytes(Bitmap bitmap)

   

        {

   

            using (MemoryStream ms = new MemoryStream())

   

            {

   

                bitmap.Save(ms, ImageFormat.Bmp);

   

                byte[] data = new byte[ms.Length];

   

                ms.Seek(0, SeekOrigin.Begin);

   

                ms.Read(data, 0, Convert.ToInt32(ms.Length));

   

                return data;

   

            }

   

        }

   

 

   

        private void buttonSend_Click(object sender, EventArgs e)

   

        {

   

            this.buttonSend.Enabled = false;

   

            IList<byte[]> imageSlices = new List<byte[]>();

   

            Bitmap bmp = new Bitmap(this._imageSource);

   

            double width = (double)bmp.Width / 5;

   

            double height = (double)bmp.Height / 5;

   

            for (int y = 0; y < 5; y++)

   

            {

   

                for (int x = 0; x < 5; x++)

   

                {

   

                    Rectangle rect = new Rectangle(Convert.ToInt32(x * width), Convert.ToInt32(y * height),

   

                        Convert.ToInt32(width), Convert.ToInt32(height));

   

                    byte[] data = BitmapToBytes(bmp.Clone(rect, PixelFormat.DontCare));

   

                    imageSlices.Add(data);

   

                }

   

            }

   

            IImageTransfer proxy = GetProxy();

   

            proxy.Erase();

   

            for (int i = 0; i < imageSlices.Count; i++)

   

            {

   

                proxy.Transfer(imageSlices[i]);

   

            }

   

            this.buttonSend.Enabled = true;

   

        }

   

    }

   

}

通过上面的代码我们可以发现,我们通过GetProxy()方法选择相应终结点对应的ChannelFactory<IImageTransfer>对象,并创建服务代理。在buttonSend_Click方法中,被选择的图片被均分成25个切片,并按照从上到下、从左至右的顺旬转化成字节数据,最终利用创建的服务代理发送出去。在发送之前,调用Erase服务操作通知接收端擦除已经接收到的切片。


所有的编程工作完成后,我们来运行我们的程序。图3表示的是没有采用可靠会话时的图片传输情况。从中我们可以看到两接收方组装后的图片不完整,有四个切片缺失。此外,接收方组装后的切片完全是错位的。

image

3 没有采用可靠会话图片传输情况

图4表示的是选择了可靠会话选项,但是没有选择有序交付选项时图片传输的情况。我们可以看出,这一次解决了切片丢失的问题,但是错位的情况下依然存在。

image

4 选择可靠会话但不选择有序交付时图片传输情况

最后,我们同是选择可靠会话和有序交付两个选项,你在接收端将会得到一张完完整整地图片,既不会有切片丢失,也不会出现切片错位的情况。最终的结果如图5所示,这才是我们希望的。

image

同时选择可靠会话和有序交付时图片传输情况

实际上,WCF的可靠会话涉及到WS中一个重要的概念——可靠消息传输(RM:Reliable Messaging)。如果想对可靠会话有一个深入的认识,对可靠消息传输的了解是必须的。《概念篇》中将会对RM的基本概念进行基本的讲述。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消