在Windows CE中,Select模型是惟一被支持的I/O模型。Select I/O模型就是利用select函數對I/O進行管理。編程
函數select的功能在於獲取一個或多個套接字的狀態,以及在必要的時候執行同步I/O操做進行等待。它的原型以下:數組
int select(緩存
int nfds,服務器
fd_set FAR* readfds,網絡
fd_set FAR* writefds,架構
fd_set FAR* exceptfds,異步
const struct timeval FAR* timeoutsocket
);ide
參數nfds被忽略,只是爲了保持與Berkeley的套接字規範相兼容。函數
參數readfds指向用於檢查可讀性的一系列套接字。
參數writefds指向用於檢查可寫性的一系列套接字。
參數exceptfds指向用於檢查錯誤的一系列套接字。
參數timeout設置select函數可以最多等待的I/O操做時間。在阻塞操做時,該參數被設置爲NULL,表示必須有操做發生才中止等待。
該函數返回結構體fd_set結構體中處於準備好狀態的套接字句柄的數目。若是超時,函數select返回0;不然,在發生錯誤的時候返回SOCKET_ERROR。函數WSAGetLastError能返回的錯誤碼以及相應的描述以下:如表11-14
錯誤碼 |
描述 |
WSANOTINITIALISED |
必須在成功調用WSAStartup函數以後,才能調用此函數 |
WSAENETDOWN |
網絡子系統出錯或者相關的服務提供者出現故障 |
WSAEINPROGRESS |
阻塞性的Winsock函數正在被調用,或是服務提供者正在處理回調函數 |
WSAENOTSOCK |
指定的套接字描述符不是合法的套接字 |
WSAEFAULT |
參數argp不是合法的用戶地址空間的地址 |
WSAEINVAL |
參數不被支持或是不合法 |
WSAEINTR |
套接字被關閉 |
表11-14錯誤碼以及對應描述
該函數用於獲取一個或多個套接字的狀態。對於每一個套接字來講,函數可以分別獲取讀、寫以及發生錯誤的狀態。待查詢狀態的套接字用結構體fd_set來表示。統一個fd_set結構體中的全部套接字必須和同一個服務提供者相關聯。函數返回的時候,結構體fd_set的值反映了知足指定條件的套接字。而函數返回值表明了知足指定條件的套接字的數目。
若是套接字處於監聽的狀態且有鏈接請求到達,那麼fd_set中相應的位被設置爲可讀,而accept函數不被阻塞而直接完成。對於其餘狀態的套接字來講,可讀性表示隊列中存在數據,而接收函數,如recv和recvfrom不用被阻塞。
若是套接字在處理非阻塞的connect函數調用,套接字只有在建立鏈接成功完成的時候才被設置爲可寫。若是套接字不是處理connect函數調用,可寫行表示發送函數,如send和sendto,可以保證成功執行。
在readfds、writefds和exceptfds這三個參數中,最多隻能有兩個參數被同時設置爲空;而另外的非空參數中至少包含一個套接字的句柄。
readfds集合包括符合下述任一條件的套接字:
1) 若是listen函數已經被調用,並且一個鏈接正在被創建,那麼accept函數將成功執行。
2) 有供讀取的數據;若是SO_OOBINLINE屬性被設置,這裏的數據包括帶外數據。
3) 鏈接被關閉、重置或是中斷。
writefds集合包括符合下述任一條件的套接字:
1) 若是正在處理一個非阻塞的connect函數調用,那麼鏈接會成功。
2) 數據可以被髮送。
exceptfds集合包括符合下述任一條件的套接字:
1) 若是正在處理一個非阻塞的connect函數調用,鏈接嘗試會失敗。
2) 在SO_OOBINLINE屬性被禁止的情形下,有帶外數據能夠被讀取。
Winsock動態連接庫還提供了相應的fd_set結構體的函數,便於程序對fd_set的控制。主要的fd_set結構體操做函數以下:
1) FD_CLR(s, *set):從集合set中刪除套接字s。
2) FD_ISSET(s, *set):檢查套接字s是不是集合set的一員。若是是,返回非零值;不然,返回0。
3) FD_SET(s, *set):往集合set中添加套接字s。
4) FD_ZERO(*set):初始化集合set爲空集合。
在Windows環境中,Ping命令是常見的網絡命令。它的主要目的是探測目標機器是否可達,以及檢測二者間的網絡狀態;而它的實現主要是經過向目標機器發送一個ICMP(Internet Control Message Protocol)格式的包來完成的。
ICMP協議是一個在網絡主機間執行流控制、錯誤信息、路由或是其餘數據的網絡協議。它主要用於網絡Ping(或是Packet Internet Groper)中。
在RFC 792的網絡協議規範中,ICMP定位於維護狀態的協議,且做爲IP協議的一部分,工做在ISO模型的網絡層中。ICMP的消息都封裝成IP數據包的格式,於是能夠在網絡中進行路由傳輸。在Windows CE中,ICMP的用途主要有:
1) 建立和維護路由表;
2) 路由發現;
3) 錯誤診斷;
4) 路由選擇;
5) 流量控制。
要編寫Ping的應用程序,離不開三個Winsock API函數的支持:IcmpCreateFile, IcmpSendEcho和IcmpSendEcho。
1、函數IcmpCreateFile介紹
函數IcmpCreateFile的功能在於建立一個用於發送ICMP請iude句柄,原型以下:
HANDLE WINAPI IcmpCreateFile (VOID);
函數執行成功時,返回有效的ICMP句柄;不然,返回INVALID_HANDLE_VALUE。
2、函數IcmpSendEcho介紹
函數IcmpSendEcho的功能在於發送一個Ping數據包,即ICMP協議包,並接受一個或多個應答。它的原型以下:
DWORD WINAPI IcmpSendEcho(
HANDLE IcmpHandle,
IPAddr DestinationAddress,
LPVOID RequestData,
WORD RequestSize,
PIP_OPTION_INFORMATION RequestOptions,
LPVOID ReplyBuffer,
DWORD ReplySize,
DWORD Timeout );
參數IcmpHandle指定了ICMP的句柄,這個句柄是由函數IcmpCreateFile打開的。
參數DestinationAddress指定了目標主機的IP地址。
參數RequestData指定了要發送數據包的緩存。
參數RequestSize指定了要發送數據包的長度,及參數RequestData中數據的長度。
參數RequestOptions指定了請求IP頭的方式,能夠爲空。另外,還支持的方式有時間戳(IP_OPT_TS)和記錄路由(IP_OPT_RR)。
參數ReplyBuffer表示應答數據的緩存。在函數返回的時候,這個緩存會保存一個或多個ICMP_ECHO_REPLY結構體,以及相應的選項和數據。
參數ReplySize表示收到的應答數據的大小。
參數Timeout表示指定等待應答的最大時間,單位是毫秒。
函數執行成功的時候,返回收到的應答的數目;不然,返回0。
3、函數IcmpCloseHandle介紹
函數IcmpCloseHandle的功能在於關閉ICMP句柄,原型以下:
BOOL WINAPI IcmpCloseHandle(
HANDLE IcmpHandle );
參數IcmpHandle是已經被函數IcmpCreateFile打開的ICMP句柄。
函數執行成功,返回TRUE;不然,返回FALSE。
若是在程序中須要探測目標主機是否可達,程序的執行流程以下:
1. 調用IcmpCreateFile函數建立一個新的ICMP句柄。
2. 循環調用IcmpSendEcho函數發送ICMP數據包並接收應答。在網絡不可達,或是鏈接超時,函數會返回錯誤。
3. 調用IcmpCloseHandle函數關閉已經建立的ICMP句柄。
RAS,即遠程訪問服務(Remote Access Service),主要鏈接遠程主機和本地計算機。它容許用戶將遠程節點的計算機鏈接到一個本地計算機網絡。若是創建了鏈接,就能夠像訪問局域網中的計算機同樣,即便計算機實際鏈接的是一個遠程網絡。
下面將介紹Windows CE爲RAS架構提供的一些經常使用的API。
在Winsock爲RAS架構提供的服務中,函數RasDial用於創建撥號鏈接。函數RasDial的功能在於在RAS客戶端和RAS服務器端,即本地主機和遠程主機間創建一個RAS鏈接。這個鏈接中傳輸的數據包括了相互間的反饋信息,以及用戶的認證信息。函數RasDial的原型以下:
DWORD RasDial(
LPRASDIALEXTENSIONS dialExtensions,
LPTSTR phoneBookPath,
LPRASDIALPARAMS rasDialParam,
DWORD NotifierType,
LPVOID notifier,
LPHRASCONN pRasConn
);
參數dialExtensions將被忽略,應該被設置爲空。在Windows CE中,它的默認值爲RASDIALEXTENSIONS。
參數phoneBookPath的值也應該被設置爲空。在撥號鏈接中,撥號名都存儲在註冊表的電話本表項中,而不是電話本文件中。
參數rasDialParam是指向結構體RASDIALPARAMS的指針,指定了撥號和用戶身份驗證參數。結構體RASDIALPARAMS的定義以下:
typedef struct _RASDIALPARAMS {
DWORD dwSize;
TCHAR szEntryName[ RAS_MaxEntryName + 1 ];
TCHAR szPhoneNumber[ RAS_MaxPhoneNumber + 1 ];
TCHAR szCallbackNumber[ RAS_MaxCallbackNumber + 1 ];
TCHAR szUserName[ UNLEN + 1 ];
TCHAR szPassword[ PWLEN + 1 ];
TCHAR szDomain[ DNLEN + 1 ];
} RASDIALPARAMS;
結構體RASDIALPARAMS的屬性以下:
1) dwSize域指定告終構體的大小,單位爲字節數。
2) szEntryName域指定了創建撥號鏈接的名稱,不能爲空。
3) szPhoneNumber域被忽略,應該設置爲空。
4) szCallbackNumber域被忽略,應該設置爲空。
5) szUserName域指定了鏈接用戶的用戶名。它是RAS服務器進行身份驗證的用戶名,不能爲空。
6) szPassword域指定了鏈接用戶的密碼。它是RAS服務器進行身份驗證的用戶名的密碼,不能爲空。
7) szDomain域指定了鏈接用戶所在的域。
參數NotifierType指定了參數notifier的屬性。若是參數notifier爲空,那麼參數NotifierType被忽略;不然,參數NotifierTye的值爲0xFFFFFFFF。
參數notifier指向了接收創建鏈接過程消息的窗口句柄。若是參數notifier不爲空,那麼RasDial將會每個RasDial創建鏈接的事件發送一個消息。此時,RasDial執行異步調用。這表示RasDial在進行鏈接的同時,函數調用會當即返回。若是參數notifier爲空,RasDial執行同步調用。這表示知道RasDial鏈接完成後,不論成功或失敗,纔會返回。
參數pRasConn指定RasDial函數創建的撥號鏈接句柄。
下面的程序展現如何使用RasDial函數來進行異步調用。
BOOL MakeRasDial (HWND hDlgWnd)
{
BOOL bPassword;
TCHAR szBuffer[100];
if (bUseCurrent)
{
// Get the last configuration parameters used for this connection.
// If the password was saved, then the logon dialog box will not be
// displayed.
if (RasGetEntryDialParams (NULL, &RasDialParams, &bPassword) != 0)
{
MessageBox (hDlgWnd,
TEXT("Could not get parameter details"),
szTitle,
MB_OK);
return FALSE;
}
}
else
{
// Display the Authentication dialog box.
DialogBox (hInst, MAKEINTRESOURCE(IDD_AUTHDLG), hDlgWnd,
AuthDlgProc);
// Set hRasConn to NULL before attempting to connect.
hRasConn = NULL;
// Initialize the structure.
memset (&RasDialParams, 0, sizeof (RASDIALPARAMS));
// Configure the RASDIALPARAMS structure.
RasDialParams.dwSize = sizeof (RASDIALPARAMS);
RasDialParams.szPhoneNumber[0] = TEXT('\0');
RasDialParams.szCallbackNumber[0] = TEXT('\0');
wcscpy (RasDialParams.szEntryName, szRasEntryName);
wcscpy (RasDialParams.szUserName, szUserName); //This is optional
wcscpy (RasDialParams.szPassword, szPassword); //This is optional
wcscpy (RasDialParams.szDomain, szDomain); //This is optional
}
// Try to establish RAS connection.
if (RasDial (NULL, // Extension not supported
NULL, // Phone book is in registry
&RasDialParams, // RAS configuration for connection
0xFFFFFFFF, // Notifier type is a window handle
hDlgWnd, // Window receives notification message
&hRasConn) != 0) // Connection handle
{
MessageBox (hDlgWnd,
TEXT("Could not connect using RAS"),
szTitle,
MB_OK);
return FALSE;
}
wsprintf (szBuffer, TEXT("Dialing %s..."), szRasEntryName);
// Set the Dialing dialog box window name to szBuffer.
SetWindowText (hDlgWnd, szBuffer);
return TRUE;
}
函數RasHangUp是Winsock中用於關閉撥號鏈接的函數,原型以下:
DWORD RasHangUp(
HRASCONN Session
);
參數Session是待關閉的鏈接的句柄。它是函數RasDial或函數RasEnumConnections返回的一個句柄。
函數執行成功的時候,返回值爲0;不然,返回非零值。
在撥號鏈接的過程當中,若是鏈接關閉,鏈接端口須要花很長時間來從新設置這個鏈接,所以,應該一直等到端口鏈接徹底關閉爲止。要判斷鏈接是否徹底關閉,能夠調用函數RasGetConnectStatus來判斷。
函數RasGetConnectStatus的功能在於獲取當前RAS的狀態信息,原型以下:
DWORD RasGetConnectStatus(
HRASCONN rasconn,
LPRASCONNSTATUS lprasconnstatus
);
參數rasconn是函數RasDial或RasEnumConnections返回的句柄。
參數lprasconnstatus是一個指向結構體RASCONNSTATUS的指針,用來獲取當前的鏈接狀態。結構體RASCONNSTATUS的定義以下:
typedef struct _RASCONNSTATUS {
DWORD dwSize;
RASCONNSTATE rasconnstate;
DWORD dwError;
TCHAR szDeviceType[ RAS_MaxDeviceType + 1 ];
TCHAR szDeviceName[ RAS_MaxDeviceName + 1 ];
} RASCONNSTATUS;
結構體RASCONNSTATUS的屬性以下:
1) dwSize域指定告終構體的大小,單位爲字節數。
2) rasconnstate域指定了當前RasDail鏈接的狀態。它可選的值是RASCS_Connected和RASCS_Disconnected,分別表示創建鏈接成功或是失敗。
3) dwError域若是不爲空,則表示失敗的緣由。它的可選值是:ERROR_NOT_ENOUGH_MEMORY和ERROR_INVALID_HANDLE。
4) szDeviceType域表示鏈接所用的設備類型,不能爲空。
5) szDeviceName域表示當前的設備名,不能爲空。
函數RasEnumConnections的功能在於列舉當前全部活動的撥號鏈接,該函數的原型以下:
DWORD RasEnumConnections(
LPRASCONN lprasconn,
LPDWORD lpcb,
LPDWORD lpcConnections
);
參數lprasconn指向結構體RASCONN的結構數組,每一個數組項表明一個RAS鏈接。結構體RASCONN的定義以下:
typedef struct _RASCONN {
DWORD dwSize;
HRASCONN hrasconn;
TCHAR szEntryName[ RAS_MaxEntryName + 1 ];
} RASCONN;
結構體RASCONNSTATUS的屬性以下:
1) dwSize域指定告終構體的大小,單位爲字節數。
2) hrasconn域表明撥號鏈接句柄。
3) szEntryName域表明撥號鏈接時的名字,不能爲空。
參數lpcb表示參數lprasonn的數據大小。
參數lpcConnections表示活動鏈接的數目。
函數RasEnumConnections的返回值爲0,表示執行成功;不然,表示失敗。
下面的程序展現如何關閉當前全部活動的撥號鏈接。
DWORD CloseRasConnections ()
{
int index; // An integer index
TCHAR szError[100]; // Buffer for error codes
DWORD dwError, // Error code from a function call
dwRasConnSize, // Size of RasConn in bytes
dwNumConnections; // Number of connections found
RASCONN RasConn[20]; // Buffer for connection state data
// Assume the maximum number of entries is
// 20.
// Assume no more than 20 connections.
RasConn[0].dwSize = sizeof (RASCONN);
dwRasConnSize = 20 * sizeof (RASCONN);
// Find all connections.
if (dwError = RasEnumConnections (RasConn, &dwRasConnSize,
&dwNumConnections))
{
wsprintf (szError, TEXT("RasEnumConnections Error: %ld"), dwError);
return dwError;
}
// If there are no connections, return zero.
if (!dwNumConnections)
{
wsprintf (szError, TEXT("No open RAS connections"));
return 0;
}
// Terminate all of the remote access connections.
for (index = 0; index < (int)dwNumConnections; ++index)
{
if (dwError = RasHangUp (RasConn[index].hrasconn))
{
wsprintf (szError, TEXT("RasHangUp Error: %ld"), dwError);
return dwError;
}
}
return 0;
}
UDP(User Datagram Protocol),即用戶數據報協議,提供無鏈接的、不可靠的傳輸服務。「無鏈接」意味着在相互交換數據以前,通訊主機間沒有創建鏈接鏈路。UDP的這種「無鏈接」數據傳輸服務沒法保障數據的可靠傳輸。UDP既不保證數據報被正確發送出去,也不會發送確認信息。另外,UDP協議也不能保證數據報的有序性。UDP常常用於「一對多」的通訊,既可以向若干個目標發送數據,也能接收發自若干個源的數據。
因爲UDP數據報不保證的可靠性,應用程序必須採起機制來維護UDP傳送數據的可靠性。雖然UDP不保證順序性、可靠性和無重複性等限制,UDP協議仍用於不少場景。例如,Winsock庫的IP組播技術就是利用UDP數據報來實現的。並且,UDP的傳輸效率高和延遲小。Microsoft的網絡利用UDP處理網絡登陸、瀏覽以及域名解析。
在編程方面,UDP實現相對簡單。UDP服務器不須要監聽或是接收客戶端的鏈接,而UDP客戶端也不用鏈接到服務器。UDP服務器端和客戶端的編程流程如圖11.3所示。
UDP通訊中服務器端的代碼的執行流程以下:
1) 調用socket函數建立一個數據報套接字。其中,參數address format的值爲AF_INET,而參數type的值爲SOCK_DGRAM。
2) 調用bind函數。其中,參數address使用SOCKADDR_IN結構體。
3) 調用函數sendto和recvfrom與客戶端進行數據的交換。
4) 調用closesocket函數關閉鏈接。此時,函數shutdown對於UDP套接字來講無效。
UDP通訊中客戶端的代碼的執行流程以下:
1) 調用socket函數建立一個數據報套接字。
2) 調用函數sendto和recvfrom與服務器端進行數據的交換。
TCP(Transport Control Protocol),即傳輸控制協議,提供了無差錯無重複且順序的數據傳輸。TCP的套接字也被稱爲流式套接字。與UDP通訊不一樣,應用程序在利用TCP進行通訊以前,客戶端和服務器端會創建一個虛擬鏈接,建立一個虛擬的數據傳輸鏈路。當這個鏈接成功創建以後,客戶端和服務器端就能夠把數據看成一個雙向字節流進行交換。
在編程方面,TCP編程相對於UDP編程來講要複雜的多。TCP服務器端和客戶端的編程流程如圖11.4所示。
TCP通訊中服務器端的代碼的執行流程以下:
1) 調用socket函數建立一個流式套接字。其中,參數address format的值爲AF_INET,而參數type的值爲SOCK_STREAM。
2) 調用bind函數。其中,參數address使用SOCKADDR_IN結構體。
3) 調用listen函數監聽客戶端發送的鏈接請求。
4) 若是監聽到有客戶端發出鏈接請求,調用accept函數創建與客戶端的鏈接;不然,一直在3)處循環等待。
5) 調用send和recv函數與客戶端進行數據的交換。
6) 調用closesocket函數關閉鏈接。爲了保證TCP鏈接上的數據不會丟失,能夠先調用shutdown函數關閉全部的鏈接。
TCP通訊中客戶端的代碼的執行流程以下:
1) 調用socket函數建立一個流式套接字。其中,參數address format的值爲AF_INET,而參數type的值爲SOCK_STREAM。
2) 調用connect函數向服務器發起鏈接請求。其中,參數address使用SOCKADDR_IN結構體。
3) 調用send和recv函數與客戶端進行數據的交換。
4)
本章主要介紹了Windows Embedded Compact 7中網絡編程的基礎,以及常見的TCP、UDP等編程的概述。首先,介紹了Windows CE中網絡編程的基礎,也就是套接字。對如何在Windows CE環境中使用套接字,以及經常使用的套接字的API做了講解。其次,介紹了Windows CE的網絡編程中最多見的四種編程方式:Ping編程、RAS撥號編程、UDP編程以及TCP編程。