作者:胡慧聖 謝曉方
在某新型全分布式艦艇指揮控制系統的設計中,需要實現圖1所示的通訊結構,圖中的連線表示系統在工作過程中可能的串連,可在系統運行期間動態串連或斷開。實現該系統的核心問題是設計一個具有一定通用性的通訊介面模組。
圖1 全分布式系統通訊結構
Windows NT提供了兩種主要的網路編程介面:管道和Windows Sockets。其中Windows Sockets的目的是為了實現對網路細節的屏蔽,程式員不用關心網路的具體實現和具體協議。雖然Windows Socket只定義了對TCP/IP協議的抽象說明,但任何協議族都可以遵循Windows Sockets,只要它提供有關實現的動態連結程式庫。
鑒於系統的工作特點決定採用Windows Sockets的客戶/伺服器模式來實現網路通訊。其中雷達等探測器和火炮等武器裝置都作為伺服器,只等待顯控台的串連請求而不主動請求串連;各顯控台既為伺服器又為客戶機,隨時可向任一單元發出串連請求,又可隨時接受其它顯控台的串連請求。而這些網路功能是通過設計一個通訊線程類來實現的。
1 通訊線程類CComThread類的實現
在Visual C++的MFC中,CAsyncSocket類描述了基本的Windows Sockets編程介面, CSocket類是從CAsyncSocket類派生而來的,它是在更高層次上對通訊端的抽象,並可使用MFC的CArchive類,使得進程間的通訊類似於MFC中對檔案的序列化。因此,使用CSocket類實現通訊線程類。通訊線程類從CWinThread類派生,以下簡要論述其實現的基本步驟:
1.1從CSocket類派生CListeningSocket類和
CConnectedSocket類
CListeningSocket類對象用於伺服器偵聽網路連接請求。其主要成員有通訊線程的指標m_pComThread,在建構函式中賦值,用於向通訊線程發送訊息。該類重載基類的OnAccept( )通知函數,即在有網路連接請求時通知通訊線程。
CConnectedSocket類對象用於代理已建立串連的通訊任務。其主要成員有:通訊線程的指標和擁有該串連的本地視窗指標用於發送各種訊息;通訊端號用於在通訊線程中標識已串連通訊端;兩個檔案對象指標用於該通訊串連的資料接收和發送。其主要方法有:初始化Init( )函數用於指定通訊線程指標和本地視窗指標;重載基類OnReceive( )通知函數用於通知通訊線程接收資料;重載基類OnClose( )函數用於通知通訊線程串連或已經斷開。
1.2 從CObject類派生CMsg類和CMsgHead類
CMsg類用於描述訊息基類,任何在網上傳輸的資料訊息都要從該類派生。其資料成員以訊息名標識,而具體的資料訊息類需要有自己要傳輸的資料成員。其方法為序列化函數,用於序列化訊息名標識;而具體的資料訊息類需要重載序列化函數,並在其中調用基類的序列化函數以及序列化其它需要傳輸的資料成員。
CMsgHead類用於描述訊息頭類,通訊線程在發送資料前都要先發送訊息頭對象,用以標識將要發送的資料訊息類型。其成員和方法與CMsg類相近。
1.3 CComThread類的主要資料成員
CListeningSocket類對象指標為m_pListeningSocket,當通訊線程作為伺服器時令其偵聽網路連接請求;指標鏈表m_ConnectedSocketList用於儲存已建立串連的CConnectedSocket對象指標,通訊線程根據此鏈表尋找特定的通訊串連來發送資料。計數器m_PipeNumber用於標識當前建立串連的通訊端號,m_pWnd用於儲存伺服器視窗指標,幾個臨時對象指標用於傳遞訊息對象指標,以及用於接受或請求串連的臨時CConnectedSocket對象指標。
1.4 CComThread類主要方法的實現
CreateSocket( )建立通訊端訊息處理函數:在通訊線程的使用者希望成為伺服器時,向通訊線程發建立通訊端訊息,在該訊息處理函數中,通訊線程根據指定連接埠號碼構造m_pListeningSocket對象並開始偵聽。
ConnectSocket( )串連請求訊息處理函數:客戶視窗通過發送此訊息向伺服器發出串連請求。在該函數中,通訊線程構造一個CConnectedSocket對象,並根據指定伺服器位址和連接埠號碼發出串連請求,若串連成功則計數器計數,設定通訊端號並將該通訊端加入通訊端鏈表,並通知客戶視窗串連成功及該串連的通訊端號。
ProcessPendingAccept( )接受串連方法:當伺服器的偵聽通訊端收到串連請求時調用該函數,其代碼如下:
CConnectedSocket* pSocket = new CConnectedSocket(this);
if (m_pListeningSocket->Accept(*pSocket))
{
m_PipeNumber ++;
pSocket->Init(m_PipeNumber,m_SourceWnd);
m_ConnectedSocketList.AddTail(pSocket);
m_pWnd -> PostMessage( MM_LISTEN_ACCEPT, m_PipeNumber,NULL) ;
}
else delete pSocket;
SendMsg( )發送資料訊息處理函數:當通訊的一方要發送資料時,向通訊線程發出發送資料訊息,傳遞訊息對象指標和通訊端號,訊息處理函數代碼如下:
void CComThread::SendMsg(CMsg* pMsg,UINT nPipeID)
{
for(POSITIONpos=m_ConnectedSocketList.
GetHeadPosition( );pos!=NULL;)
{
CConnectedSocket* pSocket
=( CConnectedSocket*) m_ConnectedSocketList. GetNext( pos );
if( pSocket -> m_PipeID = = nPipeID )
{
CMsgHead MsgHead( pMsg -> m_MsgName );
MsgHead.Serialize(*( pSocket -> m_pArchiveOut ) );
pMsg -> Serialize(*( pSocket -> m_pArchiveOut ) );
pSocket -> m_pArchiveOut -> Flush( );
pos = NULL;
}
}
}
ProcessPendingRead( )接收資料方法:當串連通訊端鏈表中的某一通訊端有資料等待接收時,調用此方法。該方法先調用:
MsgHead.Serialize(*(pSocket->m_pArchiveIn));
讀入訊息頭,並根據訊息頭中訊息名構造訊息類對象,再調用:m_pMsg->Serialize(*(pSocket->m_pArchiveIn));
讀出訊息對象,最後通過調用:
pSocket->m_pWnd->PostMessage(MM_RECEIVE_MSG,(long)MsgName,(long)m_pMsg);
通知數據傳輸目的視窗接收資料。
DeleteConnect( )中斷連線處理函數:當通訊的一方想中斷連線或另一方已中斷連線時,調用該訊息處理函數。在函數中,通訊線程根據指定要斷開的串連通訊端號,在已串連通訊端鏈表中尋找該通訊端並將其刪除。
2 結束語
這種方法實現的通訊線程具有編程簡單、運行可靠等優點,基本能滿足設計需要,並能較好地滿足通用性要求。