在写上一篇关于WCF的几种HelloWorld的写法时,我纯粹是学习语法,想对WCF有个最初的认识。进过一个多月项目的接触,似乎又明白了一些。项目中运用WCF框架实现总公司对子公司的服务器以及数据库的扫描监控(监控子公司是否正常,子公司定期给总公司发送受监控的数据),然后总公司对子公司的数据进行一些记录处理,最后再反馈到子公司(总公司回发数据给子公司)。归结起来就是服务端和客户端相互定期通信,也就是WCF中双程操作。这里主要介绍心跳技术。
每当客户端给服务端发送消息时,服务端在接收到消息后,给客户端反馈一条消息(称为回调操作),而这个反馈消息不需要考虑客户端是否收到这个反馈消息。
本文将公司的项目抽象成一个简单例子,例子展示如何实现服务端与客户端的交互。
(可以点此下载先看效果)
一、服务端
首先写服务契约
[ServiceContract(CallbackContract = typeof(IWCFServiceCallBack))]
public interface IWCFService
{
//操作契约
[OperationContract]
string OperationInfo(string name);
}
注:CallbackContract= typeof(IWCFServiceCallBack)表示服务端接收到客户端的消息时给客户的响应(即回调操作),IWCFServiceCallBack即为回调契约,定义如下:
public interface IWCFServiceCallBack
{
//操作契约
[OperationContract(IsOneWay = true)]
void FeedBack(string info);
}
注:1.回调服务契约,由于回调方法在客户端执行,因此无须添加 ServiceContract特性。2.对于成功后回调反馈操作,服务器无须获取其返回信息,因此添加 IsOneWay=true 特性。
接着,服务端实现契约:
public class WCFService : IWCFService
{
//获取当前操作客户端对象实例
IWCFServiceCallBack callback
= OperationContext.Current.GetCallbackChannel<IWCFServiceCallBack>();
//实现接口定义的方法(在服务端运行)
public string OperationInfo(string clientPCName)
{
Console.WriteLine("收到客户端的连接消息消息:{0} {1}", DateTime.Now, clientPCName);
callback.FeedBack("进过分析后的用户名" + clientPCName.Split('-')[0]);
//这边可以返回任何客户端需要的信息//如果客户端不需要信息可以将此函数设为void返回类型
return "客户端连接正常";
}
}
二、宿主,主要工作就是开启服务
配置文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="WCFService.WCFServiceBehavior"
name="Service0413.WCFService">
<endpoint
address="net.tcp://localhost:9000/WCFService"
binding="netTcpBinding"
contract="Service0413.IWCFService">
</endpoint>
<host>
<baseAddresses>
<!--这里的baseAddress是客户端服务引用的URI-->
<add baseAddress="http://localhost:9090/"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WCFService.WCFServiceBehavior">
<!--为了可以通过URI引用服务,这里需设置httpGetEnabled为true-->
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
寄宿宿主:
//服务宿主就干一件事:打开服务
class Host
{
static void Main(string[] args)
{
//回调服务WCFServiceCallBack
ServiceHost host = new ServiceHost(typeof(Service0413.WCFService));
try
{
//判断是否以及打开连接,如果尚未打开,就打开侦听端口
if (host.State != CommunicationState.Opening)
host.Open();
//显示运行状态
Console.WriteLine("ServiceHost is runing! and state is {0}", host.State);
Console.Read();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
host.Close();
}
}
}
三、客户端:
首先要实现回调的契约接口(通俗点将就是服务端接收到客户端消息后给客户端发聩信息,在客户端执行):
/// <summary>要实现回调的契约接口
/// </summary>
public class WCFCallback : Service2012.IWCFServiceCallback
{
public void FeedBack(string info)
{
Console.WriteLine("服务器已经收到您的消息!消息内容:" + info);
}
}
最后是入口函数部分(由于源代码上传的较早,这个在下载源代码里,稍有不同,可以自行替换此类)。
public class Client
{
//获得客户端的计算机名(实际可以扩展到获取客户端的一切想要的数据)
private static string clientName = Environment.MachineName;
private static Service2012.IWCFServiceCallback callBack = new WCFCallback();
private static InstanceContext context = new InstanceContext(callBack);
private static Service2012.WCFServiceClient WCFServiceCallBackClientProxy =
new WCFServiceClient(context, "NetTcpBinding_IWCFService");
static void Main(string[] args)
{
System.Timers.Timer timer = new System.Timers.Timer(2000);
//绑定定期自动执行的操作
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
timer.Enabled = true;
string s=Console.ReadLine();
Console.WriteLine(s);
}
private static void OnTimedEvent(object source, ElapsedEventArgs e)
{
string operateInfo = WCFServiceCallBackClientProxy.OperationInfo(clientName);
Console.WriteLine(operateInfo);
}
}
至此完成,本例没有进行异常处理,请注意先运行服务端,再运行客户端。
学习一个多月也遇到了一些问题,有些值得分享:
1.当在类库中添加了服务引用时,只会自动生成类库中的配置文件,其他程序引用该DLL时注意将配置文件的信息也添加进去。
2.WCF的测试可以先添加断点,开启服务,通过调试--附加到进程进行测试。
3.对于服务端的参数是List时,在客户端有两种情况,当是自己实现手动实现通道时客户端参数还是List的,而当客户端是通过引用自动生成时,参数就相应的变成了数组,此时只要通过ToArray()方法将其改为数组即可(当时遇到时,虽然看到了里面参数是数组,但我以为是哪里弄错了,重写了个例子发现还是一样,一问项目组的前辈,原来这是正常的情况,具体原因尚不知道,暂且记录,还望了解者指点)。
后记:写的还比较简单,自我感觉只是授之以鱼,而没有授之以渔,本人刚学,认识有限,很多理解的可能还只停留在鱼的地步。