先看一個截圖。

上面的圖,各位乍一看,可能會覺得是用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粘包問題。