李光明
(西安政治學院,陜西 西安710068)
衛星遙測數據處理系統是基于C/S的體系結構,分為遙測參數處理軟件和遙測參數處理結果顯示軟件,兩個軟件模塊可在同一機器上或不同機器上獨立運行,通過SOCKET鏈路交換數據,實時接收有效載荷管理工作站中的遙測數據,完成實時處理。處理結果實時回送有效載荷管理工作站進行入庫處理,同時以組廣播的形式向所有的顯示工作站實時傳送數據,供監視衛星運行情況。本文將詳細討論這項技術,同時列舉出筆者在SOCKET編程過程中的幾點經驗。
Windows NT提供了一個最重要的通信程序設計機制——Windows Sockets(WinSock),使我們在網絡通信編程上有很大的發揮空間。
一個套接字(Socket)是一個通信端點。典型的通信發生于一個客戶和一個服務器之間,就有兩個端點,一個在客戶端,一個在服務器端。對應的就有兩個套接字,且這兩個套接字在客戶和服務器之間建立了雙向數據傳送的連接。
套接字基本上分為兩類:流套接字、數據報套接字。流套接字(Stream Sockets)用于大流量數據的雙向傳輸,數據流可分為記錄流或字節流,這取決于協議。流通常用于無重復(UnDuplicated)的和順序(Sequenced,保持包發送順序)的傳送和接收數據。流套接字保證數據發送。數據報套接字(Datagram Sockets)主要用于廣播功能。數據報套接字支持雙向數據流,不保證可靠、有序、無重復性,是面向無連接的傳輸機制。
套接字應用程序可以使用一個端口(port)與其它套接字應用程序通信。端口的含義可以這樣理解:它的作用是可以實現在具有一個IP地址的單臺機器上同時有效地運行多個客戶或演示軟件,各個到達的TCP包或UDP包都被指定給某一特定的端口。例如,可以在一個窗口中執行FTP,同時在另一個窗口運行自己的套接字應用程序或其它通信程序,確保不同通信程序的數據不被混淆在一起的機制就是端口。公用通信功能使用保留端口,用戶可指定未被保留且未被使用的端口,或傳遞0作為端口值由Sockets自動分配端口。
每個套接字還有一個套接字地址,通常是應用程序運行所在計算機的IP地址。
Socket實際上代表了IP地址和端口號的組合,變成了通信中一種抽象化的終端節點。
套接字通信通常分為三個階段:
(1)執行安裝功能。創建并綁定一個套接字,定位并與遠程計算機建立一個套接字連接。
(2)發送和接收數據。若正在編寫一個服務類型的套接字應用程序,則可創建一個套接字并監聽從客戶來的套接字連接輸入。若有多個用戶想同步的建立連接,則可請求積壓連接請求。
(3)執行清除功能。斷開和關閉套接字連接。
與其它的Windows程序設計領域一樣,可用API或MFC庫對Windows Sockets編程。WinSock使用TCP/IP協議,但TCP/IP并不是 WinSock支持的唯一協議,它還支持 Novell的IPX/SPX、Digital的DECNet和除TCP/IP之外的其它協議。在TCP/IP協議組中,TCP是一種面向連接的協議,為用戶提供可靠的、全雙工的字節流服務,具有確認、流控制、多路復用和同步等功能,適于數據傳輸。而UDP協議則是無連接的,每個分組都攜帶完整的目的地址,各分組在系統中獨立傳送。它不能保證分組的先后順序,不進行分組出錯的恢復與重傳,因此不能保證傳輸的可靠性,但是,它提供高傳輸效率的數據報服務,適于實時的語音、圖像傳輸、廣播消息等網絡傳輸。
本文主要以 MFC下 Windows Sockets編程為例,介紹一般使用方法及注意事項。
MFC下提供兩種SOCKET類型供用戶使用:SOCK_STREAM、SOCK_DGRAM,相應有兩種類CasyncSocket、Csocket可供使用。
Csocket封裝的socket使用TCP協議,提供有序的、可靠的、雙向的、連接的、無重復并且無記錄邊界的比特流傳輸機制。CasyncSocket封裝的socket使用UDP協議,支持雙向數據流,但并不保證是可靠、有序、無重復的,為面向無連接的數據報傳輸機制。
MFC下客戶和服務器利用面向連接的Csocket進行通信的過程、具體編程細節參考MFC下Chatter、Chatsrvr程序示例。
一般來說,客戶方在OnReceive()中處理數據,服務器方在監聽Socket的OnAccept()中處理客戶連接,并在其中建立數據處理Socket,數據處理Socket在OnReceive()中處理數據。
客戶和服務器利用CasyncSocket進行通信的過程,UDP協議下的通信較簡單,不需要事先建立連接,而是通過數據定向發送、接收實現服務器/客戶通信。
3.1.1 一般過程
以MFC下異步SOCKET創建為例:
(1)首先聲明SOCKET對象
CAsyncSocket socket。
(2)創建SOCKET
socket.Create(UINT nSocketPort = 0,int nSocketType=SOCK_DGRAM,long lEvent= FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE,LPCTSTR lpszSocketAddress=NULL);
其中,nSocketPort是此socket綁定的端口號,區分不同的應用程序;nSocketType是socket類型,此處應用UDP協議,選用數據報類型;lEvent指明此socket需要反應的消息,可重載相應的消息映射函數做出動作。
下面的討論主要針對nSocketPort、lpszSocket-Address兩個參數的指定,為敘述簡便,標識nSocket-Port為端口,標識lpszSocketAddress為IP地址。記住,使用該類進行socket創建后,不要再調用bind()函數,因為MFC的CasyncSocket類已經將socket的create()和bind()封裝在一起,形成Create()。
3.1.2 注意事項
(1)在同一計算機、同一應用程序中創建兩個CAsyncSocket:socket1、socket2
a.socket1、socket2不能指定同一端口、同一地址;
b.socket1、socket2指定相同端口、但地址必須不同;
c.socket1、socket2指定相同地址、但端口必須不同;
d.若socket1調用Create()創建,指明端口,但未指明IP地址。則socket1的IP地址不能再改變,除非它是已連接的或I/O正在產生,否則得不到地址;socket2不能指定該端口、指定本機任一IP地址創建;socket2可以更換端口、指定本機任一IP地址創建;socket2可以更換端口、不指定IP地址創建。
(2)創建一個CAsyncSocket:socket
不要試圖嘗試以任何方式再次創建同一個socket,任何方式指端口、IP地址的組合方式,即:
不能以相同端口、不同IP地址再次創建該socket;
不能以相同端口、相同IP地址再次創建該socket;
不能以不同端口、相同IP地址再次創建該socket;
不能以不同端口、不同IP地址再次創建該socket。
組廣播通信可以實現一臺或多臺機器向網絡中的多臺機器發送數據信息,且這種方式對發送端來說編程簡潔,通過向一個單一組地址發送來實現點對多點通信。
3.2.1 組廣播通信原理
TCP/IP協議地址分配中的D類地址即為多目地址(multicast address),范圍是224.0.0.0~239.255.255.255。組廣播通信雙方認知一個D類地址中的組廣播地址,發送數據向目的端口。該地址發送數據,接收數據方將本機IP地址加入該組地址中,并在該端口上建立SOCKET等待數據接收。數據以廣播方式發送到各臺聯網計算機,由協議判斷該由哪臺計算機接收數據,判斷的依據就是本機IP地址與組廣播地址的關系。
3.2.2 組廣播實現
組廣播SOCKET的建立涉及到SOCKET屬性的設置,即函數
int setsockopt (SOCKET s,int level,int optname,const char FAR * optval,int optlen)
設置了SOCKET的屬性,創建組SOCKET時,level設置為IPPROTO_IP,optname設置為IP_ADD_MEMBERSHIP,optval類型為struct ip_mreq,組地址和本機地址就在該結構中指定,下面具體給出實例,函數和結構的具體信息可查VC幫助。
//接收方加入組地址實例
CAsyncSocket Socket; //聲明異步SOCKET
Socket.Create(5000,SOCK_DGRAM); //以端口5000,缺省地址創建SOCKET
struct ip_mreq bindGroup; //聲明結構
bindGroup.imr_multiaddr.s_addr=inet_addr(“232.20.1.1”); //填充組地址
bindGroup.imr_interface.s_addr=inet_addr(“10.3.17.13”); //填充本機地址
//設置SOCKET屬性
int status= setsockopt(Socket->m_hSocket,IPPROTO_IP,
IP_ADD_MEMBERSHIP,
(char*)&bindGroup,sizeof(struct ip_mreq));
if(status==SOCKET_ERROR)//出錯處理
{
int err= GetLastError();
CString mes;
mes.Format("Add Group Failed,Error Code:%d.",err);
MessageBox(mes);
}
3.3.1 阻塞及其處理方式
在網絡通訊中,由于網絡擁擠或一次發送的數據量過大等原因,經常會發生交換的數據在短時間內不能傳送完,收發數據的函數因此不能返回,這種現象叫做阻塞。Winsock對有可能阻塞的函數提供了兩種處理方式:阻塞和非阻塞方式。在阻塞方式下,收發數據的函數在被調用后一直要到傳送完畢或者出錯才能返回。在阻塞期間,被阻的函數不斷會調用系統函數GetMessage()來保持消息循環的正常進行。對于非阻塞方式,函數被調用后立即返回,當傳送完成后由Winsock給程序發一個事先約定好的消息。
編程時應盡量使用非阻塞方式。因為在阻塞方式下,用戶可能會長時間的等待過程中試圖關閉程序,因為消息循環還在起作用,所以程序的窗口可能被關閉。這樣當函數從Winsock的動態連接庫中返回時,主程序已經從內存中刪除,這顯然是極其危險的。
3.3.2 異步選擇函數 WSAAsyncSelect()的使用
Winsock通過WSAAsyncSelect()自動地設置套接字處于非阻塞方式。使用 Windows Sockets實現Windows網絡程序設計的關鍵就是它提供了對網絡事件基于消息的異步存取,用于注冊應用程序感興趣的網絡事件。它請求 Windows Sockets DLL在檢測到套接字上發生的網絡事件時,向窗口發送一個消息。對UDP協議,這些網絡事件主要為:
FD_READ期望在套接字收到數據(即讀準備好)時接收通知;
FD_WRITE期望在套接字可發送數(即寫準備好)時接收通知;
FD_CLOSE期望在套接字關閉時接電通知;消息變量wParam指示發生網絡事件的套接字,變量1Param的低字節描述發生的網絡事件,高字包含錯誤碼。如在窗口函數的消息循環中均加一個分支:
int ok=sizeof(SOCKADDR);
case wMsg;
switch(1Param)
{
case FD_READ://套接字上讀數據
if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&there1,
(int FAR*)&ok)==SOCKET_ERROR0
{
MessageBox}hwnd,“數據接收失敗!”,“”,MB_OK);
return(FALSE);
)
case FD_WRITE://套接字上寫數據
)
break;
在程序的編制中,應根據需要靈活地將WSAAsyncSelect()函數放在相應的消息循環之中。同時,按照程序容錯誤設計,應建立一個專門的容錯處理函數。程序中可能出現的各種錯誤都將由該函數進行處理,依據錯誤的危害程度不同,建立幾種不同的處理措施。這樣,才能保證雙方通信的順利和可靠。
[1] [美]Douglas E Comer.Internet Working with TCP/IP[M].北京:電子工業出版社,1998.
[2] 蔣東興.Windows Sockets網絡程序設計大全[M].北京:清華大學出版社,1999.
[3] [美]Kate Gregory.Visual C++5開發使用手冊[M].北京:機械工業出版社,1998.
[4] [美]Robert D Thompson.MFC開發人員參考手冊[M].前導工作室,1998.