一,Duplex简介
上一个随笔记录了SL利用Timer定时去WCF上取数据再绑定到界面上的问题,今天尝试用了WCF的Duplex双工通信来做这个事情,也以这个例子来说明WCF中Duplex的使用。
双工通信的原理很简单,我们平时用的是客户端调用服务端的方法来获取数据,而Duplex是将客户端也当作了服务器,客户端上的方法也可以被调用,以聊天功能为例子,用户A连接到服务器后,之前的做法是客户端定时取数据,而Duplex是在服务端定时检测数据变化,如果发现了发送给A的信息,那么立即会调用客户端的方法来推送信息到A。
二,建立Duplex模式的WCF服务
这里以一个简单的聊天功能来说明,WCF提供了三个方法,连接到服务器方法,发送信息方法和接收信息方法。从服务契约上来说分为两个接口,分别是为客户端提供发送信息和开始聊天方法的IChatService接口和服务器调用客户端方法的IChatServiceCallBack接口
IChatService.cs文件
{
[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
{
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...]
}
// 执行简单的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节点:
< 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:
< 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互为聊天对象。
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" 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" >
< 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,0" Name ="image1" Stretch ="Fill" VerticalAlignment ="Top" Width ="32" />
< Button Content ="关闭" Height ="29" HorizontalAlignment ="Left" Margin ="219,0" Name ="btnClose" VerticalAlignment ="Top" Width ="61" />
< TextBox Height ="23" HorizontalAlignment ="Left" Margin ="389,17,0" Name ="txtPartner" VerticalAlignment ="Top" Width ="83" />
< Image Height ="28" HorizontalAlignment ="Left" Margin ="352,0" Name ="image2" Source ="Images/online.jpg" Stretch ="Fill" VerticalAlignment ="Top" Width ="31" />
< TextBox Height ="23" HorizontalAlignment ="Left" Margin ="389,349,0" Name ="txtMe" VerticalAlignment ="Top" Width ="83" />
</ Grid >
</ UserControl >
{
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
}
}