摘 要: 提出了一種在Windows NT下基于TCP/IP協(xié)議的多線程通信的設計與實現(xiàn)方法,在此基礎上給出了多線程通信在蓄電池遠程監(jiān)控系統(tǒng)中的應用實例,。
關鍵詞: 多線程 實時性 TCP/IP協(xié)議 遠程監(jiān)控系統(tǒng)
傳統(tǒng)的應用程序都是單線程的,,即在程序運行期間,由單個線程獨占CPU的控制權,,負責執(zhí)行所有任務。在這種情況下,,程序在執(zhí)行一些比較費時的任務時,,就無法及時響應用戶的操作,影響了應用程序的實時性能,。在監(jiān)控系統(tǒng),,特別是遠程監(jiān)控系統(tǒng)中,應用程序往往不但要及時把監(jiān)控對象的最新信息反饋給監(jiān)視客戶(通過圖形顯示),,還要處理本地機與遠程機之間的通信以及對控制對象的實時控制等任務,,這時 ,僅僅由單個線程來完成所有任務,,顯然無法滿足監(jiān)控系統(tǒng)的實時性要求,。在DOS系統(tǒng)下,,這些工作可以由中斷來完成。而在Windows NT下,,中斷機制對用戶是不透明的,。為此,可引進多線程機制,,主線程專門負責消息的響應,,使程序能夠響應命令和其他事件。輔助線程可以用于完成其他比較費時的工作,,如通信,、圖形顯示和后臺打印等,這樣就不至于影響主線程的運行,。
1 Windows NT 多線程概述
Windows NT是一個真正的搶占式多任務操作系統(tǒng),。在Windows NT中,啟動一個應用程序就是啟動該應用程序的一個實例,,即進程,。進程由一個或多個線程構成,擁有內存和資源,,但自己不能執(zhí)行自己,,而是進程中的線程被調度執(zhí)行。進程至少要有一個線程,,當創(chuàng)建一個進程時,,就創(chuàng)建了一個線程,即主線程,。主線程可以創(chuàng)建其他輔助線程,,由主線程創(chuàng)建的線程又可創(chuàng)建線程。每個線程都可指定優(yōu)先級,,操作系統(tǒng)根據(jù)線程的優(yōu)先級調度線程的執(zhí)行,。
Windows NT中使用多線程的方法有三種:
· 使用C多線程庫函數(shù);
· 使用CreateThread() 等Win32函數(shù),;
· 使用MFC類,。
本文采用第三種方法。在Visual C++5.0 中,,MFC應用程序用CWinThread 對象表示線程,。基本操作如下:
· 創(chuàng)建新線程:調用MFC全局函數(shù)AfxBeginThread( )創(chuàng)建新線程,。AfxBeginThread( )啟動新線程并返回控制,,然后,,新線程和調用AfxBeginThread( )的線程同時運行,。它的返回值為指向CWinThread對象的指針,;
· 暫停/恢復線程:調用CWinThread類成員函數(shù)SuspendThread( )暫停線程的運行,調用ResumeThread( )成員函數(shù)恢復線程的運行,;
· 終止線程:在線程內部可調用全局函數(shù)AfxEndThread( )終止線程的運行,,否則,線程執(zhí)行結束后,,線程自動從線程函數(shù)返回并釋放線程占有的資源,。
2 基于TCP/IP 的多線程編程
TCP/IP是Internet上廣泛使用的一種協(xié)議,可用于異種機之間的互聯(lián),。TCP/IP協(xié)議本身是非常復雜的,,然而在網(wǎng)絡編程中,程序員不必考慮TCP/IP的實現(xiàn)細節(jié),,只需利用協(xié)議的網(wǎng)絡編程接口Socket(亦稱套接字)即可,。在Windows中,網(wǎng)絡編程接口是Windows Sockets,它包含標準的Berkley Sockets的功能調用的集合,,以及為Windows 所做的一些擴展,。TCP/IP協(xié)議的應用一般采用客戶/服務器模式,面向連接的應用調用如圖1所示,。
根據(jù)上述順序調用函數(shù)建立連接后,,通信雙方便可交換數(shù)據(jù)[1]。然而,,在調用帶*號的函數(shù)時,,操作常會阻塞,特別是當套接字工作在同步阻塞模式(Blocking Mode)時,。這時,,程序無法響應任何消息。為了避免出現(xiàn)這種情況,,本文引進輔助線程,。在執(zhí)行含有可能阻塞的函數(shù)的任務時,動態(tài)創(chuàng)建新的線程,,專門處理該任務,。主線程把任務交給輔助線程后,不再對輔助線程加以控制與調度,。本文分別針對connect(),、accept()、receive(),、send()等可能阻塞的函數(shù)創(chuàng)建了相應的線程,,如表1所示。
多線程編程常常還要考慮線程間的通信,。線程間的通信可以采用全局變量,、指針參數(shù)和文件映射等方式,。本文采用指針參數(shù)方式。在調用AfxBeginThread()函數(shù)時,,通過傳遞指針參數(shù)的方式在主線程與輔助線程間通信,。
AfxBeiginThread( )函數(shù)的用法如下:
CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSize=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL );
參數(shù)pfnThreadProc指定線程函數(shù),必須如下定義:
UINT MyControllingFunction( LPVOID pParam );
參數(shù)pParam 是調用線程傳遞給線程函數(shù)pfnThreadProc的參數(shù);
其他參數(shù)一般只需采用缺省值,。
指針參數(shù)通信方式就是通過參數(shù)pParam在線程間通信的,,它可為指向任何數(shù)據(jù)類型的指針。本文中,,定義了一個名叫EXCHANGE_INFO的結構如下:
typedef struct
{ SOCKET sServerSocket;
SOCKET *psClientSocket;
SOCKADDR_IN *pClientAddr;
BOOL *pbConnected;
unsigned char *pucBuffer;
int *pnMessageLen;
} EXCHANGE_INFO,;
在需要通信時,先聲明一個結構變量,,再把變量的指針作為pParam參數(shù),,調用AfxBeginThread((AFX_THREADPROC) CSocketThread::WaitForConnectThread, (LPVOID)& m_ExchangeInfo)函數(shù)即可。
為了利用面向對象技術編程所具有的模塊性強,、便于修改,、可移植性好等優(yōu)點,本文還把表1中的線程封裝為父類為CWinThread的自定義類CSocketThread中,。還自定義了一個叫CSocketComm的新類,,封裝了一些函數(shù),如CreateSocket,、ConnectToServer,、WaitForClient、ReadMessage,、SendMessage等,,這些函數(shù)屏蔽了面向連接的通信程序的實現(xiàn)細節(jié),如創(chuàng)建,、連接,、發(fā)送和接收等,在這些函數(shù)里,,動態(tài)創(chuàng)建輔助線程,。
下面以CSocketComm類中的等待客戶連接請求的函數(shù)WaitForClient()為例,注釋說明多線程編程的具體細節(jié),。
BOOL CSocketComm::WaitForClient()
{
if( m_bConnected ) return( TRUE );
//配置bind函數(shù)的參數(shù),即服務器的套接字地址結構
SOCKADDR_IN Addr;
memset( &Addr, 0, sizeof( SOCKADDR_IN ) );
Addr.sin_family = AF_INET;
Addr.sin_port = htons( m_nPort );
Addr.sin_addr.s_addr = htonl( INADDR_ANY );
//將套接字地址結構賦予套接字(綁定),,以指定本地半相關
int nReturnValue;
nReturnValue = ::bind( m_sServerSocket, (LPSOCKADDR) &Addr,sizeof( SOCKADDR_IN ) );
if( nReturnValue == SOCKET_ERROR ) return( FALSE );
//配置傳給WaitForConnectThread線程函數(shù)的參數(shù)m_ExchangeInfo
m_ExchangeInfo.sServerSocket = m_sServerSocket;
m_ExchangeInfo.psClientSocket = &m_sClientSocket;
m_ExchangeInfo.pClientAddr = &m_ClientAddr;
m_ExchangeInfo.pbConnected = &m_bConnected;
//以m_ExchangeInfo的指針為參數(shù)調用WaitforConnectThread線程等待客戶端連接
AfxBeginThread((AFX_THREADPROC)CSocketThread::
WaitForConnectThread, (LPVOID) & m_ExchangeInfo );
return( TRUE );
}
//等待連接線程
UINT CSocketThread::WaitForConnectThread(LPVOIDpParam)
{
EXCHANGE_INFO*pExchangeInfo=(EXCHANGE_INFO *) pParam;
int nReturnValue, nClientAddrSize = sizeof( SOCKADDR_IN );
//偵聽連接
nReturnValue = ::listen(pExchangeInfo ->sServerSocket, 1 );
if( nReturnValue == SOCKET_ERROR ) return( 0 );
//阻塞調用accept,直至有客戶連接請求
*pExchangeInfo->psClientSocket = ::accept(pExchangeInfo->sServerSocket, (LPSOCKADDR) pExchangeInfo ->pClientAddr, &nClientAddrSize );
if( (*pExchangeInfo ->psClientSocket) != INVALID_SOCKET )
//通過pExchangeInfo的指針在線程間通信
*pExchangeInfo->pbConnected = TRUE;
return( 0 );
}
3 應用實例-高層協(xié)議的設計
在電廠和電站中,,為了保證安全工作,,保護系統(tǒng)必不可少。保護系統(tǒng)的電源供應通常使用兩種方式。一般情況下,,使用交流電系統(tǒng)對保護系統(tǒng)進行供電,;當交流電系統(tǒng)出現(xiàn)故障時,立即使用后備的蓄電池系統(tǒng)對保護系統(tǒng)進行供電。為了對蓄電池系統(tǒng)進行監(jiān)控和管理,,以保證蓄電池在關鍵時刻能正常工作,設計了在Windows NT環(huán)境下具有遠程通訊功能和動態(tài)人機界面的智能蓄電池遠程監(jiān)控系統(tǒng) ,。該系統(tǒng)由蓄電池智能管理,、充電機控制、母線絕緣在線檢測,、聲光報警,、系統(tǒng)組態(tài)、遠程通信等子系統(tǒng)組成,,實現(xiàn)對蓄電池/充電機智能化遠程管理和控制,,對整個系統(tǒng)的運行狀態(tài)進行實時監(jiān)控,具有多媒體報警,、事件處理,、動態(tài)數(shù)據(jù)庫、趨勢畫面和動態(tài)畫面顯示,、操作提前提醒等功能,。系統(tǒng)框圖如圖2所示。在遠程通信模塊中,,遠程監(jiān)控機需把監(jiān)控客戶的操作命令及時傳給本地機,,本地機根據(jù)命令控制充電機,使之按照一定的方式工作,,而本地機需定時向遠程監(jiān)控機反饋實時的充電機狀態(tài)信息,。它們之間的通信是基于TCP/IP的廣域網(wǎng)通信,而且,,我們引進了多線程機制以保證系統(tǒng)具有良好的實時性,。
下面以其中的充電機控制系統(tǒng)為例談談如何使用CSocketComm類進行遠程通信。為簡單起見,,假定本地機與遠程監(jiān)控機之間通信的信息僅有下面三種類型:
·本地機接收到該命令后,,控制充電機按照穩(wěn)壓模式運行,輸出電壓為電壓給定值,;
·本地機接收到該命令后,,控制充電機按照穩(wěn)流定時模式運行,輸出電流為電流給定值,;
·本地機向遠程監(jiān)控機發(fā)送充電機的實時狀態(tài)數(shù)據(jù)(包括輸出電壓,、輸出電流、狀態(tài)指示和故障類型指示)。
在基于TCP/IP的面向連接的網(wǎng)絡通信中,,客戶與服務器之間傳送的是有序可靠的字節(jié)流(Byte Stream),,所以程序員有必要在傳輸層TCP上定義自己的高層協(xié)議,設計幀結構,,將字節(jié)流變成有意義的信息,。在CSocketComm類中由AssembleMessage( )函數(shù)把數(shù)據(jù)組合成一定的幀結構。幀結構為:
其中@為幀起始標志,,#為幀終結標志
對應的結構定義如下:
typedef struct
{ int MessageType; //信息類型
int ChargerNo; //充電機編號
int DataNo; //數(shù)據(jù)類型
float Data; //數(shù)據(jù)
} MessageStruct;
需要通信時,,先聲明一個MessageStruct變量,根據(jù)信息內容對各成員變量賦值,,傳給 AssembleMessage()函數(shù)組合成幀,,再調用SendMessage()函數(shù)發(fā)送給接受方。接受方接到數(shù)據(jù)后,,對數(shù)據(jù)內容的解釋,,是由CSocketComm類中的AnalyzeMessage()函數(shù)完成的。AnalyzeMessage()函數(shù)返回一個MessageStruct變量,。應用程序就可根據(jù)它的各成員變量控制充電機或動態(tài)顯示充電機的狀態(tài),。
總之,把多線程機制引進通信,,有利于提高應用程序的實時性,,充分利用系統(tǒng)資源。對于大型的工程應用來說,,不同的線程完成不同的任務,,也有利于提高程序的模塊化,便于維護和擴展,。本文給出了一種在Windows NT下基于TCP/IP協(xié)議的多線程通信的基本方法,,根據(jù)該方法進行修改和擴充,便可設計出符合具體應用的高質量的多線程通信程序,。
參考文獻
1 蔣東興,,林鄂華.Windows Socket 網(wǎng)絡程序設計指南.北京:清華大學出版社,1995
2 Rajagopal Raj, Monica Subodh P.Windows NT4高級程序設計.北京:機械工業(yè)出版社,,1998