您的位置:首页 > 其它

wcf 双工通讯

2015-01-14 15:53 183 查看
介绍 wcf 单工通讯

但在双向操作模式中,不但客户端可以向服务器发送请求,服务器也可以主动向客户端广播消息(也就是回调客户端中的方法)。在WCF中,不是所有的绑定都可 以实现双向操作模式的,比如http协议,它本身就是基于请求-回复的传输模式,所以本质上是实现不了双向操作的。但WCF提供了 WSDualHttpBinding协议让我们在http上实现了双向操作。其实WSDualHttpBinding并没有违反http单向传输的本质, 它实际上是创建两个了通道,一个用于客户端向服务器请求,一个用于服务器向客户端广播,间接实现了双向操作。但《WCF服务编程》书上有 说,WSDualHttpBinding无法穿越客户端与服务器的重重障碍,所以不赞成使用WSDualHttpBinding来实现双向操作。

第一步

新建一个windows应用程序,取名Host



第2步:建立接口,IMessageService

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

namespace Host
{
//和单向操作相比,我们会发现服务契约上多了一行代码:[ServiceContract(CallbackContract = typeof(ICallBack))]
[ServiceContract(CallbackContract = typeof(ICallBack))]
public interface IMessageService
{
[OperationContract]
void RegisterMes();

[OperationContract]
int SendToAll(string name, string msg);
/// <summary>
/// 文件上传
/// </summary>
/// <param name="data">字节数组</param>
/// <param name="suffix">文件后缀名</param>
/// <returns></returns>
[OperationContract]
int SentFile(byte[] data, string suffix);
}

public interface ICallBack
{
[OperationContract(IsOneWay = true)]
void SendMessage(string name, string msg);

[OperationContract(IsOneWay = true)]
void Show();
}
}


这时候引用 wcf组件



第 3步 建立 实现 MessageService

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace Host
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class MessageService : IMessageService, IDisposable
{
public static List<ICallBack> CallBackList
{
get;
set;
}

public MessageService()
{
CallBackList = new List<ICallBack>();
}

public void RegisterMes()
{
var callback = OperationContext.Current.GetCallbackChannel<ICallBack>();
string sessionid = OperationContext.Current.SessionId;

Form1.fm1.listLine.Items.Add(sessionid);//服务端显示客户端的SessionId

//OperationContext.Current.Channel.Closing +=
//    delegate
//    {
//        lock (CallBackList)
//        {
//            CallBackList.Remove(callback);
//        }
//    };

OperationContext.Current.Channel.Closing += new EventHandler(Channel_Closing);
CallBackList.Add(callback);
}

void Channel_Closing(object sender, EventArgs e)
{
lock (CallBackList)
{
CallBackList.Remove((ICallBack)sender);
}
}

public void Dispose()
{
CallBackList.Clear();
}

public int SendToAll(string name, string msg)
{
var list = Host.MessageService.CallBackList;
if (list == null || list.Count == 0)
return 0;
lock (list)
{
Task.Factory.StartNew(new Action(() =>
{
foreach (var client in list)
{
client.SendMessage(name, msg);

}
}));
}
return 1;
}

/// <summary>
/// 文件上传
/// </summary>
/// <param name="data">字节数组</param>
/// <param name="suffix">文件后缀名</param>
/// <returns></returns>
public int SentFile(byte[] data, string suffix)
{
DateTime dt = DateTime.Now;
string filename = string.Format("{0:yyyyMMddHHmmssffff}", dt);
File.WriteAllBytes(filename+suffix, data);
return 0;
}
}
}


第4步 配置 Appconfig

右键Appconfig 点击编辑wcf配置 进入元素浏览器(我把这个理解成 可视化配置),这样免去程序员手敲代码的麻烦。

上几张图







看到3张图没,新建了一个服务,添加了2个终结点

app.config生产代码

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true"/>
</system.web>
<system.serviceModel>
<services>
<service name="Host.MessageService">
<endpoint address="" binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IMessageService" contract="Host.IMessageService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
<host>
<baseAddresses>
<add baseAddress="net.tcp://192.168.2.23:9999/Host/"/>
<add baseAddress="http://192.168.2.23:9998/Host"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IMessageService" maxBufferSize="1024000000" maxReceivedMessageSize="1024000000" sendTimeout="00:00:30" transferMode="Buffered">

<security mode="None">

<transport clientCredentialType="Windows" />

<message clientCredentialType="Windows" />

</security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>


这个说下wcf 之 abc

Address: 每一个WCF的Service都有一个唯一的地址。这个地址给出了Service的地址和传输协议(Transport Protocol)

Binding:通信(Communication)的方式很多,同步的request/reply模式,非同步的fire-and- forget模式。消息可以单向或者双向的发送接收,可以立即发送或者把它放入到某一个队列中再处理。所供选择的传输协议也有Http, Tcp,P2P,IPC等。当要考虑Client/Server如何进行通讯的时候,除了考虑以上提到的几点之外,还有其它很多需要考虑的因素,如安全, 性能等。因此,简单来说,Binding只不过是微软提供的一组考虑比较周全、常用的封装好的通信方式。

Contract:Contract描述了Service能提供的各种服务。Contract有四种,包括Service Contract, Data Contract, Fault Contract和Message Contract

第5步 建立客户端 取名 Client 添加 System.ServiceModel引用,添加wcf引用

这里强调一下wcf引用 怎么加入项目里,打开wcf服务端生成的bin文件,运行Host.exe文件



这时候 http://192.168.2.23:9998/Host 就可以访问了,取名叫WcfSvc



引用wcf服务 的时候 你发现 客户端 app.config配置文件已经生成了

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IMessageService">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://192.168.2.23:9999/Host/" binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IMessageService" contract="WcfSvc.IMessageService"
name="NetTcpBinding_IMessageService" />
</client>
</system.serviceModel>
</configuration>


在Client工程里新建 MyCallBack 类回调 服务端方法

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

namespace Client
{
public class MyCallBack : WcfSvc.IMessageServiceCallback
{

public long count = 0;
public void SendMessage(string name, string msg)
{
System.Threading.Interlocked.Increment(ref count);

Form1.f.listMessage.Items.Add(string.Format("{0}:{1}:{2}", name, msg, count));
Form1.f.txtreceiving.Invoke(new Action(() =>
{
Form1.f.txtreceiving.Text = count.ToString();
}));

}

public void Show()
{
Form1.f.listMessage.Invoke(new Action(() =>
{
Form1.f.listMessage.Items.Add("show方法调用了");
}));
}

}
}


这时候双工通讯搭建完毕,再来设计UI页面



这里设置ui控件有个技巧:默认控件是 私有属性,我把上线 这边文本框 改成 public,让他接受外面访问。



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Windows.Forms;

namespace Host
{
public partial class Form1 : Form
{
public static Form1 fm1;
private ServiceHost _host = null;

public Form1()
{
InitializeComponent();

//构造函数初始化加载
fm1 =this;
CheckForIllegalCrossThreadCalls = false;
BindHost();
}

private void btnStart_Click(object sender, EventArgs e)
{
BindHost();
}

void BindHost()
{
try
{
_host = new ServiceHost(typeof(Host.MessageService));
_host.Open();
lbService.Text = string.Format("服务开启:{0}", DateTime.Now.ToString());
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}

private void btnsend_Click(object sender, EventArgs e)
{//发送内容
#region 备用
if (fm1 != null)
{
lock (MessageService.CallBackList)
{
foreach (ICallBack callback in MessageService.CallBackList)
{
callback.SendMessage("服务器发送: ", txtMsg.Text);
}

}
}
#endregion

}

/// <summary>
/// (公用的)信息显示
/// </summary>
/// <param name="msg">消息内容</param>
private void ShowMessage(string msg)
{
this.lbMessage.Text = msg;
this.lbMessage.Visible = true;
}
}
}


画客户端页面



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.Threading.Tasks;
using System.IO;

namespace Client
{
public partial class Form1 : Form
{
public static Form1 f;

public Form1()
{
InitializeComponent();

f = this;
CheckForIllegalCrossThreadCalls = false;

}
WcfSvc.MessageServiceClient svc;

private void Form1_Load(object sender, EventArgs e)
{//页面初始化
//Online();

var client = new MyCallBack();
var ctx = new InstanceContext(client);
svc = new WcfSvc.MessageServiceClient(ctx);
svc.RegisterMes();
}

private void button1_Click(object sender, EventArgs e)
{//停止
isRun = false;
}

void ShowMessage(string msg)
{
this.lbError.Text = msg;
this.lbError.Visible = true;
}

Task t;
int sleeptime = 100;
bool isRun = true;

private void btnSend_Click(object sender, EventArgs e)
{//发送
isRun = true;
sleeptime = int.Parse(txtNum.Text);
t = Task.Factory.StartNew(delegate
{
while (true)
{
if (isRun)
{
svc.SendToAll(txtUserName.Text, txtSendMessage.Text);
System.Threading.Thread.Sleep(sleeptime);
}
else
{
break;
}

}
});
}

private void btnclear_Click(object sender, EventArgs e)
{//清屏
this.listMessage.Items.Clear();
}

private void btnUpload_Click(object sender, EventArgs e)
{//文件上传
using (OpenFileDialog ofd = new OpenFileDialog())
{
if (ofd.ShowDialog() == DialogResult.OK)
{
//FileStream fs = new FileStream(ofd.FileName, FileMode.Open, FileAccess.Read);
byte[] data = File.ReadAllBytes(ofd.FileName);
int r = svc.SentFile(data, Path.GetExtension(ofd.FileName));
if (r == 0)
{
MessageBox.Show("发送完毕");
}
}
}
}

}
}


上几张双工效果图,服务实现 “有图有真相”。

服务端 向客户端发送请求。



客户端向客户端发送请求



分享一下我的源码,有什么建议的朋友可以留言给我,相互讨论,相互学习。

大家可以把 双工通讯 理解成 “礼上往来”的通讯。

源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: