面向?qū)ο笤O(shè)計vs.實用主義
這種方法的缺點之一是你必須使用一個大的switch語句結(jié)束,但是前輩一直教導(dǎo)我們大的switch語句是較差的設(shè)計的表現(xiàn)。通常的面向?qū)ο螅∣bject Oriented,OO)的途徑是使用多態(tài)性(polymorphism)的。為了達到這個目的,我們先建立一個抽象的基類(base class),接著從該類衍生出所有的消息對象。每個類需要執(zhí)行串行化、并行化和處理消息等多個方法,主要的代碼是:
· 讀取消息類型
· 建立實例(使用反射)
· 調(diào)用虛HandleMessage()函數(shù)
這樣做是可以實現(xiàn)的,但是效果很差,我并不喜歡。首先,編寫建立實例的代碼很難,并且由于它使用了反射,它的速度更慢。更重要的是,消息的處理過程并不在HandleMessage()函數(shù)之內(nèi),這意味著它必須是共享庫的一部分。這是不宜使用的,因為消息的處理過程與消息如何傳遞沒有什么關(guān)系。由于這些問題的存在,我依然決定使用較少面向?qū)ο蟮歉尤菀拙帉懙耐緩健?br> 前面的示例只處理了單個消息。在現(xiàn)實世界中,我們需要同時處理多個消息。
服務(wù)器端的多線程
我的最終的目標是把該服務(wù)器程序的功能添加到一個已有的應(yīng)用程序中。因為不希望修改已有的應(yīng)用程序的代碼,我就必須在某個線程上運行服務(wù)器程序。同樣,我希望可以同時接受多個連接。
上面的例子在端口9999上監(jiān)聽,但是由于一個客戶端只能與一個端口對話,我需要為每個連接使用不同端口的途徑。SocketListener類將在9999端口上監(jiān)視,當新的連接請求到達的時候,它將查找一個未使用的端口并把它發(fā)送回給客戶端。下面是這個類的大致情形:
public class SocketListener { int port; Thread thread;
public SocketListener(int port) { this.port = port; ThreadStart ts = new ThreadStart(WaitForConnection); thread = new Thread(ts); thread.IsBackground = true; thread.Start(); }
public void WaitForConnection() { // 主要的代碼 } }
WaitForConnection()是執(zhí)行所有這些操作的方法。這個類的構(gòu)造函數(shù)執(zhí)行建立新線程的任務(wù),這個線程將運行WaitForConnection()。打開套接字并接受連接與前面的例子相似。下面是該線程的主循環(huán):
while (true) { Console.WriteLine("Waiting for initial connection"); listener.Start(); Socket socket = listener.AcceptSocket(); NetworkStream stream = new NetworkStream(socket); BinaryReader reader = new BinaryReader(stream); BinaryWriter writer = new BinaryWriter(stream);
Console.WriteLine("Connection Requested");
int userPort = port + 1; TcpListener specificListener; while (true) { try { specificListener = new TcpListener(localAddr, userPort); specificListener.Start(); break; } catch (SocketException) { userPort++; } } //遠程用戶應(yīng)該使用specificListener。 //把該端口發(fā)送回給遠程用戶,并為我們在該端口上建立服務(wù)器應(yīng)用程序。 SocketServer socketServer = new SocketServer(specificListener);
writer.Write(userPort); writer.Close(); reader.Close(); stream.Close(); socket.Close(); }
我希望能夠支持多個連接,因此使用一個端口以便于客戶端表明它們希望建立一個連接,接著服務(wù)器程序找到一個空的端口并把該端口發(fā)送回給客戶端,該端口用于特定客戶端的連接。
我沒有找到查找未使用端口的方法,因此該While循環(huán)用于找出未使用的端口。接著它把該端口號發(fā)送回客戶端并清除對象。
此處還有需要指出的一點點微妙之處。SocketServer的原始版本把端口號作為一個參數(shù)。不幸的是,這意味著在該端口上建立監(jiān)聽器之前客戶端不能作出請求,這是很不好的。為了防止出現(xiàn)這種情況,我在給客戶端發(fā)送端口號之前建立了TcpListener,它確保不會出現(xiàn)這種緊急情況。
SocketServer類建立了額外的線程,并使用了下面的主循環(huán):
try { while (true) { MessageType messageType = (MessageType) reader.ReadInt32();
switch (messageType) { case MessageType.RequestEmployee: Employee employee = new Employee("Eric Gunnerson", "One Microsoft Way"); employee.Send(writer); break; } } } catch (IOException) {
} finally { socket.Close(); }
這個主循環(huán)是一個簡單的獲取請求/處理請求的循環(huán)。try-catch-finally在此處用于當客戶端斷開連接的時候從異常中恢復(fù)過來。
客戶端的事件
在客戶端,我編寫了一個Windows傳統(tǒng)客戶端程序,可以供PC使用也可以供Pocket PC使用。該Windows窗體環(huán)境是基于事件的,而且使用事件處理套接字消息也是理想的。這是通過SocketClient類實現(xiàn)的。第一步是為每個消息定義一個委托和事件:
public delegate void EmployeeHandler(Employee employee); public event EmployeeHandler EmployeeReceived;
第二步是編寫發(fā)送事件的代碼:
case MessageType.Employee: Employee employee = new Employee(reader); if (EmployeeReceived != null) form.Invoke(EmployeeReceived, new object[] {employee}); break;
當事件發(fā)生的時候就應(yīng)該更新窗體了。為了更可靠,這個操作需要在主UI線程上發(fā)生。這是通過調(diào)用窗體的Invoke()實現(xiàn)的,它將安排在主UI線程上調(diào)用的委托。
因為這種基于消息的體系結(jié)構(gòu),服務(wù)器程序要有對于異步事件的內(nèi)建的支持。示例有一個CurrentCount消息,它是由服務(wù)器程序每秒鐘發(fā)送的。
總結(jié)
我對這個基于套接字的體系結(jié)構(gòu)很滿意,它是輕量級的、易于使用的,并且它可以同時在PC和Pocket P
|