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* timeout

);

参数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

一、函数IcmpCreateFile介绍

函数IcmpCreateFile的功能在于创建一个用于发送ICMPiude句柄,原型如下:

HANDLE WINAPI IcmpCreateFile (VOID);

函数执行成功时,返回有效的ICMP句柄;否则,返回INVALID_HANDLE_VALUE

二、函数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

三、函数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('');

    RasDialParams.szCallbackNumber[0] = TEXT('');

    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编程。

原文地址:https://www.cnblogs.com/snake-hand/p/3190129.html