随笔 - 616, 文章 - 0, 评论 - 10492
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
[索引页]
[源码下载]


稳扎稳打Silverlight(23) - 2.0通信之调用WCF的双向通信(Duplex Service)


作者:webabcd


介绍
Silverlight 2.0 调用 WCF 的双向通信服务(Duplex Service) 。开发一个服务端主动向客服端发送股票信息的程序,首先客户端先向服务端发送需要监控的股票的股票代码,然后服务端在该股信息发生变化的时候将信息推送到客户端。
    服务端:
        定义服务契约及回调接口
        从当前上下文获取回调的客户端信道
        需要的话则向客户端信道“推”消息
    客户端:
        构造 PollingDuplexHttpBinding 并在其上创建 IDuplexSessionChannel 的信道工厂
        异步方式打开信道工厂
        异步方式打开信道
        构造需要发送到服务端的消息 System.ServiceModel.Channels.Message
        异步向服务端发送消息
        监听指定信道,用于异步方式接收服务端返回的消息
        不需要再接收服务端的消息则关闭信道


在线DEMO
http://www.cnblogs.com/webabcd/archive/2008/10/09/1307486.html


示例
服务端:
IDuplexService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

using System.ServiceModel.Channels;

/// <summary>
/// IDuplexService - 双工(Duplex)服务契约
/// CallbackContract - 双工(Duplex)服务的回调类型
/// </summary>

[ServiceContract(Namespace = "Silverlight20", CallbackContract = typeof(IDuplexClient))]
public interface IDuplexService
{
    
/// <summary>
    
/// 客户端向服务端发送消息的方法
    
/// </summary>
    
/// <param name="receivedMessage">客户端向服务端发送的消息 System.ServiceModel.Channels.Message</param>

    [OperationContract(IsOneWay = true)]
    
void SendStockCode(Message receivedMessage);
}


/// <summary>
/// 双工(Duplex)服务的回调接口
/// </summary>

public interface IDuplexClient
{
    
/// <summary>
    
/// 客户端接收服务端发送过来的消息的方法
    
/// </summary>
    
/// <param name="returnMessage">服务端向客户端发送的消息 System.ServiceModel.Channels.Message</param>

    [OperationContract(IsOneWay = true)]
    
void ReceiveStockMessage(Message returnMessage);
}


DuplexService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

using System.ServiceModel.Channels;
using System.Threading;
using System.ServiceModel.Activation;
using System.IO;

/// <summary>
/// Duplex 服务的服务端的实现
/// 本文以客户端向服务端提交股票代码,服务端定时向客户端发送股票信息为例
/// </summary>

public class DuplexService : IDuplexService
{
    IDuplexClient _client;
    
bool _status = true;

    
/// <summary>
    
/// 客户端向服务端发送股票代码的方法
    
/// </summary>
    
/// <param name="receivedMessage">包含股票代码的 System.ServiceModel.Channels.Message </param>

    public void SendStockCode(Message receivedMessage)
    
{
        
// 获取当前上下文的回调信道
        _client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();

        
// 如果发生错误则不再执行
        OperationContext.Current.Channel.Faulted += new EventHandler(delegate { _status = false; });

        
// 获取用户提交的股票代码
        string stockCode = receivedMessage.GetBody<string>();

        
// 每3秒向客户端发送一次股票信息
        while (_status)
        
{
            
// 构造需要发送到客户端的 System.ServiceModel.Channels.Message
            
// Duplex 服务仅支持 Soap11 , Action 为请求的目的地(需要执行的某行为的路径)
            Message stockMessage = Message.CreateMessage(
                MessageVersion.Soap11,
                
"Silverlight20/IDuplexService/ReceiveStockMessage",
                
string.Format("StockCode: {0}; StockPrice: {1}; CurrentTime: {2}",
                    stockCode,
                    
new Random().Next(1200),
                    DateTime.Now.ToString()));

            
try
            
{
                
// 向客户端“推”数据
                _client.ReceiveStockMessage(stockMessage);
            }

            
catch (Exception ex)
            
{
                
// 出错则记日志
                using (StreamWriter sw = new StreamWriter(@"C:\Silverlight_Duplex_Log.txt"true))
                
{
                    sw.Write(ex.ToString());
                    sw.WriteLine();
                }

            }


            System.Threading.Thread.Sleep(
3000);
        }

    }

}

PollingDuplexServiceHostFactory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Activation;

/* 以下部分摘自文档 */

// 服务 svc 文件的 Factory 要指定为此类
public class PollingDuplexServiceHostFactory : ServiceHostFactoryBase
{
    
public override ServiceHostBase CreateServiceHost(string constructorString,
        Uri[] baseAddresses)
    
{
        
return new PollingDuplexSimplexServiceHost(baseAddresses);
    }

}


class PollingDuplexSimplexServiceHost : ServiceHost
{
    
public PollingDuplexSimplexServiceHost(params System.Uri[] addresses)
    
{
        
base.InitializeDescription(typeof(DuplexService), new UriSchemeKeyedCollection(addresses));
    }


    
protected override void InitializeRuntime()
    
{
        
// 配置 WCF 服务与 Silverlight 客户端之间的 Duplex 通信
        
// Silverlight 客户端定期轮询网络层上的服务,并检查回调信道上由服务端发送的所有新的消息
        
// 该服务会将回调信道上的由服务端发送的所有消息进行排队,并在客户端轮询服务时将这些消息传递到该客户端

        PollingDuplexBindingElement pdbe 
= new PollingDuplexBindingElement()
        
{
            
// ServerPollTimeout - 轮询超时时间
            
// InactivityTimeout - 服务端与客户端在此超时时间内无任何消息交换的情况下,服务会关闭其会话

            ServerPollTimeout 
= TimeSpan.FromSeconds(3),
            InactivityTimeout 
= TimeSpan.FromMinutes(1)
        }
;

        
// 为服务契约(service contract)添加一个终结点(endpoint)
        
// Duplex 服务仅支持 Soap11
        this.AddServiceEndpoint(
            
typeof(IDuplexService),
            
new CustomBinding(
                pdbe,
                
new TextMessageEncodingBindingElement(
                    MessageVersion.Soap11,
                    System.Text.Encoding.UTF8),
                
new HttpTransportBindingElement()),
                
"");

        
base.InitializeRuntime();
    }

}


DuplexService.svc
<%@ ServiceHost Language="C#" Debug="true" Service="DuplexService" CodeBehind="~/App_Code/DuplexService.cs" Factory="PollingDuplexServiceHostFactory" %>


客户端:
DuplexService.xaml
<UserControl x:Class="Silverlight20.Communication.DuplexService"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml">
    
<StackPanel HorizontalAlignment="Left" Margin="5">
    
        
<TextBox x:Name="txtStockCode" Text="请输入股票代码" Margin="5" />
        
<Button x:Name="btnSubmit" Content="获取股票信息" Click="btnSubmit_Click" Margin="5" />
        
<Button x:Name="btnStop" Content="停止获取" Click="btnStop_Click"  Margin="5" />
        
<TextBlock x:Name="lblStockMessage" Margin="5" />
    
    
</StackPanel>
</UserControl>

DuplexService.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.IO;

namespace Silverlight20.Communication
{
    
public partial class DuplexService : UserControl
    
{
        SynchronizationContext _syncContext;

        
// 是否接收服务端发送过来的消息
        bool _status = true;

        
public DuplexService()
        
{
            InitializeComponent();
        }


        
private void btnSubmit_Click(object sender, RoutedEventArgs e)
        
{
            _status 
= true;

            
// UI 线程
            _syncContext = SynchronizationContext.Current;

            PollingDuplexHttpBinding binding 
= new PollingDuplexHttpBinding()
            
{
                
// InactivityTimeout - 服务端与客户端在此超时时间内无任何消息交换的情况下,服务会关闭其会话
                InactivityTimeout = TimeSpan.FromMinutes(1)
            }
;

            
// 构造 IDuplexSessionChannel 的信道工厂
            IChannelFactory<IDuplexSessionChannel> factory =
                binding.BuildChannelFactory
<IDuplexSessionChannel>(new BindingParameterCollection());

            
// 打开信道工厂
            IAsyncResult factoryOpenResult =
                factory.BeginOpen(
new AsyncCallback(OnOpenCompleteFactory), factory);

            
if (factoryOpenResult.CompletedSynchronously)
            
{
                
// 如果信道工厂被打开的这个 异步操作 已经被 同步完成 则执行下一步
                CompleteOpenFactory(factoryOpenResult);
            }

        }


        
private void btnStop_Click(object sender, RoutedEventArgs e)
        
{
            _status 
= false;
        }


        
void OnOpenCompleteFactory(IAsyncResult result)
        
{
            
// 该异步操作已被同步完成的话则不做任何操作,反之则执行下一步
            if (result.CompletedSynchronously)
                
return;
            
else
                CompleteOpenFactory(result);
        }


        
void CompleteOpenFactory(IAsyncResult result)
        
{
            IChannelFactory
<IDuplexSessionChannel> factory = result.AsyncState as IChannelFactory<IDuplexSessionChannel>;

            
// 完成异步操作,以打开信道工厂
            factory.EndOpen(result);

            
// 在信道工厂上根据指定的地址创建信道
            IDuplexSessionChannel channel =
                factory.CreateChannel(
new EndpointAddress("http://localhost:3036/DuplexService.svc"));

            
// 打开信道
            IAsyncResult channelOpenResult =
                channel.BeginOpen(
new AsyncCallback(OnOpenCompleteChannel), channel);

            
if (channelOpenResult.CompletedSynchronously)
            
{
                
// 如果信道被打开的这个 异步操作 已经被 同步完成 则执行下一步
                CompleteOpenChannel(channelOpenResult);
            }

        }


        
void OnOpenCompleteChannel(IAsyncResult result)
        
{
            
// 该异步操作已被同步完成的话则不做任何操作,反之则执行下一步
            if (result.CompletedSynchronously)
                
return;
            
else
                CompleteOpenChannel(result);
        }


        
void CompleteOpenChannel(IAsyncResult result)
        
{
            IDuplexSessionChannel channel 
= result.AsyncState as IDuplexSessionChannel;

            
// 完成异步操作,以打开信道
            channel.EndOpen(result);

            
// 构造需要发送到服务端的 System.ServiceModel.Channels.Message (客户端终结点与服务端终结点之间的通信单元)
            Message message = Message.CreateMessage(
                channel.GetProperty
<MessageVersion>(), // MessageVersion.Soap11 (Duplex 服务仅支持 Soap11)
                "Silverlight20/IDuplexService/SendStockCode"// Action 为请求的目的地(需要执行的某行为的路径)
                txtStockCode.Text);

            
// 向目的地发送消息
            IAsyncResult resultChannel =
                channel.BeginSend(message, 
new AsyncCallback(OnSend), channel);

            
if (resultChannel.CompletedSynchronously)
            
{
                
// 如果向目的地发送消息的这个 异步操作 已经被 同步完成 则执行下一步
                CompleteOnSend(resultChannel);
            }


            
// 监听指定的信道,用于接收返回的消息
            ReceiveLoop(channel);
        }


        
void OnSend(IAsyncResult result)
        
{
            
// 该异步操作已被同步完成的话则不做任何操作,反之则执行下一步
            if (result.CompletedSynchronously)
                
return;
            
else
                CompleteOnSend(result);
        }


        
void CompleteOnSend(IAsyncResult result)
        
{
            
try
            
{
                IDuplexSessionChannel channel 
= (IDuplexSessionChannel)result.AsyncState;

                
// 完成异步操作,以完成向目的地发送消息的操作
                channel.EndSend(result);
            }

            
catch (Exception ex)
            
{
                _syncContext.Post(WriteText, ex.ToString() 
+ Environment.NewLine);
            }

        }


        
void ReceiveLoop(IDuplexSessionChannel channel)
        
{
            
// 监听指定的信道,用于接收返回的消息
            IAsyncResult result = 
                channel.BeginReceive(
new AsyncCallback(OnReceiveComplete), channel);

            
if (result.CompletedSynchronously)
            
{
                CompleteReceive(result);
            }

        }


        
void OnReceiveComplete(IAsyncResult result)
        
{
            
if (result.CompletedSynchronously)
                
return;
            
else
                CompleteReceive(result);
        }


        
void CompleteReceive(IAsyncResult result)
        
{
            IDuplexSessionChannel channel 
= (IDuplexSessionChannel)result.AsyncState;

            
try
            
{
                
// 完成异步操作,以接收到服务端发过来的消息
                Message receivedMessage = channel.EndReceive(result);

                
if (receivedMessage == null)
                
{
                    
// 服务端会话已被关闭
                    
// 此时应该关闭客户端会话,或向服务端发送消息以启动一个新的会话
                }

                
else
                
{
                    
// 将接收到的信息输出到界面上
                    string text = receivedMessage.GetBody<string>();
                    _syncContext.Post(WriteText, text 
+ Environment.NewLine);

                    
if (!_status)
                    
{
                        
// 关闭信道
                        IAsyncResult resultFactory =
                            channel.BeginClose(
new AsyncCallback(OnCloseChannel), channel);

                        
if (resultFactory.CompletedSynchronously)
                        
{
                            CompleteCloseChannel(result);
                        }


                    }

                    
else
                    
{
                        
// 继续监听指定的信道,用于接收返回的消息
                        ReceiveLoop(channel);
                    }

                }

            }

            
catch (Exception ex)
            
{
                
// 出错则记日志
                using (StreamWriter sw = new StreamWriter(@"C:\Silverlight_Duplex_Log.txt"true))
                
{
                    sw.Write(ex.ToString());
                    sw.WriteLine();
                }

            }

        }


        
void OnCloseChannel(IAsyncResult result)
        
{
            
if (result.CompletedSynchronously)
                
return;
            
else
                CompleteCloseChannel(result);
        }


        
void CompleteCloseChannel(IAsyncResult result)
        
{
            IDuplexSessionChannel channel 
= (IDuplexSessionChannel)result.AsyncState;

            
// 完成异步操作,以关闭信道
            channel.EndClose(result);
        }


        
void WriteText(object text)
        
{
            
// 将信息打到界面上
            lblStockMessage.Text += (string)text;
        }

    }

}


OK
[源码下载]