📄 23. 领略internet.txt
字号:
领略Internet
智能中国——游戏组 整理编译
--------------------------------------------------------------------------------
Internet-全世界计算机透过不同协议交换信息的大型连结体-近几年重新定义了个人计算的几个领域。虽然拨接信息服务和电子邮件系统在Internet流行开来之前就已经存在,但它们通常局限于文字模式,并且根本没有连结而是各自分隔的。例如,每一种信息服务都需要拨不同的电话号码,用不同的使用者ID和密码登录。每一种电子邮件系统仅允许在特定系统的缴款使用者之间发送和接收邮件。
现在,往往只需要拨单一支电话就可以连结整个Internet,而且可以和有电子邮件地址的人进行全球通信。特别是在World Wide Web上,超文字、图形和多媒体(包括声音、音乐和视讯)的使用已经扩展了在线信息的范围和功能。
如果要提供涵盖Windows中所有与Internet相关程序设计问题的彻底介绍,可能还需要再加上几本书才够。所以,本章实际上主要集中在如何让小型的Microsoft Windows应用程序能够有效地从Internet上取得信息的两个领域。这两个领域分别是Windows Sockets (Winsock) API和Windows Internet(WinInet)API支持的文件传输协议(FTP:File Transfer Protocol)的部分。
Windows Sockets
Socket是由University of California在Berkeley分校开发的概念,用于在UNIX操作系统上添加网络通讯支持。那里开发的API现在称为「Berkeley socket interface」。
Sockets和TCP/IP
Socket通常(但不专用于)与主宰Internet通信的传输控件协议/因特网协议(TCP/IP:Transmission Control Protocol/Internet Protocol)牵连在一起。因特网协定(IP:Internet Protocol),作为TCP/IP的组成部分之一,用来将数据打包成「数据封包(datagram)」,该资料封包包含用于标识数据来源和目的地的表头信息。而传输控制协议(TCP:Transmission Control Protocol)则提供了可靠的传输和检查IP数据封包正确性的方法。
在TCP/IP下,通讯端点由IP地址和端口号定义。IP地址包括4个字节,用于确定Internet上的服务器。IP地址通常按「由点连结的四个小于255的数字」的格式显示,例如「209.86.105.231」。埠号确定了特定的服务或服务器提供的服务。其中一些埠号已经标准化,以提供众所周知的服务。
当Socket与TCP/IP合用时,Socket就是TCP/IP的通讯端点。因此,Socket指定了IP地址和端口号。
网络时间服务
下面给出的范例程序与提供时间协议(Time Protocol)的Internet服务器相连结。此程序将获得目前准确的日期和时间,并用此信息设定您的PC时钟。
在美国,国家标准和技术协会(National Institute of Standards and Technology)(以前称为国家标准局(National Bureau of Standards))负责维护准确时间,该时间与世界各地的机构相联系。准确时间可用于无线电广播、电话号码、计算机拨号电话号码以及Internet,关于这些的所有文件都位于网站 http://www.bldrdoc.gov/timefreq(网域名称「bldrdoc」指的是Boulder、Colorado、NIST Time的位置和Frequency Division)。
我们只对NIST Network Time Service感兴趣,其详细的文件位于 http://www.bldrdoc.gov/timefreq/service/nts.htm。此网页列出了十个提供NIST时间服务的服务器。例如,第一个名称为time-a.timefreq.bldrdoc.gov,其IP地址为132.163.135.130。
(我曾经编写过一个使用非Internet NIST计算机拨接服务的程序,并发表于《PC Magazine》,您也可以在Ziff-Davis的网站 http://www.zdnet.com/pcmag/pctech/content/16/20/ut1620.001.html中找到。此程序对于想学习如何使用Windows Telephony API的人很有帮助。)
在Internet上有三个不同的时间服务,每一个都由Request for Comment(RFC)描述为Internet标准。日期协议(Daytime Protocol)(RFC-867)提供了一个ASCII字符串用于指出准确的日期和时间。该ASCII字符串的准确格式并不标准,但人们可以理解其中的含义。时间协议(RFC-868)提供了一个32位的数字,用来表示从1900年1月1日至今的秒数。该时间是UTC(不考虑字母顺序,它表示世界时间坐标(Coordinated Universal Time)),它类似于所谓的格林威治标准时间(Greenwich Mean Time)或者GMT-英国格林威治时间。第三个协议称为网络时间协议(Network Time Protocol)(RFC-1305),该协议很复杂。
对于我们的目的,即包括分析Socket和不断更新PC时钟,时间协议RFC-868已经够用了。RFC-868只是一个两页的简短文件,主要是说用TCP获得准确时间的程序应该有如下步骤:
连结到提供此服务的服务器埠37。
接收32位的时间。
关闭连结。
现在我们已经知道了编写存取时间服务的Socket应用程序的每个细节。
NETTIME程序
Windows Sockets API,通常也称为WinSock,与Berkeley Sockets API兼容,因此,可以想象UNIX Socket程序代码可以顺利地拿到Windows上使用。Windows下更进一步的支持由对Berkeley Socket扩充的功能提供,其函数的形式是以WSA(「WinSock API」)为前缀。相关的概述和参考位于/Platform SDK/Networking and Distributed Services/Windows Sockets Version 2。
NETTIME,如程序23-1所示,展示了使用WinSock API的方法。
程序23-1 NETTIME
NETTIME.C
/*----------------------------------------------------------------------------
NETTIME.C -- Sets System Clock from Internet Services
(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#define WM_SOCKET_NOTIFY (WM_USER + 1)
#define ID_TIMER 1
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK MainDlg (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK ServerDlg (HWND, UINT, WPARAM, LPARAM) ;
void ChangeSystemTime (HWND hwndEdit, ULONG ulTime) ;
void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld,
SYSTEMTIME * pstNew) ;
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...) ;
HINSTANCE hInst ;
HWND hwndModeless ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("NetTime") ;
HWND hwnd ;
MSG msg ;
RECT rect ;
WNDCLASS wndclass ;
hInst = hInstance ;
wndclass.style = 0 ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = NULL ;
wndclass.hbrBackground = NULL ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, TEXT ("Set System Clock from Internet"),
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
WS_BORDER | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
// Create the modeless dialog box to go on top of the window
hwndModeless = CreateDialog (hInstance, szAppName, hwnd, MainDlg) ;
// Size the main parent window to the size of the dialog box.
// Show both windows.
GetWindowRect (hwndModeless, &rect) ;
AdjustWindowRect (&rect, WS_CAPTION | WS_BORDER, FALSE) ;
SetWindowPos (hwnd, NULL, 0, 0, rect.right - rect.left,
rect.bottom - rect.top, SWP_NOMOVE) ;
ShowWindow (hwndModeless, SW_SHOW) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
// Normal message loop when a modeless dialog box is used.
while (GetMessage (&msg, NULL, 0, 0))
{
if (hwndModeless == 0 || !IsDialogMessage (hwndModeless, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SETFOCUS:
SetFocus (hwndModeless) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
BOOL CALLBACK MainDlg ( HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
static char szIPAddr[32] = { "132.163.135.130" } ;
static HWND hwndButton, hwndEdit ;
static SOCKET sock ;
static struct sockaddr_in sa ;
static TCHAR szOKLabel[32] ;
int iError, iSize ;
unsigned long ulTime ;
WORD wEvent, wError ;
WSADATA WSAData ;
switch (message)
{
case WM_INITDIALOG:
hwndButton = GetDlgItem (hwnd, IDOK) ;
hwndEdit = GetDlgItem (hwnd, IDC_TEXTOUT) ;
return TRUE ;
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDC_SERVER:
DialogBoxParam (hInst, TEXT ("Servers"), hwnd, ServerDlg, (LPARAM) szIPAddr) ;
return TRUE ;
case IDOK:
// Call "WSAStartup" and display description text
if (iError = WSAStartup (MAKEWORD(2,0), &WSAData))
{
EditPrintf (hwndEdit, TEXT ("Startup error #%i.\r\n"), iError) ;
return TRUE ;
}
EditPrintf (hwndEdit, TEXT ("Started up %hs\r\n"),
WSAData.szDescription);
// Call "socket"
sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
if (sock == INVALID_SOCKET)
{
EditPrintf (hwndEdit, TEXT ("Socket creation error #%i.\r\n"), WSAGetLastError ()) ;
WSACleanup () ;
return TRUE ;
}
EditPrintf (hwndEdit, TEXT ("Socket %i created.\r\n"), sock) ;
// Call "WSAAsyncSelect"
if (SOCKET_ERROR == WSAAsyncSelect (sock, hwnd, WM_SOCKET_NOTIFY, FD_CONNECT | FD_READ))
{
EditPrintf ( hwndEdit, TEXT ("WSAAsyncSelect error #%i.\r\n"), WSAGetLastError ()) ;
closesocket (sock) ;
WSACleanup () ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -