摘要:本文討論基於套接字(socket)的體系結構以及怎樣建立一個高效的、易於使用的、可以同時在PC和Pocket PC上運行的消息傳遞(message-passing)系統。
套接字和消息
目前,大多數Web服務和所有的遠程應用程序都使用了遠程過程調用(remote-procedure-call,RPC)方法。你所做工作好像是在調用一個函數,但是在其後台執行了大量的操作以確保它在服務器上發生。在較低的層次,系統是在兩台計算機之間傳遞消息,但這是不可視的。
然而,當你轉換到套接字操作的時候,你就在純粹的基於消息的系統中編程了。這會改變你編寫的代碼的類型,因為讀取返回的數據的唯一途徑是通過消息。它與使用無返回值或輸出參數的.NET類有點類似,在這些情況下所有的返回信息都需要通過事件傳遞。
由於我希望服務器程序告訴客戶端什麼時候應該改變曲目,使用消息就很有利,因為信息可以從服務器到達客戶端,不需要客戶端明確地請求該信息。但是,它要求你使用不同的方式達到目標。
在解釋所有操作之前,我想先談論一點點安全性方面的問題。如果你在自己的計算機上打開了某個端口,其它人可能利用這個端口做不利的事情。他們可能希望寫入沒有意義的信號,以確定自己是否能夠控制你的計算機或者使它崩潰。當你編寫這類應用程序的時候考慮一下這種可能性是必要的。我的例子將運行在防火牆後面的網絡上,所以我感覺到相對安全。
簡單的套接字
我從建立一個服務器程序開始,它能給一個整數加上1,下面是服務器端代碼:
public static void Main() { IPAddress localAddr = IPAddress.Parse("127.0.0.1"); TcpListener listener = new TcpListener(localAddr, 9999); Console.WriteLine("Waiting for initial connection"); listener.Start(); Socket socket = listener.AcceptSocket(); Console.WriteLine("Connected"); NetworkStream stream = new NetworkStream(socket); BinaryReader reader = new BinaryReader(stream); BinaryWriter writer = new BinaryWriter(stream); int i = reader.ReadInt32(); i++; writer.Write(i); }
它首先在本機的9999端口上建立了一個TCP監聽器,接著啟動監聽器並等待連接。一旦得到了連接,它就接收一個整數,給它加1,並把它發送回去。
需要指出的是我在此處使用的本地地址是127.0.0.1。在測試的時候這種情形可以很好地運行,這個時候客戶端和服務器程序在相同的計算機上運行,但是當它們在不同的計算機上運行時,程序就不能運行了。在後面的部分中我將給出更復雜的代碼。
傳遞消息
通過套接字傳遞未經處理的數據毫無樂趣,而通過套接字傳遞對象可能更有趣一些。為了達到這個目標,我們需要一個得到對象並把它轉換為字節流的途徑。最明顯的解決方案是使用運行時(runtime)提供的串行化(serialization)支持。不幸的是,使用這種方法會有少量的問題。
第一個問題是串行化需要很大的開銷,這意味著它使用的字節比傳遞數據必要的字節多一些。如果使用SOAP格式化,這個問題就更嚴重。這是否成為一個問題依賴於應用程序的性能需求。第二個問題是串行化在簡潔框架組件中不能使用。由於沒有簡單的實現方法,我們需要自己做這個工作。在這個過程中,我們做的事情比串行化要少多了。
我們從建立一個枚舉開始,它定義了可以傳遞什麼類型的消息:
public enum MessageType { RequestEmployee = 1, Employee, }
對於每種消息類型,我們需要一個對象定義該對象:
public class RequestEmployee: ISocketObject { int id; public RequestEmployee(int id) { this.id = id; } } public RequestEmployee(BinaryReader reader) { id = reader.ReadInt32(); } public int ID { get { return id; } } public void Send(BinaryWriter writer) { writer.Write((int) MessageType.RequestEmployee); writer.Write(id); } }
我們使用的這種途徑與ISerializable接口很相似。ISocketObject接口定義了一個Send()函數,它串行化通過通道的數據,接著還有一個並行化該數據的構造函數。