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