zoukankan      html  css  js  c++  java
  • 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编程。

  • 相关阅读:
    [SCOI2009] Windy数
    [P1361] 小M的作物
    Wannafly Camp 2020 Day 2E 阔力梯的树
    2017百越杯反序列化writeup
    大美西安writeup
    Thinkphp的SQL查询方式
    Thinkphp的CURD
    记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门)
    ThinkPHP的输出和模型使用
    ThinkPHP的运行流程-2
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3190129.html
Copyright © 2011-2022 走看看