⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 23. 领略internet.txt

📁 本书介绍了在Microsoft Windows 98、Microsoft Windows NT 4.0和Windows NT 5.0下程序写作的方法
💻 TXT
📖 第 1 页 / 共 5 页
字号:
        
           else
        
                  EditPrintf (hwndEdit, TEXT ("Could NOT set new date and time.")) ;
        
}
        

void FormatUpdatedTime (   HWND hwndEdit, SYSTEMTIME * pstOld, SYSTEMTIME * pstNew)
        
{
        
           TCHAR szDateOld [64], szTimeOld [64], szDateNew [64], szTimeNew [64] ;
        
           GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
        
                                                         pstOld, NULL, szDateOld, sizeof (szDateOld)) ;
        
           GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
        
                       TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
        
                   pstOld, NULL, szTimeOld, sizeof (szTimeOld)) ;
        

           GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE | DATE_SHORTDATE,
        
                   pstNew, NULL, szDateNew, sizeof (szDateNew)) ;
        
           GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE |
        
                      TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT,
        
                   pstNew, NULL, szTimeNew, sizeof (szTimeNew)) ;
        

           EditPrintf (hwndEdit, TEXT ("System date and time successfully changed ")
        
                                  TEXT ("from\r\n\t%s, %s.%03i to\r\n\t%s, %s.%03i."),
        
                  szDateOld, szTimeOld, pstOld->wMilliseconds,
        
                  szDateNew, szTimeNew, pstNew->wMilliseconds) ;
        
}
        

void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...)
        
{
        
           TCHAR         szBuffer [1024] ;
        
           va_listpArgList ;
        

           va_start (pArgList, szFormat) ;
        
           wvsprintf (szBuffer, szFormat, pArgList) ;
        
           va_end (pArgList) ;
        
           SendMessage (hwndEdit, EM_SETSEL, (WPARAM) -1, (LPARAM) -1) ;
        
           SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
        
           SendMessage (hwn dEdit, EM_SCROLLCARET, 0, 0) ;
        
}
        
NETTIME.RC (摘录)
        
//Microsoft Developer Studio generated resource script.
        
#include "resource.h"
        
#include "afxres.h"
        
/////////////////////////////////////////////////////////////////////////////
        
// Dialog
        
SERVERS DIALOG DISCARDABLE  20, 20, 274, 202
        
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
        
CAPTION "NIST Time Service Servers"
        
FONT 8, "MS Sans Serif"
        
BEGIN
        
   DEFPUSHBUTTON                 "OK",IDOK,73,181,50,14
        
   PUSHBUTTON                           "Cancel",IDCANCEL,150,181,50,14
        
   CONTROL       
        
                 "time-a.timefreq.bldrdoc.gov (132.163.135.130) NIST, Boulder, Colorado",
        
                          IDC_SERVER1,"Button",BS_AUTORADIOBUTTON,9,7,256,16
        
   CONTROL       
        
          "time-b.timefreq.bldrdoc.gov (132.163.135.131) NIST, Boulder, Colorado",
        
                  IDC_SERVER2,"Button",BS_AUTORADIOBUTTON,9,24,256,16
        
   CONTROL       
        
                  "time-c.timefreq.bldrdoc.gov (132.163.135.132) Boulder, Colorado",
        
                          IDC_SERVER3,"Button",BS_AUTORADIOBUTTON,9,41,256,16
        
   CONTROL       
        
                  "utcnist.colorado.edu (128.138.140.44) University of Colorado, Boulder",
        
                          IDC_SERVER4,"Button",BS_AUTORADIOBUTTON,9,58,256,16
        
   CONTROL       
        
                          "time.nist.gov (192.43.244.18) NCAR, Boulder, Colorado",
        
                         IDC_SERVER5,"Button",BS_AUTORADIOBUTTON,9,75,256,16
        
   CONTROL       
        
                          "time-a.nist.gov (129.6.16.35) NIST, Gaithersburg, Maryland",
        
                          IDC_SERVER6,"Button",BS_AUTORADIOBUTTON,9,92,256,16
        
   CONTROL       
        
                         "time-b.nist.gov (129.6.16.36) NIST, Gaithersburg, Maryland",
        
                          IDC_SERVER7,"Button",BS_AUTORADIOBUTTON,9,109,256,16
        
   CONTROL       
        
                          "time-nw.nist.gov (131.107.1.10) Microsoft, Redmond, Washington",
        
                          IDC_SERVER8,"Button",BS_AUTORADIOBUTTON,9,126,256,16
        
  CONTROL       
        
                          "utcnist.reston.mci.net (204.70.131.13) MCI, Reston, Virginia",
        
                          IDC_SERVER9,"Button",BS_AUTORADIOBUTTON,9,143,256,16
        

   CONTROL       
        
                                  "nist1.data.com (209.0.72.7) Datum, San Jose, California",
        
                          IDC_SERVER10,"Button",BS_AUTORADIOBUTTON,9,160,256,16
        
END
        
NETTIME DIALOG DISCARDABLE  0, 0, 270, 150 STYLE WS_CHILD FONT 8, "MS Sans Serif"
        
BEGIN
        
   DEFPUSHBUTTON                 "Set Correct Time",IDOK,95,129,80,14
        
   PUSHBUTTON                           "Close",IDC_CLOSE,183,129,80,14
        
   PUSHBUTTON                           "Select Server...",IDC_SERVER,7,129,80,14
        
   EDITTEXT                                     IDC_TEXTOUT,7,7,253,110,ES_MULTILINE | ES_AUTOVSCROLL |
        
                                                                ES_READONLY | WS_VSCROLL | NOT WS_TABSTOP
        
END
        
RESOURCE.H (摘录)
        
// Microsoft Developer Studio generated include file.
        
// Used by NetTime.rc
        
#define            IDC_TEXTOUT                                                     101
        
#define            IDC_SERVER1                                                     1001
        
#define            IDC_SERVER2                                                     1002
        
#define            IDC_SERVER3                                                    1003
        
#define            IDC_SERVER4                                                     1004
        
#define            IDC_SERVER5                                                     1005
        
#define            IDC_SERVER6                                                     1006
        
#define            IDC_SERVER7                                                     1007
        
#define            IDC_SERVER8                                                    1008
        
#define            IDC_SERVER9                                                     1009
        
#define            IDC_SERVER10                                                    1010
        
#define            IDC_SERVER                                                      1011
        
#define            IDC_CLOSE                                                                1012
        
在结构上,NETTIME程序建立了一个依据NETTIME.RC中的NETTIME所建立的非系统模态对话框。程序重新定义了窗口的尺寸,以便非系统模态对话框可以覆盖程序的整个窗口显示区域。对话框包括一个只读编辑区(程序用于写入文字信息)、一个「Select Server」按钮、一个「Set Correct Time」按钮和一个「Close」按钮。「Close」按钮用于终止程序。

MainDlg中的szIPAddr变量用于储存服务器地址,内定是字符串「132.163.135.130」。「Select Server」按钮启动依据NETTIME.RC中的SERVERS模板建立的对话框。szIPAddr变量作为最后一个参数传递给DialogBoxParam。「Server」对话框列出了10个服务器(都是从NIST网站上逐字复制来的),这些服务器提供了我们感兴趣的服务。当使用者单击一个服务器时,ServerDlg将分析按钮文字,以获得相应的IP地址。新地址储存在szIPAddr变量中。

当使用者按下「Set Correct Time」按钮时,按钮将产生一个WM_COMMAND消息,其中wParam的低字组等于IDOK。MainDlg中的IDOK处理是大部分Socket初始行为发生的地方。

使用Windows Sockets API时,任何Windows程序必须呼叫的第一个函数是:

iError = WSAStartup (wVersion, &WSAData) ;
        
NETTIME将第一个参数设定为0x0200(表示2.0版本)。传回时,WSAData结构包含了Windows Sockets实作的相关信息,而且NETTIME将显示szDescription字符串,并简要提供了一些版本信息。

然后,NETTIME如下呼叫socket函数:

sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
        
第一个参数是一个地址种类,表示此处是某种Internet地址。第二个参数表示数据以数据流的形式传回,而不是以数据封包的形式传回(我们需要的数据只有4个字节长,而数据封包适用于较大的数据块)。最后一个参数是一个协议,我们指定使用的Internet协议是TCP。它是RFC-868所定义的两个协议之一。socket函数的传回值储存在SOCKET型态的变量中,以便后面的Socket函数的呼叫。

NETTIME下面呼叫的WSAAsynchSelect是另一个Windows特有的Socket函数。此函数用于避免因Internet响应过慢而造成应用程序当住。在WinSock文件中,有些函数与「阻碍性(blocking)」有关。也就是说,它们不能保证立即把控件权传回给程序。WSAAsyncSelect函数强制阻碍性的函数转为非阻碍性的,即在函数执行完之前把控件传回给程序。函数的结果以消息的形式报告给应用程序。WSAAsyncSelect函数让应用程序指定消息和接收消息的窗口的数值。通常,函数的语法如下:

WSAAsyncSelect (sock, hwnd, message, iConditions) ;
        
为此任务,NETTIME使用程序定义的一个消息,该消息称为WM_SOCKET_NOTIFY。它也用WSAAsyncSelect的最后一个参数来指定消息发送的条件,特别在连结和接收资料时(FD_CONNECT | FD_READ)。

NETTIME呼叫的下一个WinSock函数是connect。此函数需要一个指向Socket地址结构的指针,对于不同的协议来说,此Socket地址结构是不同的。NETTIME使用为TCP/IP设计的结构版本:

struct sockaddr_in
        
{
        
           short                                        sin_family;
        
           u_short                              sin_port;
        
           struct in_addr sin_addr;
        
           char                                         sin_zero[8];
        
} ;
        
其中in_addr是用于指定Internet地址,它可以用4个字节,或者2个无正负号短整数,或者1个无正负号长整数来表示。

NETTIME将sin_family字段设定为AF_INET,用于表示地址种类。将sin_port设定为埠号,这里是时间协议的埠号,RFC-868显示为37。但不要像我最初时那样,将此字段设为37。当大多数数字通过Internet时,结构的这个端口号字段必须是「big endian」的,即最高的字节排第一个。Intel微处理器是little endian。幸运的是,htons(「host-to-network short」)函数使字节翻转,因此NETTIME将sockaddr_in结构的sin_port字段设定为:

htons (IPPORT_TIMESERVER)
        
WINSOCK2.H中将常数定义为37。NETTIME用inet_addr函数将储存在szIPAddr字符串中的服务器地址转化为无正负号长整数,该整数用于设定结构的sin_addr字段。

如果应用程序在Windows 98下呼叫connect,而且目前Windows没有连结到Internet,那么将显示「拨号联机」对话框。这就是所谓的「自动拨号」。在Windows NT 4.0中没有实作「自动拨号」,因此如果在NT环境下执行,那么在执行NETTIME之前,就必须先连结上Internet。

connect函数通常已经会阻碍着后面程序的执行,这是因为连结成功以前需要花些时间。然而,由于NETTIME呼叫了WSAAsyncSelect,所以connect不会等待连结,事实上,它会立即传回SOCKET_ERROR的值。这并不是出现了错误,这只是表示现在还没有联机成功而已。NETTIME也不会检查这个传回值,只是呼叫WSAGetLastError而已。如果WSAGetLastError传回WSAEWOULDBLOCK(即函数的执行通常要受阻,但这里并没有受阻),那就一切都还很正常。NETTIME将「Set Correct Time」按钮改成「Cancel」,并设定了一个1秒的定时器。WM_TIMER的处理方式只是在程序窗口中显示句点,以告诉使用者程序仍在执行,系统没有当掉。

连结最终完成时,MainDlg由WM_SOCKET_NOTIFY消息-NETTIME在WSAAsyncSelect函数中指定的程序自订消息所通知。lParam的低字组等于FD_CONNECT,高字组表示错误。这时的错误可能是程序不能连结到指定的服务器。NETTIME还列出了其它9个服务器,供您选择,让您可以试试其它的服务器。

如果一切顺利,那么NETTIME将呼叫recv(「receive:接收」)函数来读取数据:

recv (sock, (char *) &ulTime, 4, MSG_PEEK) ;
        
这意味着,用4个字节来储存ulTime变量。最后一个参数表示只是读此数据,并不将其从输入队列中删除。像connect函数一样,recv传回一个错误代码,以表示函数通常受阻,但这时没有受阻。理论上来说(当然这不大可能),函数至少能传回数据的一部分,然后透过再次呼叫以获得其余的32个字节值。那就是呼叫recv函数时带有MSG_PEEK选项的原因。

与connect函数类似,recv函数也产生WM_SOCKET_NOTIFY消息,这时带有FD_READ的事件代码。NETTIME通过再次呼叫recv来对此响应,这时最后的参数是0,用于从队列中删除数据。我将简要讨论一下程序处理接收到的ulTime的方法。注意,NETTIME通过向自己发送WM_COMMAND消息来结束处理,该消息中wParam等于IDCANCEL。对话框程序通过呼叫closesocket和WSACleanup来响应。

再次呼叫NETTIME接收的32位的ulTime值是从1990年1月1日开始的0:00 UTC秒数。但最高顺序的字节是第一个字节,因此该值必须通过ntohl(「network-to-host long」)函数处理来调整字节顺序,以便Intel微处理器能够处理。然后,NETTIME呼叫ChangeSystemTime函数。

ChangeSystemTime首先取得目前的本地时间-即,使用者所在时区和日光节约时间的目前系统时间。将SYSTEMTIME结构设定为1900年1月1日午夜(0时)。并将这个SYSTEMTIME结构传递给SystemTimeToFileTime,将此结构转化为FILETIME结构。FILETIME实际上只是由两个32位的DWORD一起组成64位的整数,用来表示从1601年1月1日至今间隔为100奈秒(nanosecond)的间隔数。

ChangeSystemTime函数将FILETIME结构转化为LARGE_INTEGER。它是一个union,允许64位的值可以被当成两个32位的值使用,或者当成一个__int64数据型态的64位整数使用(__int64数据型态是Microsoft编译器对ANSI C标准的扩充)。因此,此值是1601年1月1日到1900年1月1日之间间隔为100奈秒的间隔数。这里,添加了1900年1月1日至今间隔为100奈秒的间隔数-ulTime的10,000,000倍。

然后通过呼叫FileTimeToSystemTime将作为结果的FILETIME值转换回SYSTEMTIME结构。因为时间协议传回目前的UTC时间,所以NETTIME通过呼叫SetSystemTime来设定时间,SetSystemTime也依据UTC。基于显示的目的,程序呼叫GetLocalTime来获得更新时间。最初的本地时间和新的本地时间一起传递给FormatUpdatedTime,这个函数用GetTimeFormat函数和GetDateFormat函数将时间转化为ASCII字符串。

如果程序在Windows NT下执行,并且使用者没有取得设定时间的权限,那么SetSystemTime函数可能失败。如果SetSystemTime失败,则NETTIME将发出一个新时间未设定成功的消息来指出问题所在。

WinInet 和 FTP


WinInet(「Windows Internet」)API是一个高阶函数集,帮助程序写作者使用三个常见的Internet协议,这三个协议是:用于World Wide Web全球信息网的超文字传输协议(HTTP:Hypertext Transfer Protocol)、文件传输协议(FTP:File Transfer Protocol)和另一个称为Gopher的文件传输协议。WinInet函数的语法与常用的Windows文件函数的语法类似,这使得使用这些协议就像使用本地磁盘驱动器上的文件一样容易。WinInet API的文件位于/Platform SDK/Internet, Intranet, Extranet Services/Internet Tools and Technologies/WinInet API。

下面的范例程序将展示如何使用WinInet API的FTP部分。许多有网站的公司也都有「匿名FTP」服务器,这样使用者可以在不输入使用者名称和密码的情况下下载文件。例如,如果您在Internet Explorer的地址栏输入ftp://ftp.microsoft.com,那么您就可以浏览FTP服务器上的目录并下载文件。如果进入ftp://ftp.cpetzold.com/cpetzold.com/ProgWin/UpdDemo,那么您将在我的匿名FTP服务器上发现与待会要提到的范例程序一块使用的文件列表。

虽然现今FTP服务对大多数的Web使用者来说并不是那么方便使用,但它仍然相当有用。例如,应用程序能利用FTP从匿名FTP服务器上取得数据,这些取得数据的运作程序几乎完全在台面下处理,而不需要使用者操心。这就是我们将讨论的UPDDEMO(「update demonstration:更新范例」)程序的构想。

FTP API概况


使用WinInet的程序必须在所有呼叫WinInet函数的源文件中包括表头文件WININET.H。程序还必须连结WININET.LIB。在Microsoft Visual C++中,您可以在「Project Settings」对话框的「Link」页面卷标中指定。执行时,程序将和WININET.DLL动态链接库连结。

在下面的论述中,我不会详细讨论函数的语法,因为某些函数有很多选项,这让它变得相当复杂。要掌握WinInet,您可以将UPDDEMO原始码当成食谱来看待。这时最重要的是了解有关的各个步骤以及FTP函数的范围。

要使用Windows Internet API,首先要呼叫InternetOpen。然后,使用WinInet支持的任何一种协议。InternetOpen给您一个Internet作业句柄,并储存到HINTERNET型态的变量中。用完WinInet API以后,应该通过呼叫InternetCloseHandle来关闭句柄。

要使用FTP,您接下来就要呼叫InternetConnect。此函数需要使用由InternetOpen建立Internet作业句柄,并且传回FTP作业的句柄。您可将此句柄作为名称开头为Ftp的所有函数的第一个参数。InternetConnect函数的参数指出要使用的FTP,还提供了服务器名称,例如,ftp.cpetzold.com。此函数还需要使用者名称和密码。如果存取匿名FTP服务器,这些参数可以设定为NULL。如果应用程序呼叫InternetConnect时PC并没有连结到Internet,Windows 98将显示「拨号联机」对话框。当使用FTP的应用程序结束时,呼叫InternetCloseHandle来关闭句柄。

这时可以开始呼叫有Ftp前缀的函数。您将发现这些函数与标准的Windows文件I/O函数很相似。为了避免与其它协议重复,一些以Internet为前缀的函数也可以处理FTP。

下面四个函数用于处理目录:

fSuccess = FtpCreateDirectory             (hFtpSession, szDirectory) ;
        
fSuccess = FtpRemoveDirectory             (hFtpSession, szDirectory) ;
        
fSuccess = FtpSetCurrentDirectory (hFtpSession, szDirectory) ;
        
fSuccess = FtpGetCurrentDirectory (hFtpSession, szDirectory, &dwCharacterCount) ;
        
注意,这些函数很像我们所熟悉的Windows提供用于处理本地文件系统的CreateDirectory、RemoveDirectory、SetCurrentDirectory和GetCurrentDirectory函数。

当然,存取匿名FTP的应用程序不能建立或删除目录。而且,程序也不能假定FTP目录具有和Windows文件系统相同的目录结构型态。特别是用相对路径名设定目录的程序,不能假定关于新的目录全名的一切。如果程序需要知道最后所在目录的整个名称,那么呼叫了SetCurrentDirectory之后必须再呼叫GetCurrentDirectory。GetCurrentDirectory的字符串参数至少包含MAX_PATH字符,并且最后一个参数应指向包含该值的变量。

下面两个函数让您删除或者重新命名文件(但不是在匿名FTP服务器上):

fSuccess = FtpDeleteFile (hFtpSession, szFileName) ;
        
fSuccess = FtpRenameFile (hFtpSession, szOldName, szNewName) ;
        
经由先呼叫FtpFindFirstFile,可以查找文件(或与含有万用字符的文件名样式相符的多个文件)。此函数很像FindFirstFile函数,甚至都使用了相同的WIN32_FIND_DATA结构。该文件为列举出来的文件传回了一个句柄。您可以将此句柄传递给InternetFindNextFile函数以获得额外的文件名称信息。最后通过呼叫InternetCloseHandle来关闭句柄。

要打开文件,可以呼叫FtpFileOpen。这个函数传回一个文件句柄,此句柄可以用于InternetReadFile、InternetReadFileEx、InternetWrite和InternetSetFilePointer呼叫。最后可以通过呼叫最常用的InternetCloseHandle函数来关闭句柄。

最后,下面两个高级函数特别有用:FtpGetFile呼叫将文件从FTP服务器复制到本地内存,它合并了FtpFileOpen、FileCreate、InternetReadFile、WriteFile、InternetCloseHandle和CloseHandle呼叫。FtpGetFile的另一个参数是一个旗标,如果本地已经存在同名文件,那么该旗标将导致函数呼叫失败。FtpPutFile与此函数类似,用于将文件从本地内存复制到FTP服务器。

更新展示程序


UPDDEMO,如程序23-2所示,展示了用WinInet FTP函数在第二个线程执行期间从匿名FTP服务器上下载文件的方法。

程序23-2 UPDDEMO 
        
UPDDEMO.C
        
/*---------------------------------------------------------------------------
        
  UPDDEMO.C -- Demonstrates Anonymous FTP Access
        
                                                         (c) Charles Petzold, 1998
        
----------------------------------------------------------------------------*/
        

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -