|
面向?qū)ο笤O(shè)計(jì)vs.實(shí)用主義
這種方法的缺點(diǎn)之一是你必須使用一個(gè)大的switch語(yǔ)句結(jié)束,但是前輩一直教導(dǎo)我們大的switch語(yǔ)句是較差的設(shè)計(jì)的表現(xiàn)。通常的面向?qū)ο螅∣bject Oriented,OO)的途徑是使用多態(tài)性(polymorphism)的。為了達(dá)到這個(gè)目的,我們先建立一個(gè)抽象的基類(base class),接著從該類衍生出所有的消息對(duì)象。每個(gè)類需要執(zhí)行串行化、并行化和處理消息等多個(gè)方法,主要的代碼是:
· 讀取消息類型
· 建立實(shí)例(使用反射)
· 調(diào)用虛HandleMessage()函數(shù)
這樣做是可以實(shí)現(xiàn)的,但是效果很差,我并不喜歡。首先,編寫建立實(shí)例的代碼很難,并且由于它使用了反射,它的速度更慢。更重要的是,消息的處理過(guò)程并不在HandleMessage()函數(shù)之內(nèi),這意味著它必須是共享庫(kù)的一部分。這是不宜使用的,因?yàn)橄⒌奶幚磉^(guò)程與消息如何傳遞沒(méi)有什么關(guān)系。由于這些問(wèn)題的存在,我依然決定使用較少面向?qū)ο蟮歉尤菀拙帉懙耐緩健?br> 前面的示例只處理了單個(gè)消息。在現(xiàn)實(shí)世界中,我們需要同時(shí)處理多個(gè)消息。
服務(wù)器端的多線程
我的最終的目標(biāo)是把該服務(wù)器程序的功能添加到一個(gè)已有的應(yīng)用程序中。因?yàn)椴幌M薷囊延械膽?yīng)用程序的代碼,我就必須在某個(gè)線程上運(yùn)行服務(wù)器程序。同樣,我希望可以同時(shí)接受多個(gè)連接。
上面的例子在端口9999上監(jiān)聽(tīng),但是由于一個(gè)客戶端只能與一個(gè)端口對(duì)話,我需要為每個(gè)連接使用不同端口的途徑。SocketListener類將在9999端口上監(jiān)視,當(dāng)新的連接請(qǐng)求到達(dá)的時(shí)候,它將查找一個(gè)未使用的端口并把它發(fā)送回給客戶端。下面是這個(gè)類的大致情形:
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è)類的構(gòu)造函數(shù)執(zhí)行建立新線程的任務(wù),這個(gè)線程將運(yùn)行WaitForConnection()。打開(kāi)套接字并接受連接與前面的例子相似。下面是該線程的主循環(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++; } } //遠(yuǎn)程用戶應(yīng)該使用specificListener。 //把該端口發(fā)送回給遠(yuǎn)程用戶,并為我們?cè)谠摱丝谏辖⒎⻊?wù)器應(yīng)用程序。 SocketServer socketServer = new SocketServer(specificListener);
writer.Write(userPort); writer.Close(); reader.Close(); stream.Close(); socket.Close(); }
我希望能夠支持多個(gè)連接,因此使用一個(gè)端口以便于客戶端表明它們希望建立一個(gè)連接,接著服務(wù)器程序找到一個(gè)空的端口并把該端口發(fā)送回給客戶端,該端口用于特定客戶端的連接。
我沒(méi)有找到查找未使用端口的方法,因此該While循環(huán)用于找出未使用的端口。接著它把該端口號(hào)發(fā)送回客戶端并清除對(duì)象。
此處還有需要指出的一點(diǎn)點(diǎn)微妙之處。SocketServer的原始版本把端口號(hào)作為一個(gè)參數(shù)。不幸的是,這意味著在該端口上建立監(jiān)聽(tīng)器之前客戶端不能作出請(qǐng)求,這是很不好的。為了防止出現(xiàn)這種情況,我在給客戶端發(fā)送端口號(hào)之前建立了TcpListener,它確保不會(huì)出現(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(); }
這個(gè)主循環(huán)是一個(gè)簡(jiǎn)單的獲取請(qǐng)求/處理請(qǐng)求的循環(huán)。try-catch-finally在此處用于當(dāng)客戶端斷開(kāi)連接的時(shí)候從異常中恢復(fù)過(guò)來(lái)。
客戶端的事件
在客戶端,我編寫了一個(gè)Windows傳統(tǒng)客戶端程序,可以供PC使用也可以供Pocket PC使用。該Windows窗體環(huán)境是基于事件的,而且使用事件處理套接字消息也是理想的。這是通過(guò)SocketClient類實(shí)現(xiàn)的。第一步是為每個(gè)消息定義一個(gè)委托和事件:
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;
當(dāng)事件發(fā)生的時(shí)候就應(yīng)該更新窗體了。為了更可靠,這個(gè)操作需要在主UI線程上發(fā)生。這是通過(guò)調(diào)用窗體的Invoke()實(shí)現(xiàn)的,它將安排在主UI線程上調(diào)用的委托。
因?yàn)檫@種基于消息的體系結(jié)構(gòu),服務(wù)器程序要有對(duì)于異步事件的內(nèi)建的支持。示例有一個(gè)CurrentCount消息,它是由服務(wù)器程序每秒鐘發(fā)送的。
總結(jié)
我對(duì)這個(gè)基于套接字的體系結(jié)構(gòu)很滿意,它是輕量級(jí)的、易于使用的,并且它可以同時(shí)在PC和Pocket P
|
溫馨提示:喜歡本站的話,請(qǐng)收藏一下本站!