先看一個截圖。
上面的圖,各位乍一看,可能會覺得是用Socket編寫的聊天程序。告訴你吧,這玩意兒不是用Socket實現,呵呵,當然它的底層肯定與Socket有一定關系,我只說我的代碼沒有用到socket而已。
那麼,除了Socket可以用於通信,還有其他技術嗎?有啊,首先,如果你足夠強大,用HTTP也行,但HTTP初始化的過程貌似比較慢。那麼還有嗎?當然了,各位還記得.NET以前有一個很X但又很少被關注的技術——Remoting。用過吧?沒用過也沒關系,因為它已經有替代品了。
這時候大家肯定想到WCF不是一盞“省油”的燈,其實不然,對比於用Socket要編寫的代碼數量和維護成本,用WCF編寫網絡通信程序,不僅省油,而且省時省力,最重要的是省心。所以,人的健康,心理健康是占主導的,你看一個心理不健康的人,其身體也不會健康到哪裡去,今天這病明天那病。
因而,編程這事啊,越省心越好,有利於我們的健康,賺錢永遠不是目的,身心健康才是活在這個世界上的主旋律,至於你信不信,反正我深信不疑。
我就這個WCF版的聊天程序的大致思路說一說。
這個程序既可以充當服務器端也同時作為客戶端,每個應用實例都扮演著雙重角色。這裡我不需要引用服務。首先看服務協定和服務類。
using System; using System.ServiceModel; namespace ServiceDef { [ServiceContract] public interface IService { [OperationContract(IsOneWay = true)] void SendMessage(string msg); } /// <summary> /// 服務 /// </summary> public class MyChatService : IService { /// <summary> /// 收到消息後引發的事件 /// </summary> public event EventHandler<MessageReceiveEventArgs> MessageGot; public MyChatService() { MessageGot += new EventHandler<MessageReceiveEventArgs>(TestApp.Form1.GetMessageCallBack); } public void SendMessage(string msg) { if (MessageGot != null) { MessageGot(this, new MessageReceiveEventArgs(msg)); } } } /// <summary> /// 收到消息後引發事件的參數 /// </summary> public class MessageReceiveEventArgs : EventArgs { private string m_Message = string.Empty; public MessageReceiveEventArgs(string message) { this.m_Message = message; } public string MessageText { get { return this.m_Message; } } } }
服務協定沒什麼好看的了,相信大家都會寫,這裡的服務類與以往的有些不同,大家看到,裡面定義了一個事件。那麼,為什麼要這樣做呢?為什麼要在服務方法被調用時引發這個事件呢?
想一想,我們以上代碼是與UI分離的,也就是說,與UI分離是一種很好的編程方法,這樣在修改維護時不會搞得亂七八糟。但是,我們都知道世間萬物皆為陰陽所生,所以才有太極生兩儀,兩儀生四象,四象成八卦。而陰與陽是統一的,陰中有陽,陽在有陰。
我們的應用程序的UI就是陽,而業務邏輯就是陰,所以編程就是這麼簡單——陰陽互動。為了完成陰中有陽的功能,我們要想辦法讓這些代碼與窗口上的控件互動,當然方法很多,也相當靈活。使用事件是比較好的。
於是,在服務類中定義一個事件,而事件的處理方法在主窗口類中定義,這樣一來,陽與陰之間就有了一個可以相通的渠道。
為了使用訪問方便,在窗口類中定義的處理事件的方法使用靜態方法,靜態方法的好處在於,它不基於對象,而是基於類的,你去到哪裡都可以訪問,它是全球化的。
這時候有人會問了,靜態方法不能訪問類對象的成員,那麼這個靜態方法又如何與窗體上的控件互動呢?技巧都是拿來用的。有了靜態方法,難道我不能在窗口類中定義一個保存當前類實例的靜態變量嗎?
比如,我這個窗口的類名為FormMain,我只要在FormMain裡面定義一個static FormMain CurrentForm = null;就完事了,這樣不就可以在靜態方法中訪問了嗎?
只要在FormMain的構造函數中賦值就行了,CurrentForm = this;
比如本例的代碼:
#region 靜態成員 static Form1 CurrentInstance = null; public static void GetMessageCallBack(object sender, ServiceDef.MessageReceiveEventArgs e) { if (CurrentInstance != null) { CurrentInstance.AddMessageToListBox(e.MessageText); } } #endregion public Form1() { InitializeComponent(); CurrentInstance = this; …………
你看,這就成了。
然後當然是定義服務器了,這裡我們只有一個終結點,就是上面的IService,所以不用基址了,因為我們也不需要引用服務,直接利用ChannelFactory就行了。
#region 與服操作有關 ServiceHost host = null; /// <summary> /// 啟動服務 /// </summary> /// <param name="port">監聽端口</param> void OpenService(int port) { host = new ServiceHost(typeof(ServiceDef.MyChatService)); NetTcpBinding binding = new NetTcpBinding(); binding.Security.Mode = SecurityMode.None; host.AddServiceEndpoint(typeof(ServiceDef.IService), binding, "net.tcp://" + Dns.GetHostName() + ":"+ port.ToString() + "/chatsvc/"); host.Opened += host_Opened; host.Closed += host_Closed; try { host.Open(); } catch (Exception ex) { ShowMessage(ex.Message); } } void host_Closed(object sender, EventArgs e) { ShowMessage("服務已關閉。"); } void host_Opened(object sender, EventArgs e) { ShowMessage("服務已啟動。"); } /// <summary> /// 關閉服務 /// </summary> void CloseService() { if (host != null) { host.Close(); host.Opened -= host_Opened; host.Closed -= host_Closed; } } #endregion
好了,接下來就是發送消息,其實就是調用服務方法IService.SendMessage,這裡我們只用ChannelFactory<TChannel>工廠來生產一個IService通道,而後直接調用就可以了,就不必引用服務,也不用生成什麼WSDL文件,也不考慮SOAP版本了。
// 發送消息,即調用服務 NetTcpBinding binding =new NetTcpBinding(); binding.Security.Mode = SecurityMode.None; try { ServiceDef.IService ep = ChannelFactory<ServiceDef.IService>.CreateChannel(binding,new EndpointAddress("net.tcp://" + txtSvrHostName.Text + ":" + rmPort.ToString() + "/chatsvc/")); ep.SendMessage(txtSendMessage.Text); txtSendMessage.Clear(); } catch (Exception ex) { ShowMessage(ex.Message); }
哈哈,是不是很簡單,而且,你也可以想到,如果用WCF來做文件傳輸,比如PC與手機上的文件傳送,是不是很方便呢?也不必擔心TCP粘包問題。