Silverlight与WCF之间的通信(2)利用WCF的双工通信“推送”给SL数据

简介:

一,Duplex简介

上一个随笔记录了SL利用Timer定时去WCF上取数据再绑定到界面上的问题,今天尝试用了WCF的Duplex双工通信来做这个事情,也以这个例子来说明WCF中Duplex的使用。

双工通信的原理很简单,我们平时用的是客户端调用服务端的方法来获取数据,而Duplex是将客户端也当作了服务器,客户端上的方法也可以被调用,以聊天功能为例子,用户A连接到服务器后,之前的做法是客户端定时取数据,而Duplex是在服务端定时检测数据变化,如果发现了发送给A的信息,那么立即会调用客户端的方法来推送信息到A。

二,建立Duplex模式的WCF服务

这里以一个简单的聊天功能来说明,WCF提供了三个方法,连接到服务器方法,发送信息方法和接收信息方法。从服务契约上来说分为两个接口,分别是为客户端提供发送信息和开始聊天方法的IChatService接口和服务器调用客户端方法的IChatServiceCallBack接口

IChatService.cs文件

代码
namespace  ChatWCF  
{  
     [ServiceContract(CallbackContract
= typeof (IChatServiceCallBack))] // 这里需要定义IChatService接口的回调接口IChatServiceCallBack 
     public   interface  IChatService  
    {  
        [OperationContract]  
        
bool  SendMessage(MessageInfo msg);  // 发送信息  

        [OperationContract]  
        
bool  LoginChat( string  User, string  Partner); // 开始聊天模式   
    }  

    [ServiceContract]  
    
public   interface  IChatServiceCallBack  //供 服务端回调的接口 
    {  
        [OperationContract(IsOneWay
= true )]  
        
void  ReceiveMessages(List < MessageInfo >  listMessages); // 客户端被服务端回调后接收信息 
    }  


 

接下来需要实现这接口,IChatService.svc.cs 

代码
namespace  ChatWCF  
{  
     
public   class  ChatService : IChatService  
     {  
        IChatServiceCallBack chatserviceCallBack;  
        
string  _user;  
        
string  _partner;  
Timer timer;
// 开始聊天 
public   bool  LoginChat( string  User,  string  Partner) 

        {  
            
try   
            {  
                chatserviceCallBack 
=  OperationContext.Current.GetCallbackChannel < IChatServiceCallBack > ();  
                _user 
=  User;  
                _partner 
=  Partner;  

                timer 
=   new  Timer( new  TimerCallback(CheckMessages),  this 100 100 );  

                
return   true ;  
            }  
            
catch (Exception ex)  
            {  
                
return   false ;  
            }  
        }  
//检查消息并回调客户端接收此消息,此处是回调的重点 
        
private   void  CheckMessages( object  o)
        {  
            chatserviceCallBack.ReceiveMessages(GetMessages(_user,_partner));  
        }  

// 发送信息  
public   bool  SendMessage(MessageInfo msg)  

                [
将MessageInfo写入数据库...]   
}  

// 检测数据库  
private  List < MessageInfo >  GetMessages( string  User,  string  Partner)  
{  
            List
< MessageInfo >  listMsg  =   new  List < MessageInfo > ();

   [检测数据库并返回检测到的MessageInfo...]      
    
             return  listMsg;  
}  

// 执行简单的SQL语句  
private  DataSet ExcuteSQL( string  strSql)  
       {  
            
string  strServer  =   " server=LEON-PC\\sql2005;database=jplan;uid=sa;pwd=sa; " ;  
            SqlConnection con 
=   new  SqlConnection(strServer);  
            con.Open();  
            SqlDataAdapter dataAdapter 
=   new  SqlDataAdapter(strSql, con);  
            DataSet ds 
=   new  DataSet();  
            dataAdapter.Fill(ds);  
            con.Close();  

            
return  ds;  
        }  
    }  


 

这里需要注意一点的是这个WCF是建立在Duplex基础上的,所以在wcf的项目中需要添加一个程序集:

Assembly System.ServiceModel.PollingDuplex 
    C:\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Server\System.ServiceModel.PollingDuplex.dll

 

接下来需要对Web.config进行配置,主要是ServiceModel节点:

代码
< system.serviceModel >   
    
< behaviors >   
      
< serviceBehaviors >   
        
< behavior  name ="ChatWCF.ChatBehavior" >   
          
< serviceMetadata  httpGetEnabled ="true"   />   
          
< serviceDebug  includeExceptionDetailInFaults ="false"   />   
        
</ behavior >   
      
</ serviceBehaviors >   
    
</ behaviors >   
    
< services >   
      
< service  behaviorConfiguration ="ChatWCF.ChatBehavior"  name ="ChatWCF.ChatService" >   
        
< endpoint  
           
address =""   
           binding
="pollingDuplexHttpBinding"   
           bindingConfiguration
="multipleMessagesPerPollPollingDuplexHttpBinding"   
           contract
="ChatWCF.IChatService" >   
        
</ endpoint >   
        
< endpoint  
            
address ="mex"   
            binding
="mexHttpBinding"   
            contract
="IMetadataExchange" />   
      
</ service >   
    
</ services >   
    
< extensions >   
      
< bindingExtensions >   
        
< add  name =  
            "pollingDuplexHttpBinding"
  
            type
="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"   />   
      
</ bindingExtensions >   
    
</ extensions >   

    
< bindings >    
      
< pollingDuplexHttpBinding >   
        
< binding  name ="multipleMessagesPerPollPollingDuplexHttpBinding"   
                 duplexMode
="MultipleMessagesPerPoll"   
                 maxOutputDelay
="00:00:07" />   
      
</ pollingDuplexHttpBinding >  
    
</ bindings >   
    
< serviceHostingEnvironment  multipleSiteBindingsEnabled ="true"   />   
  
</ system.serviceModel >   

 

如果您的WCF服务是一个单独的站点,而客户端是SL的话,鉴于SL的安全性考虑不支持跨域访问,那么就需要在WCF的根目录下放置一个XML策略文件,文件名为

clientaccesspolicy.xml:

代码
<? xml version="1.0" encoding="utf-8" ?>   
< access-policy >   
  
< cross-domain-access >   
    
< policy >   
      
< allow-from  http-request-headers ="SOAPAction" >   
        
< domain  uri ="*" />   
      
</ allow-from >   
      
< grant-to >   
        
< resource  path ="/"  include-subpaths ="true" />   
      
</ grant-to >   
    
</ policy >   
  
</ cross-domain-access >   
</ access-policy >

 如果您的配置和代码书写正确,浏览一下WCF服务会发现下图,说明服务已经正确host到VS的轻量级IIS上了

  

三,Silverlight跨域访问WCF的Duplex服务

先建立一个单独的project,silverlight app,当然还是需要先引用WCF服务的: 

  

新建一个SL文件,Chat.xaml代码,包括了一个发送消息窗口和一个显示聊天信息的窗口,这个聊天的窗口从普通HTML街面上接收两个参数,即user和partner互为聊天对象。

代码
< UserControl  x:Class ="ChatSL.Chat"   
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation%22  
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml%22  
    xmlns:d
="http://schemas.microsoft.com/expression/blend/2008%22  
    xmlns:mc="
http://schemas.openxmlformats.org/markup-compatibility/2006%22  
    mc:Ignorable
="d"   
    d:DesignHeight
="510"  d:DesignWidth ="514" >   

    
< Grid  x:Name ="LayoutRoot"  Background ="White"  Height ="479"  Width ="485" >   
        
< TextBox  Height ="87"  HorizontalAlignment ="Left"  Margin ="12,347,0,0"  Name ="txtMessage"  VerticalAlignment ="Top"  Width ="335"   />   
        
< Button  Content ="发送"  Height ="29"  HorizontalAlignment ="Right"  Margin ="0,440,138,0"  Name ="btnSend"  VerticalAlignment ="Top"  Width ="61" />   
        
< ListBox  Height ="317"  HorizontalAlignment ="Left"  ItemsSource =" {Binding MessageInfo,Mode=OneWay} "  Name ="listMsgs"  VerticalAlignment ="Top"  Width ="335"  Margin ="12,12,0,0" >   
            
< ListBox.ItemTemplate >   
                
< DataTemplate >   
                    
< StackPanel  Orientation ="Horizontal" >   
                        
< TextBlock  Text =" {Binding SendTime,StringFormat='HH:mm:ss'} "    ></ TextBlock >   
                        
< TextBlock  Text ="    "   ></ TextBlock >   
                        
< TextBlock  Text =" {Binding Sender} "  Width ="60" ></ TextBlock >   
                        
< TextBlock  Text ="  :  "   ></ TextBlock >   
                        
< TextBlock  Text =" {Binding Message} "  FontSize ="12"  FontFamily ="Verdana"  Foreground ="Chocolate" ></ TextBlock >   
                    
</ StackPanel >   
                
</ DataTemplate >   
            
</ ListBox.ItemTemplate >   
        
</ ListBox >   
        
< Image  Height ="31"  HorizontalAlignment ="Left"  Source ="Images/online.jpg"  Margin ="351,12,0,0"  Name ="image1"  Stretch ="Fill"  VerticalAlignment ="Top"  Width ="32"   />   
        
< Button  Content ="关闭"  Height ="29"  HorizontalAlignment ="Left"  Margin ="219,440,0,0"  Name ="btnClose"  VerticalAlignment ="Top"  Width ="61"   />   
        
< TextBox  Height ="23"  HorizontalAlignment ="Left"  Margin ="389,17,0,0"  Name ="txtPartner"  VerticalAlignment ="Top"  Width ="83"   />   
        
< Image  Height ="28"  HorizontalAlignment ="Left"  Margin ="352,347,0,0"  Name ="image2"  Source ="Images/online.jpg"  Stretch ="Fill"  VerticalAlignment ="Top"  Width ="31"   />   
        
< TextBox  Height ="23"  HorizontalAlignment ="Left"  Margin ="389,349,0,0"  Name ="txtMe"  VerticalAlignment ="Top"  Width ="83"   />   
    
</ Grid >   
</ UserControl >  

 

后台代码中需要从HTML中接收聊天对象:

代码
namespace  ChatSL  
{  
    
public   partial   class  Chat : UserControl  
    {   
        
string  user;  
        
string  partner;  

        EndpointAddress address ;  
        PollingDuplexHttpBinding binding;  
        ChatService.ChatServiceClient proxy;  

        
public  Chat()  
        {  
            InitializeComponent(); 

            
this .Loaded += new  RoutedEventHandler(Chat_Loaded);  
            
this .txtMessage.KeyDown  +=   new  KeyEventHandler(KeyDownProcess);  
            
this .btnSend.Click  +=   new  RoutedEventHandler(btnSend_Click);  
            
this .btnClose.Click  +=   new  RoutedEventHandler(btnClose_Click);  
        }  

        
void  Chat_Loaded( object  sender,RoutedEventArgs e)  
        {  
            HtmlElement element;  
            element 
=  HtmlPage.Document.GetElementById( " lbluser " );  
            
this .txtMe.Text  =  element.GetAttribute( " innerText " );  
            element 
=  HtmlPage.Document.GetElementById( " lblpartner " );  
            
this .txtPartner.Text  =  element.GetAttribute( " innerText " );  

            user 
=   this .txtMe.Text;  
            partner 
=   this .txtPartner.Text;  

            LogIn();  
        }  
//向服务器示意上线 
        
private   void  LogIn()  
        {  
            address 
=   new  EndpointAddress( " http://localhost:32662/ChatService.svc%22); 
            binding  =   new  PollingDuplexHttpBinding(PollingDuplexMode.MultipleMessagesPerPoll);  
            proxy 
=   new  ChatService.ChatServiceClient(binding,address);  
            proxy.ReceiveMessagesReceived
+= new  EventHandler < ChatService.ReceiveMessagesReceivedEventArgs > (proxy_ReceiveMessagesReceived);  
            proxy.LoginChatAsync(user, partner);  
        }  

        
#region  绑定数据 
        
void  proxy_ReceiveMessagesReceived( object  sender,ChatService.ReceiveMessagesReceivedEventArgs e)  
        {  
            
this .listMsgs.ItemsSource  =  e.listMessages;  
        }  

        
private   void  SetDataSource()  
        {  
             
        }  
        
#endregion    

        
#region  键盘事件 
        
protected   void  KeyDownProcess( object  sender, KeyEventArgs e)  
        {  
            
if  (e.Key  ==  Key.Enter)  
            {  
                SendMessage();  
            }  
        }  
        
#endregion  

        
#region   发送信息 
        
private   void  btnSend_Click( object  sender, RoutedEventArgs e)  
        {  
            SendMessage();  
        }  
        
private   void  SendMessage()  
        {  
            
if  ( this .txtMessage.Text  ==   "" )  
            {  
                MessageBox.Show(
" 请输入信息! " );  
                
return ;  
            }  

            ChatService.MessageInfo message 
=   new  ChatService.MessageInfo();  
            message.ID 
=  Guid.NewGuid().ToString();  
            message.Receipt 
=   0 ;  
            message.ReceiveMode 
=   " user " ;  
            message.ReceiveOrgan 
=   "" ;  
            message.ReceiveUser 
=   this .txtPartner.Text;  
            message.Message 
=   this .txtMessage.Text;  
            message.Sender 
=   this .txtMe.Text;  
            message.SendTime 
=  DateTime.Now;  
            message.Source 
=   " web " ;  
            message.State 
=   0 ;  
            message.Title 
=   this .txtMessage.Text;  

            proxy 
=   new  ChatService.ChatServiceClient(binding, address);  
            proxy.SendMessageCompleted  
+=   new  EventHandler < ChatService.SendMessageCompletedEventArgs > (SendMessageComleted);  
            proxy.SendMessageAsync(message);  

            
this .txtMessage.Text  =   "" ;  
        }  
        
void  SendMessageComleted( object  sender, ChatService.SendMessageCompletedEventArgs e)  
        {  
            
if  (e.Error  ==   null )  
            {  
                
// MessageBox.Show(e.Result.ToString()); 
            }  
        }  
        
#endregion    

        
#region  关闭窗口 
        
private   void  btnClose_Click( object  sender, EventArgs e)  
        {  

        }  
        
#endregion    
    }  

效果图: 




     本文转自wengyuli 51CTO博客,原文链接:http://blog.51cto.com/wengyuli/587835,如需转载请自行联系原作者



 

相关文章
|
JavaScript
wcf双工通讯
首先说一个服务引用不成功的解决办法: 需要把服务端配置文件中的Binding换成: 或: 下面是一个wcf的简单示例: 服务契约代码: using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WcfService { // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IUser”。
948 0