Visual C#建立簡單消息傳遞系統(2)
2024-07-21 02:20:26
供稿:網友
面向對象設計vs.實用主義
這種方法的缺點之一是你必須使用一個大的switch語句結束,但是前輩一直教導我們大的switch語句是較差的設計的表現。通常的面向對象(object oriented,oo)的途徑是使用多態性(polymorphism)的。為了達到這個目的,我們先建立一個抽象的基類(base class),接著從該類衍生出所有的消息對象。每個類需要執行串行化、并行化和處理消息等多個方法,主要的代碼是:
· 讀取消息類型
· 建立實例(使用反射)
· 調用虛handlemessage()函數
這樣做是可以實現的,但是效果很差,我并不喜歡。首先,編寫建立實例的代碼很難,并且由于它使用了反射,它的速度更慢。更重要的是,消息的處理過程并不在handlemessage()函數之內,這意味著它必須是共享庫的一部分。這是不宜使用的,因為消息的處理過程與消息如何傳遞沒有什么關系。由于這些問題的存在,我依然決定使用較少面向對象但是更加容易編寫的途徑。
前面的示例只處理了單個消息。在現實世界中,我們需要同時處理多個消息。
服務器端的多線程
我的最終的目標是把該服務器程序的功能添加到一個已有的應用程序中。因為不希望修改已有的應用程序的代碼,我就必須在某個線程上運行服務器程序。同樣,我希望可以同時接受多個連接。
上面的例子在端口9999上監聽,但是由于一個客戶端只能與一個端口對話,我需要為每個連接使用不同端口的途徑。socketlistener類將在9999端口上監視,當新的連接請求到達的時候,它將查找一個未使用的端口并把它發送回給客戶端。下面是這個類的大致情形:
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()是執行所有這些操作的方法。這個類的構造函數執行建立新線程的任務,這個線程將運行waitforconnection()。打開套接字并接受連接與前面的例子相似。下面是該線程的主循環:
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++;
}
}
//遠程用戶應該使用specificlistener。
//把該端口發送回給遠程用戶,并為我們在該端口上建立服務器應用程序。
socketserver socketserver = new socketserver(specificlistener);
writer.write(userport);
writer.close();
reader.close();
stream.close();
socket.close();
}
我希望能夠支持多個連接,因此使用一個端口以便于客戶端表明它們希望建立一個連接,接著服務器程序找到一個空的端口并把該端口發送回給客戶端,該端口用于特定客戶端的連接。
我沒有找到查找未使用端口的方法,因此該while循環用于找出未使用的端口。接著它把該端口號發送回客戶端并清除對象。
此處還有需要指出的一點點微妙之處。socketserver的原始版本把端口號作為一個參數。不幸的是,這意味著在該端口上建立監聽器之前客戶端不能作出請求,這是很不好的。為了防止出現這種情況,我在給客戶端發送端口號之前建立了tcplistener,它確保不會出現這種緊急情況。
socketserver類建立了額外的線程,并使用了下面的主循環:
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();
}
這個主循環是一個簡單的獲取請求/處理請求的循環。try-catch-finally在此處用于當客戶端斷開連接的時候從異常中恢復過來。
客戶端的事件
在客戶端,我編寫了一個windows傳統客戶端程序,可以供pc使用也可以供pocket pc使用。該windows窗體環境是基于事件的,而且使用事件處理套接字消息也是理想的。這是通過socketclient類實現的。第一步是為每個消息定義一個委托和事件:
public delegate void employeehandler(employee employee);
public event employeehandler employeereceived;
第二步是編寫發送事件的代碼:
case messagetype.employee:
employee employee = new employee(reader);
if (employeereceived != null)
form.invoke(employeereceived, new object[] {employee});
break;
當事件發生的時候就應該更新窗體了。為了更可靠,這個操作需要在主ui線程上發生。這是通過調用窗體的invoke()實現的,它將安排在主ui線程上調用的委托。
因為這種基于消息的體系結構,服務器程序要有對于異步事件的內建的支持。示例有一個currentcount消息,它是由服務器程序每秒鐘發送的。
總結
我對這個基于套接字的體系結構很滿意,它是輕量級的、易于使用的,并且它可以同時在pc和pocket pc上運行。