基于WCF的即时通讯软件的设计与实现.docx
《基于WCF的即时通讯软件的设计与实现.docx》由会员分享,可在线阅读,更多相关《基于WCF的即时通讯软件的设计与实现.docx(57页珍藏版)》请在冰豆网上搜索。
基于WCF的即时通讯软件的设计与实现
基于WCF的即时通讯软件的设计与实现
摘要:
介绍了Microsoft用于构建分布式面向服务架构系统的新一代框架WCF的体系结构与技术要素,并通过开发一套即时通信软件展现了基于WCF构架开发分布式应用程序的编程步骤与技巧。
关键词:
WCF;SOA;分布式;即时通信
一、前言
自从在微软提出.NET战略以来,就针对建立企业级的分布式应用先后推出了一系列产品和技术,包括:
ASP.NETWeb服务、.NETRemoting、MessageQueuing以及EnterpriseService等。
这些技术为基于微软技术的软件研发人员开发分布式应用提供了很大的便利,同时也各自存在着一些不足。
WCF(WindowsCommunicationFoundation)作为微软基于SOA所推出的.NET平台下的新一代框架产品集成了现有技术的优点,代表了未来软件架构设计与开发的发展方向。
因此,掌握并能在未来应用中合理运用WCF技术,对于程序员特别是基于微软技术开发的程序员而言是十分必要的。
基于此,文章通过介绍一套即时通信软件的具体开发过程来展现基于WCF技术的分布式软件研发的基本步骤与高级技巧。
二、WCF概述
对于一个好的分布式系统来讲,设计时应当考虑到异构性、开放性、安全性、可扩展性、故障处理、并发性以及透明性等问题。
基于SOAP的WebService可以实现异构环境的互操作性,保证了跨平台的通信。
利用WSE(WebServiceEnhancements)可以为ASMX提供安全性的保证。
.NETRemoting具有丰富的扩展功能,可以创建定制的信道、格式化器和代理程序。
EnterpriseService(COM+)提供了对事务的支持,其中还包括分布式事务,可实现故障的恢复。
MSMQ可以支持异步调用、脱机连接、断点连接等功能,利用消息队列支持应用程序之间的消息传递。
从功能角度来看,WCF整合了ASMX、.NetRemoting、EnterpriseService、WSE以及MSMQ等现有技术的优点,它提供了一种构建安全可靠的分布式面向服务系统的统一的框架模型,使软件研发人员在开发分布式应用时变得更加轻松。
1.面向服务
既然WCF是一套面向服务的框架,服务自然便是WCF中最为重要的概念。
服务是指暴露在外的一系列功能的集合,面向服务则是指一套构建“面向服务程序”的抽象原则以及最优方法。
对于业务逻辑的理解,传统的编程方式认为应将业务逻辑封装为对象,该对象提供了与业务相关的一些功能;而基于WCF的程序设计却更多的是考虑如何提供服务以及消费服务。
与面向组件服务程序类似,基于SOA的应用程序将服务封装到了单个逻辑程序当中,如图1所示。
图1封装服务的SOA应用程序逻辑图
2.WCF体系结构
WCF拥有一个非常灵活的分层体系结构,分布式应用程序可以使用高级API或者低级API编写。
高级API或者服务层可以用于调用方法和事件。
服务层把这些高级的抽象代码转换为消息,以使用低级API上的信道和端口。
图2中显示了WCF应用程序的各个层。
图2WCF体系结构图
WCF提供了对可靠性、事务性、并发管理、安全性以及实例激活等技术的有力支持,而这些支持均依赖于如图3所示的WCF构架。
在客户端,分布式应用通过一个代理来转发对宿主端所提供服务的调用,而代理拥有和服务相同的操作接口,另外还有一些附加的代理管理方法。
这也就意味着客户端从来不会直接调用服务,即便这个服务就在本机的内存中。
当客户端代理接收到来自客户端的调用请求后,它将消息通过信道链向下传递。
每个信道都会执行相应的消息的调用前处理,例如对消息的编码、提供可靠的会话、对消息进行加密等。
客户端的最后一个信道则是传输信道,根据配置的传输方式发送消息给宿主。
在宿主端,消息同样通过信道链进行传输。
与客户端信道相对应,宿主端信道也会对消息执行相应的宿主端的调用前处理,例如对消息的解码、提供会话管理、对消息进行解密等。
宿主端的最后一个信道则负责将消息发送给消息分发器(Dispatcher),由分发器负责调用服务的实例。
图3WCF构架示意图
3.WCF的基本技术要素
作为基于SOA的一个框架产品,WCF实际上是构建了一个在互联系统中实现各个应用程序之间通信的分布式框架。
它使得系统构架师与开发人员在构建分布式系统时,能将更多的精力投入到与系统的业务逻辑本身的设计上来,而无需过多的考虑底层通信的实现及相关问题。
WCF最核心的部分是能够快捷的创建一个服务,一个WCF服务端框架由宿主、端点以及服务类三部分所组成,如图4所示。
图4WCF服务框架
宿主(Host),即承载WCFService运行的环境。
可用的宿主环境包括:
(1)自承载方式:
在控制台应用程序与基于WinForm的应用程序中都可以使用这种方式;
(2)系统服务方式:
服务可以随着操作系统的启动而自动启动;
(3)IIS方式:
与WebServices的部署方式类似,由请求消息来激活服务,但只支持HTTP方式的绑定;
(4)WAS(WindowsProcessActivationService)方式:
这个宿主是IIS7的一部分,只有WindowsVista和WindowsServer2008提供默认支持,它支持几乎所有的通讯协议并提供了应用程序池、循环回收、空闲管理、身份管理、隔离等强大的功能。
服务类(ServiceClass)是指一个标记了一些WCF特有的属性的类,它包含了对服务的业务逻辑的具体实现。
端点(Endpoints)是WCF实现通信的核心要素,客户端和服务端都通过端点来交换消息,WCF允许我们为服务添加多个绑定和端点。
端点由地址(Address)、绑定(Binding)以及契约(Contract)三部分组成,如图5所示。
在WCF中,类ServiceEndpoint代表了一个Endpoint,在类中包含的EndpointAddress,Binding,ContractDescription类型分别对应端点中的地址、绑定以及契约。
图5端点构成图
地址:
每个服务都会关联到一个唯一的地址,因此地址定位和唯一标志了一个端点,其主要提供了两个重要信息:
服务位置以及传送协议。
在WCF中,地址由System.ServiceModel.EndpointAddress对象来表示,其包括URI、Identity、Headers三个要素。
绑定:
绑定提供了一种可设置的方式来选择传输协议、消息编码、通讯模式、可靠性、安全性、事务传播以及交互方式等。
例如在传输协议上可以选择HTTP/HTTPS、TCP、P2P、IPC甚至是MSMQ等方式。
消息编码上可以选择使用纯文本方式来确保互操作能力,或者选择二进制编码来优化性能,或者使用MTOM来提高负载能力,甚至是自定义编码方式。
WCF中提供了BasicHttpBinding、NetTcpBinding、NetPeerTcpBinding、NetNamedPipeBinding、WSHttpBinding、WSFederationHttpBinding、WSDualHttpBinding、NetMsmqBinding以及MsmqIntegrationBinding九种标准类型的绑定。
契约:
契约是用来描述服务功能的一种平台中立的标准方式,WCF所有服务都需要实现一个或多个契约。
WCF定义了四种类型的契约:
(1)服务契约(ServiceContracts):
定义了客户端可以使用哪些服务操作。
(2)数据契约(DataContracts):
定义了服务传输的数据类型。
WCF定义了一些隐式数据契约,比如int、string等,但更多时候需要使用DataContractAttribute显式定义那些自定义类型数据的数据契约。
(3)错误处理契约(FaultContracts):
定义了服务引发的错误信息,以及如何将这些异常传递给客户端。
(4)消息契约(MessageContracts):
允许直接操控服务的消息内容和格式。
一般情况下,应当用接口来定义服务契约,尽管我们也可以使用类。
将服务契约定义为接口基于如下几点优点:
(1)便于契约的继承,不同根的类型可以自由实现相同的契约;
(2)同一服务类型可以同时实现多个契约;
(3)类似于接口隔离原则,可以随时修改服务类型的实现而不影响其它实现;
(4)便于制定版本升级策略,新、旧版本的服务契约可以同时使用而互不影响。
在WCF中,对于自承载的服务,端点的相关的信息可以有代码实现与配置文件两种定义方式。
而对于IIS承载服务,端点的相关的信息一般定义在虚拟根目录下的Web.Config文件中。
一般来讲,使用配置文件来定义端点相关信息是更为灵活、更为推荐的一种方式,其可以在不修改代码、不重新发布系统的情况下对服务的地址、绑定和契约等参数进行修改(因为修改config类型文件的内容是不需要重新编译和重新部署的)。
在下面的代码中具体说明了如何定义宿主端的端点相关信息。
其中地址为http:
//localhost:
8080/HelloService,契约为WCFServiceHello命名空间下的IHello接口,绑定采用的是WSHttpBinding方式。
值得注意的是,代码中的HelloService为相对地址,http:
//localhost:
8080/提供的是基址,当然去掉基址直接将address设为http:
//localhost:
8080/HelloService也是可以的。
代码中还添加了名为MyServiceTypeBehaviors的行为配置,其将serviceMetadata节中的httpGetEnabled属性设为了true,目的是为了自动透过HTTP-GET发布服务的元数据。
WCF提供的另外一种发布元数据的方式是使用专门的MEX端点。
behaviorConfiguration="MyServiceTypeBehaviors">
binding="wsHttpBinding"
address="HelloService"/>
//localhost:
8080/"/>
在接下来的宿主代码中,只需要简单的创建ServiceHost类型的对象,并利用其实例方法Open启动服务应用程序即可,简要代码如下所示:
using(ServiceHosthost=newServiceHost(typeof(WCFServiceHello.HelloWorld)))
{
Console.WriteLine("HelloServicehasbeenstarted...");
host.Open();
Console.ReadKey();
}
客户端和服务之间通过消息交换来完成方法调用和数据传递,而在WCF中定义了3种消息交换模式,如图6所示。
(1)OneWay:
这种消息交换模式在调用方法后会立即返回而不需要等待服务端的消息返回。
(2)Request/Reply:
这种消息交换模式属于同步调用。
在调用服务方法后需要等待服务端的消息返回。
(3)Duplex:
这种消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。
图6WCF中的3种消息交换模式
在设置完宿主端端点之后,同样也必须为分布式应用程序定义客户端的端点,而且只有当客户端的端点与宿主端的某个端点相互匹配时,客户端的请求才能被宿主端所监听到。
如果服务提供了发布元数据,那么利用.NETFramework3.0SDK所提供的SvcUtil.exe工具可以很轻松的自动生成与宿主端对应的客户端代理以及客户端配置文件。
比如,运行宿主端应用程序,然后打开VisualStudio2005命令提示符,键入SvcUtilhttp:
//localhost:
8080,便可以在当前目录下得到客户端代理文件HelloWorld.cs与客户端配置文件output.config。
另外一种简便直观的可视化工具是SDK所附带的SvcConfigEditor.exe(C:
\ProgramFiles\MicrosoftSDKs\Windows\v6.0A\bin目录下,XP系统),使用这个工具可以非常方便地创建或修改宿主端和客户端的配置文件。
生成好客户端代理与配置文件后,在代码中直接使用客户端代理对象即可。
using(HelloClientclient=newHelloClient())
{
client.Hello();
}
Console.ReadKey();
另外一种创建客户端代理的方式是使用ChannelFactory动态的来创建。
虽然WCF提供了这种方式,但是在实际开发中并不推荐使用它,毕竟ChannelFactory直接依赖于契约,而这恰恰违背了SOA中边界隔离的原则。
利用服务器端与客户端之间的Channel来创建客户端代理的代码举例如下:
ServiceEndpointhttpendpoint=
newServiceEndpoint(ContractDescription.GetContract(typeof(IHello)),
newWSHttpBinding(),newEndpointAddress("http:
//localhost:
8080/HelloService"));
using(ChannelFactoryfactory=newChannelFactory(httpendpoint))
{
IHelloservice=factory.CreateChannel();
service.Hello();
}
Console.ReadKey();
三、软件的分析与设计
软件主要的功能是初步实现基于WCF的局域网内的实时通信,以面向服务为指导思想将具体开发过程分为即时通信服务的设计与实现、宿主的设计与实现以及即时通信客户端的设计与实现三部分,使得应用程序具有较好的安全性、并发性、可扩展性以及可维护性。
1.服务的设计
服务的设计包括了通用模块Common.dll的设计、服务契约IChat的定义与实现、客户端的回调接口IChatCallback的定义三个部分。
通用模块Common.dll主要包括对聊天者类型Person的定义,其包括name(聊天者的名称或代号)以及ImageURL(聊天者选择的头像图片的存储路径)两个私有字段及相应的属性访问器。
由于Person类型是自定义数据类型,因此必须加上DataContractAttribute来显式定义它。
Person类的实现核心代码如下:
[DataContract]
publicclassPerson
{
privatestringimageURL;
privatestringname;
publicPerson(stringimageURL,stringname)
{
this.imageURL=imageURL;
this.name=name;
}
[DataMember]
publicstringImageURL
{
get{returnimageURL;}
set
{
imageURL=value;
}
}
[DataMember]
publicstringName
{
get{returnname;}
set
{
name=value;
}
}
}
服务契约IChat的定义中主要包括Join、Say、Whisper以及Leave四个基本方法。
其中Join表示:
进入聊天;Say表示向所有用户广播消息;Whisper表示对指定的用户发送消息;Leave表示离开聊天。
需添加ServiceContract属性将IChat接口标记为服务契约,由于要实现客户端与服务端双向通信的功能,因此还必须设置CallbackContract参数,其参数IChatCallback为Duplex模式下的客户端回调类型。
对于IChat接口中的方法来讲,必须标记OperationContract属性。
其中IsOneWay指示在该消息交换模式下调用方法后是否会立即返回而不需要等待服务端的消息返回;IsInitiating指示服务方法是否启动一个Session;IsTerminating指示服务方法调用完成是否结束Session。
IChat的定义代码如下:
[ServiceContract(SessionMode=SessionMode.Required,CallbackContract=typeof(IChatCallback))]
interfaceIChat
{
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=false)]
voidSay(stringmsg);
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=false)]
voidWhisper(stringto,stringmsg);
[OperationContract(IsOneWay=false,IsInitiating=true,IsTerminating=false)]
Person[]Join(Personname);
[OperationContract(IsOneWay=true,IsInitiating=false,IsTerminating=true)]
voidLeave();
}
客户端的回调接口IChatCallback的定义包括了分别对应Join、Say、Whisper以及Leave的UserEnter、Receive、ReceiveWhisper以及UserLeave四个基本方法。
UserEnter表示当有新聊天用户加入时所有聊天用户接收到一个相应的通知;Receive表示接收用户广播的消息;ReceiveWhisper表示接收相关用户发来的消息;UserLeave表示当有聊天用户离开时所有聊天用户接收一个相应的通知。
注意在接口定义中,每个服务方法的消息转换模式均设置为One-Way。
此外,回调接口是被本地调用,因此不需要定义[ServiceContract]属性。
回调接口IChatCallback的定义的代码如下:
interfaceIChatCallback
{
[OperationContract(IsOneWay=true)]
voidReceive(Personsender,stringmessage);
[OperationContract(IsOneWay=true)]
voidReceiveWhisper(Personsender,stringmessage);
[OperationContract(IsOneWay=true)]
voidUserEnter(Personperson);
[OperationContract(IsOneWay=true)]
voidUserLeave(Personperson);
}
定义了服务契约以及客户端回调接口之后,仍需要定义一些消息类型用作客户端与服务之间的交互,具体代码及解释如下:
//定义说明消息类型的枚举类型MessageType
publicenumMessageType{Receive,UserEnter,UserLeave,ReceiveWhisper};
//ChatEventArgs继承至EventArgs,作为传递的消息参数,
//其包括消息的类型msgType、消息发送者person以及消息的主体内容message
publicclassChatEventArgs:
EventArgs
{
publicMessageTypemsgType;
publicPersonperson;
publicstringmessage;
}
最后是对服务契约中IChat接口的实现类ChatService的设计。
值得注意的是ChatService继承了IChat,不再需要像服务契约IChat一样添加ServiceContract属性。
另外设置了其ServiceBehavior属性的InstanceContextMode参数来决定实例化方式(PerSession方式或者PerCall方式)。
PerSession表明服务对象的生命周期存活于一个会话期间,在同一个会话期间对于服务的不同操作的调用都会施加到同一个客户端代理类型的对象上;PerCall则表示服务对象是在方法被调用时创建,结束后即被销毁。
ServiceBehavior.ConcurrencyMode参数的设置则用于控制具体服务对象的并发行为,其包括三种行为:
Single:
为默认方式。
服务实例是单线程的,不接受重入调用(reentrantcalls)。
也就是说对于同一个服务实例的多个调用必须排队,直到上一次调用完成后才能继续。
Reentrant:
和Single一样,也是单线程的,但能接受重入调用,至于针对同一服务对象的多个调用仍然需要排队。
因为在Single模式下,当方法调用另外一个服务时,方法会阻塞,直到所调用的服务完成。
如果方法不能重入,那么调用方会因无法接受所调用服务的返回消息,无法解除阻塞状态而陷入死锁。
Reentrant模式解决了这种不足。
Multiple:
允许多个客户端同时调用服务方法。
不再有锁的问题,不再提供同步保障。
因此使用该模式时,必须自行提供多线程同步机制(比如在本例中给static类型的syncObj对象加锁)来保证数据成员的读写安全。
类ChatService的实现代码及解析如下:
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession,
ConcurrencyMode=ConcurrencyMode.Multiple)]
publicclass