Windows Embedded Compact 7網絡編程概述(下)

11.1.1 Select I/O模型

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函數不被阻塞而直接完成。對於其餘狀態的套接字來講,可讀性表示隊列中存在數據,而接收函數,如recvrecvfrom不用被阻塞。

若是套接字在處理非阻塞的connect函數調用,套接字只有在建立鏈接成功完成的時候才被設置爲可寫。若是套接字不是處理connect函數調用,可寫行表示發送函數,如sendsendto,可以保證成功執行。

readfdswritefdsexceptfds這三個參數中,最多隻能有兩個參數被同時設置爲空;而另外的非空參數中至少包含一個套接字的句柄。

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爲空集合。

11.1 Ping編程

11.2.1 Ping編程概述

Windows環境中,Ping命令是常見的網絡命令。它的主要目的是探測目標機器是否可達,以及檢測二者間的網絡狀態;而它的實現主要是經過向目標機器發送一個ICMPInternet 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的功能在於建立一個用於發送ICMPiude句柄,原型以下:

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句柄。

11.2.2 Ping編程示例

11.2 RAS撥號編程

RAS,即遠程訪問服務(Remote Access Service),主要鏈接遠程主機和本地計算機。它容許用戶將遠程節點的計算機鏈接到一個本地計算機網絡。若是創建了鏈接,就能夠像訪問局域網中的計算機同樣,即便計算機實際鏈接的是一個遠程網絡。

在Windows CE RAS 架構中, RAS 直接與 PPP 協議( Point-to-Point 協議)通訊,建立鏈接到遠程訪問的通路。 RAS 利用 PPP 協議將須要進行傳輸的數據封裝成 IP 數據包,並經過點對點的鏈路將數據發送出去。當 PPP 協議接收到從 TCP/IP 協議上發送過來的 IP 請求數據包時,它將包發送到 AsyncMAC 微端口。以後, AsyncMAC 將數據包組裝成異步幀,並調用 Win32 的串行 API 將包轉發到 TAPI 設備。這樣,就完成了數據包的傳輸。整個數據包的處理和控制流程如圖 11.2 所示。

下面將介紹Windows CERAS架構提供的一些經常使用的API

11.3.1 創建撥號鏈接

在WinsockRAS架構提供的服務中,函數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;

}

11.3.2 關閉撥號鏈接

函數RasHangUpWinsock中用於關閉撥號鏈接的函數,原型以下:

DWORD RasHangUp(

  HRASCONN Session 

);

參數Session是待關閉的鏈接的句柄。它是函數RasDial或函數RasEnumConnections返回的一個句柄。

函數執行成功的時候,返回值爲0;不然,返回非零值。

在撥號鏈接的過程當中,若是鏈接關閉,鏈接端口須要花很長時間來從新設置這個鏈接,所以,應該一直等到端口鏈接徹底關閉爲止。要判斷鏈接是否徹底關閉,能夠調用函數RasGetConnectStatus來判斷。

函數RasGetConnectStatus的功能在於獲取當前RAS的狀態信息,原型以下:

DWORD RasGetConnectStatus(

  HRASCONN rasconn, 

  LPRASCONNSTATUS lprasconnstatus 

);

參數rasconn是函數RasDialRasEnumConnections返回的句柄。

參數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_ConnectedRASCS_Disconnected,分別表示創建鏈接成功或是失敗。

3) dwError域若是不爲空,則表示失敗的緣由。它的可選值是:ERROR_NOT_ENOUGH_MEMORYERROR_INVALID_HANDLE

4) szDeviceType域表示鏈接所用的設備類型,不能爲空。

5) szDeviceName域表示當前的設備名,不能爲空。

11.3.3 列舉已創建的活動鏈接

函數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;

}

11.3 UDP編程概述

UDPUser 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) 調用函數sendtorecvfrom與客戶端進行數據的交換。

4) 調用closesocket函數關閉鏈接。此時,函數shutdown對於UDP套接字來講無效。

UDP通訊中客戶端的代碼的執行流程以下:

1) 調用socket函數建立一個數據報套接字。

2) 調用函數sendtorecvfrom與服務器端進行數據的交換。

11.4 TCP編程概述

TCPTransport 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) 調用sendrecv函數與客戶端進行數據的交換。

6) 調用closesocket函數關閉鏈接。爲了保證TCP鏈接上的數據不會丟失,能夠先調用shutdown函數關閉全部的鏈接。

TCP通訊中客戶端的代碼的執行流程以下:

1) 調用socket函數建立一個流式套接字。其中,參數address format的值爲AF_INET,而參數type的值爲SOCK_STREAM

2) 調用connect函數向服務器發起鏈接請求。其中,參數address使用SOCKADDR_IN結構體。

3) 調用sendrecv函數與客戶端進行數據的交換。

4) 

調用 closesocket 函數關閉鏈接。爲了保證 TCP 鏈接上的數據不會丟失,能夠先調用 shutdown 函數關閉全部的鏈接。

11.5 小結

本章主要介紹了Windows Embedded Compact 7中網絡編程的基礎,以及常見的TCPUDP等編程的概述。首先,介紹了Windows CE中網絡編程的基礎,也就是套接字。對如何在Windows CE環境中使用套接字,以及經常使用的套接字的API做了講解。其次,介紹了Windows CE的網絡編程中最多見的四種編程方式:Ping編程、RAS撥號編程、UDP編程以及TCP編程。

相關文章
相關標籤/搜索