基于WCF的即時通訊軟件的設(shè)計與開發(fā)_第1頁
基于WCF的即時通訊軟件的設(shè)計與開發(fā)_第2頁
基于WCF的即時通訊軟件的設(shè)計與開發(fā)_第3頁
基于WCF的即時通訊軟件的設(shè)計與開發(fā)_第4頁
基于WCF的即時通訊軟件的設(shè)計與開發(fā)_第5頁
已閱讀5頁,還剩29頁未讀 繼續(xù)免費閱讀

付費下載

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

基于WCF的即時通訊軟件的設(shè)計與實現(xiàn)摘要:介紹了Microsoft用于構(gòu)建分布式面向服務(wù)架構(gòu)系統(tǒng)的新一代框架WCF的體系結(jié)構(gòu)與技術(shù)要素,并通過開發(fā)一套即時通信軟件展現(xiàn)了基于WCF構(gòu)架開發(fā)分布式應(yīng)用程序的編程步驟與技巧。關(guān)鍵詞:WCF;SOA;分布式;即時通信一、前言自從在微軟提出.NET戰(zhàn)略以來,就針對建立企業(yè)級的分布式應(yīng)用先后推出了一系列產(chǎn)品和技術(shù),包括:ASP.NETWeb服務(wù)、.NETRemoting、MessageQueuing以及EnterpriseService等。這些技術(shù)為基于微軟技術(shù)的軟件研發(fā)人員開發(fā)分布式應(yīng)用提供了很大的便利,同時也各自存在著一些不足。WCF(WindowsCommunicationFoundation)作為微軟基于SOA所推出的.NET平臺下的新一代框架產(chǎn)品集成了現(xiàn)有技術(shù)的優(yōu)點,代表了未來軟件架構(gòu)設(shè)計與開發(fā)的發(fā)展方向。因此,掌握并能在未來應(yīng)用中合理運用WCF技術(shù),對于程序員特別是基于微軟技術(shù)開發(fā)的程序員而言是十分必要的。基于此,文章通過介紹一套即時通信軟件的具體開發(fā)過程來展現(xiàn)基于WCF技術(shù)的分布式軟件研發(fā)的基本步驟與高級技巧。二、WCF概述對于一個好的分布式系統(tǒng)來講,設(shè)計時應(yīng)當考慮到異構(gòu)性、開放性、安全性、可擴展性、故障處理、并發(fā)性以及透明性等問題?;赟OAP的WebService可以實現(xiàn)異構(gòu)環(huán)境的互操作性,保證了跨平臺的通信。利用WSE(WebServiceEnhancements)可以為ASMX提供安全性的保證。.NETRemoting具有豐富的擴展功能,可以創(chuàng)建定制的信道、格式化器和代理程序。EnterpriseService(COM+)提供了對事務(wù)的支持,其中還包括分布式事務(wù),可實現(xiàn)故障的恢復(fù)。MSMQ可以支持異步調(diào)用、脫機連接、斷點連接等功能,利用消息隊列支持應(yīng)用程序之間的消息傳遞。從功能角度來看,WCF整合了ASMX、.NetRemoting、EnterpriseService、WSE以及MSMQ等現(xiàn)有技術(shù)的優(yōu)點,它提供了一種構(gòu)建安全可靠的分布式面向服務(wù)系統(tǒng)的統(tǒng)一的框架模型,使軟件研發(fā)人員在開發(fā)分布式應(yīng)用時變得更加輕松。1.面向服務(wù)既然WCF是一套面向服務(wù)的框架,服務(wù)自然便是WCF中最為重要的概念。服務(wù)是指暴露在外的一系列功能的集合,面向服務(wù)則是指一套構(gòu)建“面向服務(wù)程序”的抽象原則以及最優(yōu)方法。對于業(yè)務(wù)邏輯的理解,傳統(tǒng)的編程方式認為應(yīng)將業(yè)務(wù)邏輯封裝為對象,該對象提供了與業(yè)務(wù)相關(guān)的一些功能;而基于WCF的程序設(shè)計卻更多的是考慮如何提供服務(wù)以及消費服務(wù)。與面向組件服務(wù)程序類似,基于SOA的應(yīng)用程序?qū)⒎?wù)封裝到了單個邏輯程序當中,如圖1所示。圖1封裝服務(wù)的SOA應(yīng)用程序邏輯圖2.WCF體系結(jié)構(gòu)WCF擁有一個非常靈活的分層體系結(jié)構(gòu),分布式應(yīng)用程序可以使用高級API或者低級API編寫。高級API或者服務(wù)層可以用于調(diào)用方法和事件。服務(wù)層把這些高級的抽象代碼轉(zhuǎn)換為消息,以使用低級API上的信道和端口。圖2中顯示了WCF應(yīng)用程序的各個層。圖2WCF體系結(jié)構(gòu)圖WCF提供了對可靠性、事務(wù)性、并發(fā)管理、安全性以及實例激活等技術(shù)的有力支持,而這些支持均依賴于如圖3所示的WCF構(gòu)架。在客戶端,分布式應(yīng)用通過一個代理來轉(zhuǎn)發(fā)對宿主端所提供服務(wù)的調(diào)用,而代理擁有和服務(wù)相同的操作接口,另外還有一些附加的代理管理方法。這也就意味著客戶端從來不會直接調(diào)用服務(wù),即便這個服務(wù)就在本機的內(nèi)存中。當客戶端代理接收到來自客戶端的調(diào)用請求后,它將消息通過信道鏈向下傳遞。每個信道都會執(zhí)行相應(yīng)的消息的調(diào)用前處理,例如對消息的編碼、提供可靠的會話、對消息進行加密等??蛻舳说淖詈笠粋€信道則是傳輸信道,根據(jù)配置的傳輸方式發(fā)送消息給宿主。在宿主端,消息同樣通過信道鏈進行傳輸。與客戶端信道相對應(yīng),宿主端信道也會對消息執(zhí)行相應(yīng)的宿主端的調(diào)用前處理,例如對消息的解碼、提供會話管理、對消息進行解密等。宿主端的最后一個信道則負責(zé)將消息發(fā)送給消息分發(fā)器(Dispatcher),由分發(fā)器負責(zé)調(diào)用服務(wù)的實例。圖3WCF構(gòu)架示意圖3.WCF的基本技術(shù)要素作為基于SOA的一個框架產(chǎn)品,WCF實際上是構(gòu)建了一個在互聯(lián)系統(tǒng)中實現(xiàn)各個應(yīng)用程序之間通信的分布式框架。它使得系統(tǒng)構(gòu)架師與開發(fā)人員在構(gòu)建分布式系統(tǒng)時,能將更多的精力投入到與系統(tǒng)的業(yè)務(wù)邏輯本身的設(shè)計上來,而無需過多的考慮底層通信的實現(xiàn)及相關(guān)問題。WCF最核心的部分是能夠快捷的創(chuàng)建一個服務(wù),一個WCF服務(wù)端框架由宿主、端點以及服務(wù)類三部分所組成,如圖4所示。圖4WCF服務(wù)框架宿主(Host),即承載WCFService運行的環(huán)境??捎玫乃拗鳝h(huán)境包括:(1)自承載方式:在控制臺應(yīng)用程序與基于WinForm的應(yīng)用程序中都可以使用這種方式;(2)系統(tǒng)服務(wù)方式:服務(wù)可以隨著操作系統(tǒng)的啟動而自動啟動;(3)IIS方式:與WebServices的部署方式類似,由請求消息來激活服務(wù),但只支持HTTP方式的綁定;(4)WAS(WindowsProcessActivationService)方式:這個宿主是IIS7的一部分,只有WindowsVista和WindowsServer2008提供默認支持,它支持幾乎所有的通訊協(xié)議并提供了應(yīng)用程序池、循環(huán)回收、空閑管理、身份管理、隔離等強大的功能。服務(wù)類(ServiceClass)是指一個標記了一些WCF特有的屬性的類,它包含了對服務(wù)的業(yè)務(wù)邏輯的具體實現(xiàn)。端點(Endpoints)是WCF實現(xiàn)通信的核心要素,客戶端和服務(wù)端都通過端點來交換消息,WCF允許我們?yōu)榉?wù)添加多個綁定和端點。端點由地址(Address)、綁定(Binding)以及契約(Contract)三部分組成,如圖5所示。在WCF中,類ServiceEndpoint代表了一個Endpoint,在類中包含的EndpointAddress,Binding,ContractDescription類型分別對應(yīng)端點中的地址、綁定以及契約。圖5端點構(gòu)成圖地址:每個服務(wù)都會關(guān)聯(lián)到一個唯一的地址,因此地址定位和唯一標志了一個端點,其主要提供了兩個重要信息:服務(wù)位置以及傳送協(xié)議。在WCF中,地址由System.ServiceModel.EndpointAddress對象來表示,其包括URI、Identity、Headers三個要素。綁定:綁定提供了一種可設(shè)置的方式來選擇傳輸協(xié)議、消息編碼、通訊模式、可靠性、安全性、事務(wù)傳播以及交互方式等。例如在傳輸協(xié)議上可以選擇HTTP/HTTPS、TCP、P2P、IPC甚至是MSMQ等方式。消息編碼上可以選擇使用純文本方式來確?;ゲ僮髂芰?,或者選擇二進制編碼來優(yōu)化性能,或者使用MTOM來提高負載能力,甚至是自定義編碼方式。WCF中提供了BasicHttpBinding、NetTcpBinding、NetPeerTcpBinding、NetNamedPipeBinding、WSHttpBinding、WSFederationHttpBinding、WSDualHttpBinding、NetMsmqBinding以及MsmqIntegrationBinding九種標準類型的綁定。契約:契約是用來描述服務(wù)功能的一種平臺中立的標準方式,WCF所有服務(wù)都需要實現(xiàn)一個或多個契約。WCF定義了四種類型的契約:(1)服務(wù)契約(ServiceContracts):定義了客戶端可以使用哪些服務(wù)操作。(2)數(shù)據(jù)契約(DataContracts):定義了服務(wù)傳輸?shù)臄?shù)據(jù)類型。WCF定義了一些隱式數(shù)據(jù)契約,比如int、string等,但更多時候需要使用DataContractAttribute顯式定義那些自定義類型數(shù)據(jù)的數(shù)據(jù)契約。(3)錯誤處理契約(FaultContracts):定義了服務(wù)引發(fā)的錯誤信息,以及如何將這些異常傳遞給客戶端。(4)消息契約(MessageContracts):允許直接操控服務(wù)的消息內(nèi)容和格式。一般情況下,應(yīng)當用接口來定義服務(wù)契約,盡管我們也可以使用類。將服務(wù)契約定義為接口基于如下幾點優(yōu)點:(1)便于契約的繼承,不同根的類型可以自由實現(xiàn)相同的契約;(2)同一服務(wù)類型可以同時實現(xiàn)多個契約;(3)類似于接口隔離原則,可以隨時修改服務(wù)類型的實現(xiàn)而不影響其它實現(xiàn);(4)便于制定版本升級策略,新、舊版本的服務(wù)契約可以同時使用而互不影響。在WCF中,對于自承載的服務(wù),端點的相關(guān)的信息可以有代碼實現(xiàn)與配置文件兩種定義方式。而對于IIS承載服務(wù),端點的相關(guān)的信息一般定義在虛擬根目錄下的Web.Config文件中。一般來講,使用配置文件來定義端點相關(guān)信息是更為靈活、更為推薦的一種方式,其可以在不修改代碼、不重新發(fā)布系統(tǒng)的情況下對服務(wù)的地址、綁定和契約等參數(shù)進行修改(因為修改config類型文件的內(nèi)容是不需要重新編譯和重新部署的)。在下面的代碼中具體說明了如何定義宿主端的端點相關(guān)信息。其中地址為http://localhost:8080/HelloService,契約為WCFServiceHello命名空間下的IHello接口,綁定采用的是WSHttpBinding方式。值得注意的是,代碼中的HelloService為相對地址,http://localhost:8080/提供的是基址,當然去掉基址直接將address設(shè)為http://localhost:8080/HelloService也是可以的。代碼中還添加了名為MyServiceTypeBehaviors的行為配置,其將serviceMetadata節(jié)中的httpGetEnabled屬性設(shè)為了true,目的是為了自動透過HTTP-GET發(fā)布服務(wù)的元數(shù)據(jù)。WCF提供的另外一種發(fā)布元數(shù)據(jù)的方式是使用專門的MEX端點。<configuration><system.serviceModel><services><servicename="WCFServiceHello.HelloWorld"behaviorConfiguration="MyServiceTypeBehaviors"><endpointcontract="WCFServiceHello.IHello"binding="wsHttpBinding"address="HelloService"/><host><baseAddresses><addbaseAddress="http://localhost:8080/"/></baseAddresses></host></service></services><behaviors><serviceBehaviors><behaviorname="MyServiceTypeBehaviors"><serviceMetadatahttpGetEnabled="true"/></behavior></serviceBehaviors></behaviors></system.serviceModel></configuration>在接下來的宿主代碼中,只需要簡單的創(chuàng)建ServiceHost類型的對象,并利用其實例方法Open啟動服務(wù)應(yīng)用程序即可,簡要代碼如下所示:using(ServiceHosthost=newServiceHost(typeof(WCFServiceHello.HelloWorld))){Console.WriteLine("HelloServicehasbeenstarted...");host.Open();Console.ReadKey();}客戶端和服務(wù)之間通過消息交換來完成方法調(diào)用和數(shù)據(jù)傳遞,而在WCF中定義了3種消息交換模式,如圖6所示。(1)OneWay:這種消息交換模式在調(diào)用方法后會立即返回而不需要等待服務(wù)端的消息返回。(2)Request/Reply:這種消息交換模式屬于同步調(diào)用。在調(diào)用服務(wù)方法后需要等待服務(wù)端的消息返回。(3)Duplex:這種消息交換模式具有客戶端與服務(wù)端雙向通信的功能,同時它的實現(xiàn)還可以使消息交換具有異步回調(diào)的作用。圖6WCF中的3種消息交換模式在設(shè)置完宿主端端點之后,同樣也必須為分布式應(yīng)用程序定義客戶端的端點,而且只有當客戶端的端點與宿主端的某個端點相互匹配時,客戶端的請求才能被宿主端所監(jiān)聽到。如果服務(wù)提供了發(fā)布元數(shù)據(jù),那么利用.NETFramework3.0SDK所提供的SvcUtil.exe工具可以很輕松的自動生成與宿主端對應(yīng)的客戶端代理以及客戶端配置文件。比如,運行宿主端應(yīng)用程序,然后打開VisualStudio2005命令提示符,鍵入SvcUtilhttp://localhost:8080,便可以在當前目錄下得到客戶端代理文件HelloWorld.cs與客戶端配置文件output.config。另外一種簡便直觀的可視化工具是SDK所附帶的SvcConfigEditor.exe(C:\ProgramFiles\MicrosoftSDKs\Windows\v6.0A\bin目錄下,XP系統(tǒng)),使用這個工具可以非常方便地創(chuàng)建或修改宿主端和客戶端的配置文件。生成好客戶端代理與配置文件后,在代碼中直接使用客戶端代理對象即可。using(HelloClientclient=newHelloClient()){client.Hello();}Console.ReadKey();另外一種創(chuàng)建客戶端代理的方式是使用ChannelFactory動態(tài)的來創(chuàng)建。雖然WCF提供了這種方式,但是在實際開發(fā)中并不推薦使用它,畢竟ChannelFactory直接依賴于契約,而這恰恰違背了SOA中邊界隔離的原則。利用服務(wù)器端與客戶端之間的Channel來創(chuàng)建客戶端代理的代碼舉例如下:ServiceEndpointhttpendpoint=newServiceEndpoint(ContractDescription.GetContract(typeof(IHello)),newWSHttpBinding(),newEndpointAddress("http://localhost:8080/HelloService"));using(ChannelFactory<IHello>factory=newChannelFactory<IHello>(httpendpoint)){IHelloservice=factory.CreateChannel();service.Hello();}Console.ReadKey();三、軟件的分析與設(shè)計軟件主要的功能是初步實現(xiàn)基于WCF的局域網(wǎng)內(nèi)的實時通信,以面向服務(wù)為指導(dǎo)思想將具體開發(fā)過程分為即時通信服務(wù)的設(shè)計與實現(xiàn)、宿主的設(shè)計與實現(xiàn)以及即時通信客戶端的設(shè)計與實現(xiàn)三部分,使得應(yīng)用程序具有較好的安全性、并發(fā)性、可擴展性以及可維護性。1.服務(wù)的設(shè)計服務(wù)的設(shè)計包括了通用模塊Common.dll的設(shè)計、服務(wù)契約IChat的定義與實現(xiàn)、客戶端的回調(diào)接口IChatCallback的定義三個部分。通用模塊Common.dll主要包括對聊天者類型Person的定義,其包括name(聊天者的名稱或代號)以及ImageURL(聊天者選擇的頭像圖片的存儲路徑)兩個私有字段及相應(yīng)的屬性訪問器。由于Person類型是自定義數(shù)據(jù)類型,因此必須加上DataContractAttribute來顯式定義它。Person類的實現(xiàn)核心代碼如下:[DataContract]publicclassPerson{privatestringimageURL;privatestringname;publicPerson(stringimageURL,stringname){this.imageURL=imageURL;=name;}[DataMember]publicstringImageURL{get{returnimageURL;}set{imageURL=value;}}[DataMember]publicstringName{get{returnname;}set{name=value;}}}服務(wù)契約IChat的定義中主要包括Join、Say、Whisper以及Leave四個基本方法。其中Join表示:進入聊天;Say表示向所有用戶廣播消息;Whisper表示對指定的用戶發(fā)送消息;Leave表示離開聊天。需添加ServiceContract屬性將IChat接口標記為服務(wù)契約,由于要實現(xiàn)客戶端與服務(wù)端雙向通信的功能,因此還必須設(shè)置CallbackContract參數(shù),其參數(shù)IChatCallback為Duplex模式下的客戶端回調(diào)類型。對于IChat接口中的方法來講,必須標記OperationContract屬性。其中IsOneWay指示在該消息交換模式下調(diào)用方法后是否會立即返回而不需要等待服務(wù)端的消息返回;IsInitiating指示服務(wù)方法是否啟動一個Session;IsTerminating指示服務(wù)方法調(diào)用完成是否結(jié)束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();}客戶端的回調(diào)接口IChatCallback的定義包括了分別對應(yīng)Join、Say、Whisper以及Leave的UserEnter、Receive、ReceiveWhisper以及UserLeave四個基本方法。UserEnter表示當有新聊天用戶加入時所有聊天用戶接收到一個相應(yīng)的通知;Receive表示接收用戶廣播的消息;ReceiveWhisper表示接收相關(guān)用戶發(fā)來的消息;UserLeave表示當有聊天用戶離開時所有聊天用戶接收一個相應(yīng)的通知。注意在接口定義中,每個服務(wù)方法的消息轉(zhuǎn)換模式均設(shè)置為One-Way。此外,回調(diào)接口是被本地調(diào)用,因此不需要定義[ServiceContract]屬性。回調(diào)接口IChatCallback的定義的代碼如下:interfaceIChatCallback{[OperationContract(IsOneWay=true)]voidReceive(Personsender,stringmessage);[OperationContract(IsOneWay=true)]voidReceiveWhisper(Personsender,stringmessage);[OperationContract(IsOneWay=true)]voidUserEnter(Personperson);[OperationContract(IsOneWay=true)]voidUserLeave(Personperson);}定義了服務(wù)契約以及客戶端回調(diào)接口之后,仍需要定義一些消息類型用作客戶端與服務(wù)之間的交互,具體代碼及解釋如下://定義說明消息類型的枚舉類型MessageTypepublicenumMessageType{Receive,UserEnter,UserLeave,ReceiveWhisper};//ChatEventArgs繼承至EventArgs,作為傳遞的消息參數(shù),//其包括消息的類型msgType、消息發(fā)送者person以及消息的主體內(nèi)容messagepublicclassChatEventArgs:EventArgs{publicMessageTypemsgType;publicPersonperson;publicstringmessage;}最后是對服務(wù)契約中IChat接口的實現(xiàn)類ChatService的設(shè)計。值得注意的是ChatService繼承了IChat,不再需要像服務(wù)契約IChat一樣添加ServiceContract屬性。另外設(shè)置了其ServiceBehavior屬性的InstanceContextMode參數(shù)來決定實例化方式(PerSession方式或者PerCall方式)。PerSession表明服務(wù)對象的生命周期存活于一個會話期間,在同一個會話期間對于服務(wù)的不同操作的調(diào)用都會施加到同一個客戶端代理類型的對象上;PerCall則表示服務(wù)對象是在方法被調(diào)用時創(chuàng)建,結(jié)束后即被銷毀。ServiceBehavior.ConcurrencyMode參數(shù)的設(shè)置則用于控制具體服務(wù)對象的并發(fā)行為,其包括三種行為:Single:為默認方式。服務(wù)實例是單線程的,不接受重入調(diào)用(reentrantcalls)。也就是說對于同一個服務(wù)實例的多個調(diào)用必須排隊,直到上一次調(diào)用完成后才能繼續(xù)。Reentrant:和Single一樣,也是單線程的,但能接受重入調(diào)用,至于針對同一服務(wù)對象的多個調(diào)用仍然需要排隊。因為在Single模式下,當方法調(diào)用另外一個服務(wù)時,方法會阻塞,直到所調(diào)用的服務(wù)完成。如果方法不能重入,那么調(diào)用方會因無法接受所調(diào)用服務(wù)的返回消息,無法解除阻塞狀態(tài)而陷入死鎖。Reentrant模式解決了這種不足。Multiple:允許多個客戶端同時調(diào)用服務(wù)方法。不再有鎖的問題,不再提供同步保障。因此使用該模式時,必須自行提供多線程同步機制(比如在本例中給static類型的syncObj對象加鎖)來保證數(shù)據(jù)成員的讀寫安全。類ChatService的實現(xiàn)代碼及解析如下:[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession,ConcurrencyMode=ConcurrencyMode.Multiple)]publicclassChatService:IChat{//用于保障多線程同步而設(shè)置的對象privatestaticObjectsyncObj=newObject();//客戶端的回調(diào)接口對象IChatCallbackcallback=null;//用于廣播事件的委托publicdelegatevoidChatEventHandler(objectsender,ChatEventArgse);publicstaticeventChatEventHandlerChatEvent;privateChatEventHandlermyEventHandler=null;//利用字典對象chatters來保存聊天者對象以及綁定的對應(yīng)事件委托staticDictionary<Person,ChatEventHandler>chatters=newDictionary<Person,ChatEventHandler>();//當前聊天者對象privatePersonperson;//判斷具有相應(yīng)名字的聊天者是否存在于字典對象中privateboolcheckIfPersonExists(stringname){foreach(Personpinchatters.Keys){if(p.Name.Equals(name,StringComparison.OrdinalIgnoreCase)){returntrue;}}returnfalse;}//在字典對象中搜索判斷其中是否包含了相應(yīng)名字的聊天者,//如果有則返回其對應(yīng)的委托ChatEventHandler;否則返回空privateChatEventHandlergetPersonHandler(stringname){foreach(Personpinchatters.Keys){//不區(qū)分大小寫if(p.Name.Equals(name,StringComparison.OrdinalIgnoreCase)){ChatEventHandlerchatTo=null;chatters.TryGetValue(p,outchatTo);returnchatTo;}}returnnull;}//在字典對象中搜索判斷其中是否包含了相應(yīng)名字的聊天者,//如果有則返回聊天者對象;否則返回空privatePersongetPerson(stringname){foreach(Personpinchatters.Keys){if(p.Name.Equals(name,StringComparison.OrdinalIgnoreCase)){returnp;}}returnnull;}//加入聊天,如果字典對象chatters中沒有同名的聊天者publicPerson[]Join(Personperson){booluserAdded=false;//創(chuàng)建新的ChatEventHandler類型委托,其指向MyEventHandler()方法myEventHandler=newChatEventHandler(MyEventHandler);//執(zhí)行關(guān)鍵區(qū)域,判斷是否存在同名聊天者,//如果不存在則向字典中添加該對象并將MyEventHandler委托作為其value,//以待后面來觸發(fā)lock(syncObj){if(!checkIfPersonExists(person.Name)&&person!=null){this.person=person;chatters.Add(person,MyEventHandler);userAdded=true;}}//如果新的聊天者添加成功,獲得一個回調(diào)的實例來創(chuàng)建一個消息,//并將其廣播給其他的聊天者//讀取字典chatters的所有聊天者對象并返回一個包含了所有對象的列表if(userAdded){callback=OperationContext.Current.GetCallbackChannel<IChatCallback>();ChatEventArgse=newChatEventArgs();e.msgType=MessageType.UserEnter;e.person=this.person;//廣播有新聊天者加入的消息BroadcastMessage(e);//將新加入聊天者對象的委托加到全局的多播委托上ChatEvent+=myEventHandler;Person[]list=newPerson[chatters.Count];//執(zhí)行關(guān)鍵區(qū)域,將字典chatters的所有聊天者對象//拷貝至一個聊天者列表上,用于方法返回lock(syncObj){chatters.Keys.CopyTo(list,0);}returnlist;}else{returnnull;}}//廣播當前聊天者輸入的消息給所有聊天者publicvoidSay(stringmsg){ChatEventArgse=newChatEventArgs();e.msgType=MessageType.Receive;e.person=this.person;e.message=msg;BroadcastMessage(e);}//在字典對象chatters中查找具有相應(yīng)名稱的聊天者對象,//并異步地觸發(fā)其對應(yīng)的ChatEventHandle委托publicvoidWhisper(stringto,stringmsg){ChatEventArgse=newChatEventArgs();e.msgType=MessageType.ReceiveWhisper;e.person=this.person;e.message=msg;try{ChatEventHandlerchatterTo;//執(zhí)行關(guān)鍵區(qū)域,獲取具有相應(yīng)名稱的聊天者對象在chatters中所對應(yīng)的委托l(wèi)ock(syncObj){chatterTo=getPersonHandler(to);if(chatterTo==null){thrownewKeyNotFoundException("Thepersonwhosnameis"+to+"couldnotbefound");}}//異步執(zhí)行委托chatterTo.BeginInvoke(this,e,newAsyncCallback(EndAsync),null);}catch(KeyNotFoundException){}}///當聊天者離開時,從字典chatters中移除包含該對象的項。///并從全局的多播委托上移除該對象對應(yīng)的委托publicvoidLeave(){if(this.person==null)return;//獲得該聊天者對象所對應(yīng)的ChatEventHandler委托ChatEventHandlerchatterToRemove=getPersonHandler(this.person.Name);//執(zhí)行關(guān)鍵區(qū)域,從從字典chatters中移除包含該聊天者對象的項。lock(syncObj){chatters.Remove(this.person);}//從全局的多播委托上移除該對象對應(yīng)的委托ChatEvent-=chatterToRemove;ChatEventArgse=newChatEventArgs();e.msgType=MessageType.UserLeave;e.person=this.person;this.person=null;//將消息廣播給其他聊天者BroadcastMessage(e);}//當chatters中的聊天者對象所對應(yīng)的ChatEventHandler委托被觸發(fā)時,//MyEventHandler方法將執(zhí)行。//該方法通過檢查傳遞過來的ChatEventArgs參數(shù)類型來執(zhí)行相應(yīng)的客戶端//的回調(diào)接口中的方法。privatevoidMyEventHandler(objectsender,ChatEventArgse){try{switch(e.msgType){caseMessageType.Receive:callback.Receive(e.person,e.message);break;caseMessageType.ReceiveWhisper:callback.ReceiveWhisper(e.person,e.message);break;caseMessageType.UserEnter:callback.UserEnter(e.person);break;caseMessageType.UserLeave:callback.UserLeave(e.person);break;}}catch{Leave();}}//異步地觸發(fā)字典chatters中所有聊天者對象所對應(yīng)的ChatEventHandler委托。//BeginInvoke方法可啟動異步調(diào)用。第一個參數(shù)是一個AsyncCallback委托,//該委托引用在異步調(diào)用完成時要調(diào)用的方法。//第二個參數(shù)是一個用戶定義的對象,該對象可向回調(diào)方法傳遞信息。//BeginInvoke立即返回,不等待異步調(diào)用完成。//BeginInvoke會返回IAsyncResult,這個結(jié)果可用于監(jiān)視異步調(diào)用進度。privatevoidBroadcastMessage(ChatEventArgse){ChatEventHandlertemp=ChatEvent;if(temp!=null){foreach(ChatEventHandlerhandlerintemp.GetInvocationList()){handler.BeginInvoke(this,e,newAsyncCallback(EndAsync),null);}}}//EndInvoke方法檢索異步調(diào)用的結(jié)果。//調(diào)用BeginInvoke后可隨時調(diào)用EndInvoke方法;//如果異步調(diào)用尚未完成,EndInvoke將一直阻止調(diào)用線程,//直到異步調(diào)用完成后才允許調(diào)用線程執(zhí)行。privatevoidEndAsync(IAsyncResultar){ChatEventHandlerd=null;try{//System.Runtime.Remoting.Messaging.AsyncResult封裝了異步委托上//的異步操作的結(jié)果。//AsyncResult.AsyncDelegate屬性可用于獲取在其上調(diào)用//異步調(diào)用的委托對象。System.Runtime.Remoting.Messaging.AsyncResultasres=(System.Runtime.Remoting.Messaging.AsyncResult)ar;d=((ChatEventHandler)asres.AsyncDelegate);d.EndInvoke(ar);}catch{ChatEvent-=d;}}}2.宿主端的設(shè)計設(shè)計好服務(wù)之后,宿主端的設(shè)計相對簡單,只是一個基于控制臺的應(yīng)用程序。首先建立一個ServiceHost類型的對象host,然后調(diào)用ServiceHost類型的實例方法Open即可啟動宿主監(jiān)聽。需要注意的是在程序末尾應(yīng)當調(diào)用Abort以及Close將資源釋放掉,或者利用using語句來自動釋放所占用的資源。下面是宿主端的實現(xiàn)代碼以及配置文件內(nèi)容:staticvoidMain(string[]args){Uriuri=newUri(ConfigurationManager.AppSettings["addr"]);ServiceHosthost=newServiceHost(typeof(Chatters.ChatService),uri);host.Open();Console.WriteLine("ChatserviceHostnowlisteningonEndpoint{0}",uri.ToString());Console.WriteLine("PressENTERtostopchatservice...");Console.ReadLine();host.Abort();host.Close();}}配置文件中,地址為net.tcp://localhost:10001/chatservice;綁定為netTcpBinding,并且提供可靠性傳輸、不保障安全性,發(fā)送超時時間限定為1秒;契約為Chatters.IChat接口。另外設(shè)置了名稱為DuplexBinding的綁定配置,其對服務(wù)的分流作了限制,并發(fā)的Session個數(shù)的上限為10000。<?xmlversion="1.0"encoding="utf-8"?><configuration><appSettings><addkey="addr"value="net.tcp://localhost:10001/chatservice"/></appSettings><system.serviceModel><services><servicename="Chatters.ChatService"behaviorConfiguration="MyBehavior"><endpointaddress=""binding="netTcpBinding"bindingConfiguration="DuplexBinding"contract="Chatters.IChat"/></service></services><behaviors><serviceBehaviors><behaviorname="MyBehavior"><serviceThrottlingmaxConcurrentSessions="10000"/></behavior></serviceBehaviors></behaviors><bindings><netTcpBinding><bindingname="DuplexBinding"sendTimeout="00:00:01"><reliableSessionenabled="true"/><securitymode="None"/></binding></netTcpBinding></bindings></system.serviceModel></configuration>3.客戶端的設(shè)計客戶端的設(shè)計主要包括實現(xiàn)客戶端代理與配置文件、客戶端的回調(diào)接口契約的實現(xiàn)以及客戶端登錄、主窗體、聊天窗體界面的設(shè)計與代碼邏輯實現(xiàn)這三部分。表示客戶端的各個對象之間行為關(guān)系的序列圖如圖7所示。圖7客戶端應(yīng)用的序列圖首先是利用svcutil.exe工具自動生成的客戶端代理以及客戶端的配置文件,并做出相應(yīng)的更改。打開VisualStudio2005命令提示,轉(zhuǎn)到ChatService所在目錄,依次輸入以下命令可以生成同步的客戶端代理。svcutilChatService.exesvcutil*.wsdl*.xsd/language:C#/out:MyProxy.cs/config:app.config輸入以下命令可以創(chuàng)建異步的客戶端代理:svcutil*.wsdl*.xsd/a/language:C#/out:MyProxy2.cs/config:app.config因為用戶加入聊天這個過程是異步執(zhí)行的,所以可以利用自動生成的異步客戶端代理文件中的BeginJoin與EndJoin方法來替換同步的Join方法??蛻舳舜淼膶崿F(xiàn)代碼如下所示:usingCommon;[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","")][System.ServiceModel.ServiceContractAttribute(CallbackContract=typeof(IChatCallback),SessionMode=System.ServiceModel.SessionMode.Required)]publicinterfaceIChat{[System.ServiceModel.OperationContractAttribute(AsyncPattern=true,Action="/IChat/Join",ReplyAction="/IChat/JoinResponse")]System.IAsyncResultBeginJoin(Personname,System.AsyncCallbackcallback,objectasyncState);Person[]EndJoin(System.IAsyncResultresult);[System.ServiceModel.OperationContractAttribute(IsOneWay=true,IsInitiating=false,Action="/IChat/Leave")]voidLeave();[System.ServiceModel.OperationContractAttribute(IsOneWay=true,IsInitiating=false,Action="/IChat/Say")]voidSay(stringmsg);[System.ServiceModel.OperationContractAttribute(IsOneWay=true,IsInitiating=false,Action="/IChat/Whisper")]voidWhisper(stringto,stringmsg);}[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","")]publicinterfaceIChatCallback{[System.ServiceModel.OperationContractAttribute(IsOneWay=true,Action="/IChat/Receive")]voidReceive(Personsender,stringmessage);[System.ServiceModel.OperationContractAttribute(IsOneWay=true,Action="/IChat/ReceiveWhisper")]voidReceiveWhisper(Personsender,stringmessage);[System.ServiceModel.OperationContractAttribute(IsOneWay=true,Action="/IChat/UserEnter")]voidUserEnter(Personperson);[System.ServiceModel.OperationContractAttribute(IsOneWay=true,Action="/IChat/UserLeave")]voidUserLeave(Personperson);}[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","")]publicinterfaceIChatChannel:IChat,System.ServiceModel.IClientChannel{}[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","")]publicpartialclassChatProxy:System.ServiceModel.DuplexClientBase<IChat>,IChat{publicChatProxy(System.ServiceModel.InstanceContextcallbackInstance):base(callbackInstance){}publicChatProxy(System.ServiceModel.InstanceContextcallbackInstance,stringendpointConfigurationName):base(callbackInstance,endpointConfigurationName){}publicChatProxy(System.ServiceModel.InstanceContextcallbackInstance,stringendpointConfigurationName,stringremoteAddress):base(callbackInstance,endpointConfigurationName,remoteAddress){}publicChatProxy(System.ServiceModel.InstanceContextcallbackInstance,stringendpointConfigurationName,System.ServiceModel.EndpointAddressremoteAddress):base(callbackInstance,endpointConfigurationName,remoteAddress){}publicChatProxy(System.ServiceModel.InstanceContextcallbackInstance,System.ServiceModel.Channels.Bindingbinding,System.ServiceModel.EndpointAddressremoteAddress):base(callbackInstance,binding,remoteAddress){}publicSystem.IAsyncResultBeginJoin(Personname,System.AsyncCallbackcallback,objectasyncState){returnbase.Channel.BeginJoin(name,callback,asyncState);}publicPerson[]EndJoin(System.IAsyncResultresult){returnbase.Channel.EndJoin(result);}publicvoidLeave(){base.Channel.Leave();}publicvoidSay(stringmsg){base.Channel.Say(msg);}publicvoidWhisper(stringto,stringmsg){base.Channel.Whisper(to,msg);}}在客戶端配置文件中,地址、綁定、契約分別與宿主端配置所一一對應(yīng)。<?xmlversion="1.0"encoding="utf-8"?><configuration><system.serviceModel><client><endpointname=""address="net.tcp://localhost:10001/chatservice"binding="netTcpBinding"bindingConfiguration="DuplexBinding"contract="IChat"/></client><bindings><netTcpBinding><bindingname="DuplexBinding"sendTimeout="00:00:20"><reliableSessionenabled="true"/><securitymode="None"/></binding></netTcpBinding></bindings></system.serviceModel></configuration>對于客戶端的回調(diào)接口契約的實現(xiàn),首先需要定義一些消息類型作為客戶端與服務(wù)交互的消息,代碼及解釋如下://定義了消息類型的枚舉publicenumCallBackType{Receive,ReceiveWhisper,UserEnter,UserLeave};//代理事件參數(shù)publicclassProxyEventArgs:EventArgs{//當前的在線用戶列表publicPerson[]list;}//客戶端回調(diào)方法所綁定的事件的參數(shù)定義publicclassProxyCallBackEventArgs:EventArgs{//回調(diào)的類型publicCallBackTypecallbackType;//收到的消息內(nèi)容publicstringmessage="";//發(fā)送消息的人publicPersonperson=null;}然后是對實現(xiàn)了客戶端回調(diào)接口契約IChatCallback的Proxy_Singleton類的設(shè)計。該類采用了將構(gòu)造函數(shù)設(shè)置為私有并通過GetInstance()返回static的Proxy_Singleton類型的對象來實現(xiàn)單例設(shè)計模式,它提供了該唯一對象的唯一全局訪問點,這樣使得在同一個Session中對于ChatService的不同操作的調(diào)用都是對于同一個Proxy_Singleton類型的對象的操作。其保障了程序正確無誤的執(zhí)行而無需擔(dān)心每次操作是否會重新初始化新的Proxy_Singleton類型的對象或者同一個Session中不同操作是否會是對于不同Proxy_Singleton類型的對象的操作調(diào)用。從這點來講,這也與服務(wù)中設(shè)置的InstanceContextMode=InstanceContextMode.PerSession的語義一致。為了保障了多線程環(huán)境下的線程安全,代碼中通過給readonly的靜態(tài)對象加鎖的方式來保障這點。下面的代碼是對Proxy_Singleton類的具體實現(xiàn):publicsealedclassProxy_Singleton:IChatCallback{privatestaticProxy_Singletonsingleton=null;privatestaticreadonlyobjectsingletonLock=newobject();privateChatProxyproxy;privatePersonmyPerson;privatedelegatevoidHandleDelegate(Person[]list);privatedelegatevoidHandleErrorDelegate();//ProxyEventHandler與ProxyCallBackEventHandler事件用于窗體訂閱。publicdelegatevoidProxyEventHandler(objectsender,ProxyEventArgse);publicdelegatevoidProxyCallBackEventHandler(objectsender,ProxyCallBackEventArgse);publiceventProxyEventHandlerProxyEvent;publiceventProxyCallBackEventHandlerProxyCallBackEvent;privateProxy_Singleton(){}//接收發(fā)來的廣播消息publicvoidReceive(Personsender,stringmessage){Receive(sender,message,CallBackType.Receive);}//接收發(fā)來的消息publicvoidReceiveWhisper(Personsender,stringmessage){Receive(sender,message,CallBackType.ReceiveWhisper);}//根據(jù)callbacktype參數(shù)的值來封裝消息,并觸發(fā)ProxyCallBackEvent事件privatevoidReceive(Personsender,stringmessage,CallBackTypecallbackType){ProxyCallBackEventArgse=newProxyCallBackEventArgs();e.message=message;e.callbackType=callbackType;e.person=sender;OnProxyCallBackEvent(e);}//新的聊天者加入publicvoidUserEnter(Personperson){UserEnterLeave(person,CallBackType.UserEnter);}//聊天者離開publicvoidUserLeave(Personperson){UserEnterLeave(person,CallBackType.UserLeave);}//被UserEnter()以及UserLeave()調(diào)用,//根據(jù)callbackType參數(shù)的值來封裝消息,并觸發(fā)ProxyCallBackEvent事件privatevoidUserEnterLeave(Personperson,CallBackTypecallbackType){ProxyCallBackEventArgse=newProxyCallBackEventArgs();e.person=person;e.callbackType=callbackType;OnProxyCallBackEvent(e);}//異步的Join操作,首先調(diào)用BeginJoin,該操作完成后將調(diào)用OnEndJoin。publicvoidConnect(Personp){InstanceContextsite=newInstanceContext(this);proxy=newChatProxy(site);IAsyncResultiar=proxy.BeginJoin(p,newAsyncCallback(OnEndJoin),null);}//作為異步調(diào)用中BeginInvoke的回調(diào)方法,//獲取當前在線用戶的列表privatevoidOnEndJoin(IAsyncResultiar){try{Person[]list=proxy.EndJoin(iar);HandleEndJoin(list);}catch(Exceptione){MessageBox.Show(e.Message,"Error",MessageBoxButtons.OK,MessageBoxIcon.Error);}}//如果在線者列表不為空,則觸發(fā)ProxyEvent事件privatevoidHandleEndJoin(Person[]list){if(list==null){MessageBox.Show("Error:Listisempty","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);ExitChatSession();}else{ProxyEventArgse=newProxyEventArgs();e.list=list;OnProxyEvent(e);}}//觸發(fā)ProxyCallBackEvent事件protectedvoidOnProxyCallBackEvent(ProxyCallBackEventArgse){if(ProxyCallBackEvent!=null){ProxyCallBackEvent(this,e);}}//觸發(fā)ProxyEvent事件protectedvoidOnProxyEvent(ProxyEventArgse){if(ProxyEvent!=null){ProxyEvent(this,e);}}//單例模式,返回Proxy_Singleton類型的唯一對象publicstaticProxy_SingletonGetInstance(){lock(singletonLock){if(singleton==null){singleton=newProxy_Singleton();}returnsingleton;}}//通過布爾值pvt來決定是調(diào)用客戶端代理的廣播方法還是發(fā)送消息給//指定目標的方法publicvoidSayAndClear(stringto,stringmsg,boolpvt){if(!pvt)proxy.Say(msg);elseproxy.Whisper(to,msg);}//先調(diào)用客戶端代理對象的Leave方法//最后調(diào)用AbortProxy()來釋放客戶端代理對象publicvoidExitChatSession(){try{proxy.Leave();}catch{}finally{AbortProxy();}}//調(diào)用客戶端代理的Abort與Close方法publicvoidAbortProxy(){if(proxy!=null){proxy.Abort();proxy.Close();proxy=null;}}}登錄界面Login主要完成打開PickForm窗體以及選擇用戶的頭像并顯示,驗證頭像圖片URL、用戶名稱的合法性并將它們作為參數(shù)創(chuàng)建一個聊天者類型的對象,最終將此對象傳遞給新創(chuàng)建的主窗體并顯示主窗體。登錄界面Login執(zhí)行效果圖如圖8所示。圖8登錄界面執(zhí)行效果圖登錄界面Login主要功能的實現(xiàn)代碼及詳細解析如下://當關(guān)閉選擇圖像的窗體時,在Login窗體中加載所選擇的圖片privatevoidShowFace(objectsender,EventArgse){if(pickfaceform.Visible==false){this.pbFace.Image=Image.FromFile(pickfaceform.ImageURL);}}//當用戶選擇好圖像且輸入了自己的名稱時創(chuàng)建一個聊天者類型的對象,//將此對象傳遞給新創(chuàng)建的主窗體并顯示主窗體privatevoidbtnLogin_Click(objectsender,EventArgse){imgurl=this.pickfaceform.ImageURL;if((txtName.Text.ToString()!="")&&(imgurl!=null)){this.Visible=false;currentPerson=newPerson(imgurl,txtName.Text);MainWindowmainwindow=newMainWindow(currentPerson);mainwindow.Show();}else{MessageBox.Show("PleasechooseafaceandthenEnteryournamebeforecontinue!","Retry",MessageBoxButtons.OK,MessageBoxIcon.Warning);}}privatevoidbtnPickface_Click(objectsender,EventArgse){pickfaceform.Show();}選擇頭像界面完成的功能相對單一,只是加載指定路徑下的所有頭像圖片并在ListView控件中顯示出它們,執(zhí)行效果圖如圖9所示。圖9選擇頭像執(zhí)行效果圖選擇用戶頭像界面PickForm主要功能實現(xiàn)代碼及詳細解析如下://加載指定路徑下各種類型的圖片privatevoidLoadImage(){stringpath=AppDomain.CurrentDomain.BaseDirectory+@"Faces";DirectoryInfodi=newDirectoryInfo(path);//獲得的各種類型文件存放的路徑存放于FileInfo類型的數(shù)組中FileInfo[]jpgfis=di.GetFiles("*.jpg");FileInfo[]pngfis=di.GetFiles("*.png");FileInfo[]giffis=di.GetFiles("*.gif");FileInfo[]bmpfis=di.GetFiles("*.bmp");string[]faces_path=newString[jpgfis.Length+pngfis.Length+giffis.Length+bmpfis.Length];inti=0,j=0;foreach(FileInfojpgisiteminjpgfis){faces_path[i++]=jpgfis[j++].FullName;}j=0;foreach(FileInfogiffisitemingiffis){faces_path[i++]=giffis[j++].FullName;}j=0;foreach(FileInfopngfisiteminpngfis){faces_path[i++]=pngfis[j++].FullName;}j=0;foreach(FileInfobmpfisiteminbmpfis){faces_path[i++]=bmpfis[j++].FullName;}//i值記錄相應(yīng)圖片在ImageList中的索引值i=0;ImageListlargeimageList=newImageList();ImageListsmallimageList=newImageList();largeimageList.ImageSize=newSize(40,40);smallimageList.ImageSize=newSize(20,20);foreach(stringfacepathinfaces_path){ListViewItemitem=newListViewItem("",i);//將圖片路徑存儲到Tag屬性里item.Tag=facepathasobject;lstFaces.Items.AddRange(newListViewItem[]{item});largeimageList.Images.Add(Bitmap.FromFile(facepath));smallimageList.Images.Add(Bitmap.FromFile(facepath));i++;}//設(shè)置lstFaces的LargeImageList與SmallImageList屬性,//用于控件顯示為大、小圖標時使用lstFaces.SmallImageList=smallimageList;lstFaces.LargeImageList=largeimageList;}//確定選擇的頭像后,隱藏窗體并將選中頭像的Tag值//(存儲了該頭像圖片的路徑)賦值給imageurlprivatevoidbtnOK_Click(objectsender,EventArgse){if(lstFaces.SelectedItems.Count!=0){imageurl=lstFaces.SelectedItems[0].Tag.ToString();this.Visible=false;}else{MessageBox.Show("PleaseclickonefaceatleastbeforepressOK!","Retry",

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論