Programowniae windows petzold Petzold24


Rozdział 23
Smak Internetu
Internet - ogromna sieć łącząca komputery z całego świata, implementująca różne
protokoły wymiany danych - zmieniła sposób pojmowania wielu aspektów in-
formatyki ostatnich lat. Choć serwisy elektroniczne i systemy poczty elektronicz-
nej istniały jeszcze przed boomem internetowym, nie były tak atrakcyjne jak dzi-
siejsze. Przede wszystkim dlatego, że były ograruczone do trybu tekstowego i sta-
nowiły osobne systemy. Do każdego systemu informacyjnego należało na przy-
kład wybrać inny numer telefonu (modemu), inaczej trzeba się było logować itd.
Każdy system pocztowy pozwalał na wysyłanie i odbieranie poczty wyłącznie
między osobami zapisanymi do ruego.
Dziś wybierając jeden numer telefonu, uzyskujemy dostęp do wszystkich ogól-
noświatowych zasobów Intemetu oraz do uniwersalnego systemu wymiany pocz-
ty. Zakres i uniwersalność usług elektronicznych rozszerzyła w istotny sposób
sieć World Wide Web, a także zastosowanie hipertekstu, grafiki i multimediów
(obejmujących dźwięk, muzykę i wideo).
Kompletny podręcznik uwzględniający wszystkie tematy programistyczne Mi-
crosoft Windows związane z Internetem zająłby prawdopodobnie kilka opasłych
tomów. W tym rozdziale poruszam dwa typy zagadnień, które mogą się okazać
przydatne w małych aplikacjach Microsoft Windows pobierających dane z Inter-
netu. Mowa w nim będzie o korzystaniu z systemu WinInet (Windows Internet
API) za pomocą API do obsługi gniazd Windows (WinSock od Windows Sockets)
oraz z API do obsługi protokołu FTP (File Transfer Protocol).
Gniazda Windows
Koncepcję gniazd (ang. sockets) opracowano w University of Califomia w Berkeley.
Grazda miały uzupełruć system operacyjny UNIX o obshzgę komunikacji sieciowej.
Opracowany tam interfejs API znany jest pod nazwą interfejsu gniazd z Berkeley.
Gniazda a TCP/IP
Gniazda są generalnie (choć nie tylko) używane łącznie z protokołami TCP/IP
(Transmission Control Protocol/Internet Protocol), które zdominowały komunikację
internetową. Protokół IP (Internet Protocol), stanowiący składnik TCP/IP, definiu-
je pakowanie danych w datagramy, które rue zawierają informacji nagłówkowych
identyfikujących adres źródłowy i docelowy danych. Niezawodny transport da-
tagramów IP oraz kontrolę błędów zapewnia protokół TCP (Transmission Control
Protocol).
1254 Część III: Zagadnienia zaawansowane
W TCP/IP punkt końcowy komunikacji jest definiowany przez adres IP i numer
portu. Adres IP składa się z 4 bajtów identyfikujących serwer w Internecie. Ma
on tak zwany format z kropkami, który polega na zapisie owych czterech bajtów
w postaci liczb dziesiętnych oddzielonych kropkami, na przykład 209.86.105.231.
Numer portu identyfikuje konkretną usługę prowadzoną przez serwer. Niektóre
numery portów są ustandaryzowane, co oznacza, że pewna grupa usług ma nu-
mery portów przypisane na stałe.
Gniazdo w kontekście TCP/IP oznacza punkt końcowy komunikacji TCP/IP
Można je więc identyfikować za pomocą adresu IP i numeru portu.
Sieciowe usługi związane z czasem
Program przykładowy, który za chwilę omówię, łączy się z serwerem interneto-
wym oferującym usługę zwaną Time Protocol. Program odczytuje bieżącą datę
i godzinę i synchronizuje z odczytanymi parametrami komputer osobisty, w któ-
rym działa.
W Stanach Zjednoczonych za utrzymanie prawidłowego czasu (w połączeniu
z innymi biurami z całego świata) odpowiedzialny jest National Institute of Stan-
dards and Technology (NIST - Narodowy Instytut Norm i Techniki), funkcjonu-
jący kiedyś pod nazwą National Bureau of Standards (Narodowego Biura Norm).
Dokładny czas jest udostępniany przez publicznych nadawców radiowych, tele-
foniczne systemy informacyjne, elektroruczne systemy komputerowe dostępne
przez modem oraz przez Internet. Dokumentację wszystkich tych źródeł można
znaleźć na stronie WWW http://www.bldrdoc.gov/timefreq. Nazwa domeny bldr-
doc oznacza Boulder, Colorado, siedzibę oddziału NIST zajmującego się czasem
(Time and Frequency Division).
Nas interesuje przede wszystkim usługa o nazwie NIST Network Time Service,
której dokładniejszy opis można znaleźć na stronie WWW http://www.bldr-
doc.gov/timefreq/service/nts.htm. Zawiera ona listę dziesięciu serwerów świad-
czących usługi "zegarynkowe" NIST. Pierwszy nazywa się time-a.timefreq.bldr-
doc.gov i ma adres IP 132.163.135.130.
Napisałem również program, który korzysta z nieintemetowego serwera NIST.
Opublikował go "PC Magazine". Jest on dostępny ze strony WWW Ziff-Davis
httpJ/www.zdnet.com/pcmag/pctech/content/16/20/ut1620.001.html. Program ten
polecam wszystkim, którzy pragną zgłębić Windows Telephony API.
Przez Internet dostępne są trzy różne usługi. Wszystkie są opisane w dokumen-
tach RFC (Request for Comment), materiałach źródłowych opisujących standardy
internetowe. Daytime Protocol (RFC-867) udostępnia łańcuch ASCII zawierający
dokładną datę i godzinę. Dokładny format łańcucha ASCII nie jest całkiem stan-
dardowy, a to dlatego, że został pomyślany jako czytelny dla ludzi. Time Proto-
col (RFC-868) udostępnia 32-bitową liczbę oznaczającą liczbę sekund od 1 stycz-
nia 1900 roku. Ten czas jest w formacie LTTC (który mimo złej kolejności liter skrótu
oznacza koordynowany czas uniwersalny, ang. Coordinated Universal Time), bar-
dzo podobny do formatu nazywanego kiedyś GMT lub Greenwich Mean Time -
czas z Greenwich. Trzeci protokół nazywa się Network Time Protocol (RFC-1305)
i jest dość złożony.
Rozdział 23: Smak Internetu 1255
Dla naszych potrzeb - którymi są poznanie gniazd i skuteczna aktualizacja cza-
! su komputera osobistego - najlepszy jest Time Protocol. RFC-868 to krótki, dwu-
stronicowy dokument, który można streścić, podając trzy kolejne czynności, ja-
kie powinien wykonać program, aby uzyskać dokładny czas poprzez TCP:
l. Połączyć się z portem numer 37 serwera oferującego usługę.
2. Odebrać 32-bitowy czas.
3. Zamknąć połączenie.
Mamy więc wszystko, co jest potrzebne do napisania aplikacji opartej na techni-
ce gniazd, która będzie odczytywała godzinę z serwera-zegarynki.
Program NETTIME
Windowsowy interfejs API dla gniazd, nazywany powszechnie WinSockiem, jest
kompatybilny z API dla gniazd z Berkeley. Oznacza to, że programy systemu
UMX korzystające z gniazd powinny prawie bez problemów dać się przenieść
do Windows. Implementacja z Windows jest jednak rozszerzona w stosunku do
implementacji Berkeley - zmieniona została postać nazw funkcji, które w Win-
dows zaczynają się od prefiksu WSA (WinSock API). Ich przegląd i opis znajduje
się w /Platform SDIC/IVetworking and Distributed Services/Windows Sockets Uersion
2.
Program NETTIME, przedstawiony na rysunku 23-1, demonstruje, jak korzystać
z WinSock API.
NETTIME.C
/*
NETTIME.C - Program synchronizujdcy zegar systemowy z godziną
odczytaną z usług internetowych
(c) Charles Petzold, 1998
*/
ilinclude
ilinclude "resource.h"
4ldefine WM SOCKET NOTIFY (WM USER + 1)
ifdefine 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, ULON6 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)
1256 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1255)
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 PPLICATION) ; '
wndclass.hCursor = NULL ;
wndclass.hbrBackground = NULL ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
(
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ;
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, CWUSEDEFAULT, !
NULL, NULL, hInstance, NULL) ;
// utworzenie okna potomnego
hwndModeless = CreateDialog (hInstance, szAppName, hwnd, MainDlg) ;
// rozmiar okna nadrzędnego równy rozmiarowi okna dialogowego
// wyświetlenie obu okien
GetWindowRect (hwndModeless, &rect) ;
AdjustWindowRect (&rect, WSCAPTION WSBORDER, 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) ;
// zwykla pętla komunikatów
while (GetMessage (&msg, NULL, 0, 0))
(
if (hwndModeless = 0 !IsDialogMessage (hwndModeless, &msg))
Rozdział 23: Smak Internetu
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
l
l
return msg.wParam ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
f
switch (message)
(
case WM_SETFOCUS:
SetFocus (hwndModeless) ;
return 0 ;
% case WMDESTROY:
PostOuitMessage (0> ;
return 0 ;
,
return DefWindowProc (hwnd, message, wParam, lParam) ;
)
BOOL CALLBACK MainDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
1
static char szIPAddrC327 = ( "132.163.135.130" 1 ;
static HWND hwndButton, hwndEdit ;
static SOCKET sock ;
static struct sockaddr_in sa ;
static TCHAR szOKLabelC327 ;
int iError, iSize ;
unsigned long ulTime ;
WORD wEvent, wError ;
WSADATA WSAData ;
, switch (message)
,
case WM_INITDIALOG:
hwndButton = GetDlgItem (hwnd, IDOK) ;
hwndEdit = GetDlgItem (hwnd, IDCTEXTOUT) ;
return TRUE ;
case WM COMMAND:
switch (LOWORD (wParam))
(
case IDC_SERVER:
DialogBoxParam (hInst, TEXT ("Servers"), hwnd, ServerDlg,
(LPARAM) szIPAddr) ;
return TRUE ;
case IDOK:
// wywołanie "WSAStartup" i wyświetlenie opisu
if (iError = WSAStartup (MAKEWORD(2.0), &WSAData))
(
EditPrintf (hwndEdit, TEXT ("Startup error %i.\r\n"),
iError) ;
return TRUE ;
Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1257)
EditPrintf (hwndEdit, TEXT ("Started up %hs\r\n"),
WSAData.szDescription);
// wywołanie "socket"
sock = socket (AF INET, SOCK STREAM. IPPROTO TCP) ; '
if (sock == INVALID SOCKET)
(
EditPrintf (hwndEdit,
TEXT ("Error creating socket l%i.\r\n"),
WSAGetLastError ()) ;
WSACleanup () ;
return TRUE ;
,
EditPrintf (hwndEdit, TEXT ("Socket %i created.\r\n"), sock) ;
// wywolanie funkcji "WSAAsyncSelect"
if (SOCKETERROR = WSAAsyncSelect (sock, hwnd, WM_SOCKET_NOTIFY,
FDCONNECT FD READ))
EditPrintf (hwndEdit, ,
TEXT ("WSAAsyncSelect error l%i.\r\n"),
WSAGetLastError ()) ;
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
)
// wywolanie "connect" z adresem IP !
// i numerem portu serwera
sa.sin family = AFINET ;
sa.sinport = htons (IPPORT_TIMESERVER) ;
sa.sin "ddr.S un.S "ddr = inet addr (szIPAddr) ;
connect(sock, (SOCKADDR *) &sa, sizeof (sa)) ;
// Funkcja "connect" zwróci SOCKET ERROR i nawet jeżeli
// zakończy się powodzeniem, będzie wymagala blokowania.
// Poniżej raportowanie tylko blędów niespodziewanych.
if (WSAEWOULDBLOCK != (iError = WSAGetLastError ()))
t
EditPrintf (hwndEdit, TEXT ("Connect error it%i.\r\n"),
iError) ;
closesocket (sock) ;
WSACleanup () ;
return TRUE ;
)
EditPrintf (hwndEdit, TEXT ("Connecting to %hs..."). szIPAddr) ;
// Wynik funkcji "connect" będzie zwrócony
// poprzez komunikat WMSOCKET NOTIFY.
Rozdział 23: Smak Internetu 1259
// Ustawienie zegara i zmiana przycisku na "Cancel"
SetTimer (hwnd, ID_TIMER, 1000, NULL) ;
GetWindowText (hwndButton, szOKLabel, sizeof (szOKLabel) /
sizeof (TCHAR>) ;
SetWindowText (hwndButton, TEXT ("Cancel")) ;
SetWindowLong (hwndButton, GWLID, IDCANCEL) ;
return TRUE ;
case IDCANCEL:
closesocket (sock) ;
sock = 0 ;
WSACleanup () ;
SetWindowText (hwndButton, szOKLabel) ;
SetWindowLong (hwndButton, GWL ID, IDOK) ;
KillTimer (hwnd, ID_TIMER) ;
EditPrintf (hwndEdit, TEXT ("\r\nSocket closed.\r\n")) ;
return TRUE ;
case IDC_CLOSE:
if (sock)
SendMessage (hwnd, WM COMMAND, IDCANCEL, 0) ;
DestroyWindow (GetParent (hwnd)) ;
return TRUE ;
)
return FALSE ;
case WM TIMER:
EditPrintf (hwndEdit, TEXT (".")) ;
return TRUE ;
case WM_SOCKET_NOTIFY:
wEvent = WSAGETSELECTEVENT (lParam) ; // ie, LOWORD
wError = WSAGETSELECTERROR (lParam) ; // ie, HIWORD
// przetwarzanie dwóch zdarzeń WSAAsyncSelect
switch (wEvent)
f
// to zdarzenie występuje na skutek wywolania "connect"
case FD CONNECT:
EditPrintf (hwndEdit, TEXT ("\r\n")) ;
if (wError)
(
EditPrintf (hwndEdit, TEXT ("Connect error %i.").
wError) ;
SendMessage (hwnd, WM COMMAND, IDCANCEL, 0) ;
return TRUE ;
)
EditPrintf (hwndEdit, TEXT ("Connected to %hs.\r\n"), szIPAddr) ;
// Próba odebrania danych. Wywolanie spowoduje błąd
// WSAEWOULDBLOCK i zdarzenie FD READ
1260 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1259)
recv (sock, (char *) &ulTime, 4, MSGPEEK) ;
EditPrintf (hwndEdit, TEXT ("Waiting to receive...")) ;
return TRUE ; I
// do tego zdarzenia dochodzi nawet wtedy,
// kiedy można wywołać "recv"
case FD_READ:
I
KillTimer (hwnd, IDTIMER) ;
EditPrintf (hwndEdit, TEXT ("\r\n")) ;
if (wError)
(
EditPrintf (hwndEdit, TEXT ("FDREAD error %i.">,
wError) ;
SendMessage (hwnd, WM COMMAND, IDCANCEL, 0) ;
return TRUE ;
l
// odczytanie czasu i zamiana bajtów
iSize = recv (sock, (char *) &ulTime, 4, 0) ;
ulTime = ntohl (ulTime) ;
EditPrintf (hwndEdit,
TEXT ("Received current time of %u seconds ")
TEXT ("since Jan. 1 1900.\r\n"), ulTime) ;
// zmiana daty systemowej
ChangeSystemTime (hwndEdit, ulTime) ;
SendMessage (hwnd, WM COMMAND, IDCANCEL, 0) ;
return TRUE ;
)
return FALSE ;
?
return FALSE ;
)
BOOL CALLBACK ServerDlg (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static char * szServer ;
static WORD wServer = IDC SERVER1 ;
char szLabel [64) ;
switch (message)
case WM_INITDIALOG:
szServer = (char *) lParam ;
CheckRadioButton (hwnd, IDC SERVERl, IDCSERVERlO, wServer) ;
return TRUE ;
case WM COMMAND:
switch (LOWORD (wParam))
(
case IDC_SERVERl:
case IDC_SERVER2:
case IDC SERVER3:
I
Rozdział 23: Smak Internetu 1261
case IDC_SERVER4:
case IDC_SERVER5:
case IDC_SERVER6:
case IDC_SERVER7:
case IDC_SERVERB:
case IDC_SERVER9:
case IDC_SERVERlO:
wServer = LOWORD (wParam) ;
return TRUE ;
case IDOK:
GetDlgItemTextA (hwnd, wServer, szLabel, sizeof (szLabel)) ;
strtok (szLabel, "(") ;
strcpy (szServer, strtok (NULL, ")")) ;
EndDialog (hwnd, TRUE) ;
return TRUE ;
case IDCANCEL:
EndDialog (hwnd, FALSE) ;
return TRUE ;
)
break ;
)
return FALSE ;
void ChangeSystemTime (HWND hwndEdit, ULONG ulTime)
(
FILETIME ftNew ;
LARGE_INTEGER li ;
SYSTEMTIME stOld, stNew ;
GetLocalTime (&stOld) ;
stNew.wYear = 1900 ;
stNew.wMonth = 1 ;
stNew.wDay = 1 ;
stNew.wHour = 0 ;
stNew.wMinute = 0 ;
stNew.wSecond = 0 ;
stNew.wMilliseconds = 0 ;
SystemTimeToFileTime (&stNew, &ftNew) ;
li = * (LARGEINTEGER *) &ftNew ;
li.OuadPart += (LONGLONG) 10000000 * ulTime ;
ftNew = * (FILETIME *) &li ;
FileTimeToSystemTime (&ftNew, &stNew) ;
if (SetSystemTime (&stNew))
(
GetLocalTime (&stNew) ;
FormatUpdatedTime (hwndEdit, &stOld, &stNew) ;
1
else
EditPrintf (hwndEdit, TEXT ("Could NOT set new date and time.")) ;
1
void FormatUpdatedTime (HWND hwndEdit, SYSTEMTIME * pstOld, SYSTEMTIME * pstNew)
(
12g2 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1261)
TCHAR szDate0ld C64], szTime0ld C64], szDateNew [64], szTimeNew C647 ;
GetDateFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE DATE_SHORTDATE,
pstOld, NULL, szDate0ld, sizeof (szDate0ld)) ;
GetTimeFormat (LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE
TIME_NOTIMEMARKER TIME_FORCE24HOURFORMAT,
pstOld, NULL, szTime0ld, sizeof (szTime0ld)) ;
GetDateFormat (LOCALĘ USER DEFAULT, LOCALĘ NOUSEROVERRIDE DATESHORTDATE,
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 ("z\r\n\t%s, %s.%03i na\r\n\t%s, %s.%03i."),
szDate0ld, szTime0ld, pstOld->wMilliseconds,
szDateNew, szTimeNew, pstNew->wMilliseconds) ;
)
void EditPrintf (HWND hwndEdit, TCHAR * szFormat, ...)
(
TCHAR szBuffer [1024] ;
valist pArgList ;
vastart (pArgList, szFormat) ;
wvsprintf (szBuffer, szFormat, pArgList) ;
vaend (pArgList) ;
SendMessage (hwndEdit, EM_SETSEL, (WPARAM) -1, (LPARAM) -1> ;
SendMessage (hwndEdit, EM_REPLACESEL, FALSE, (LPARAM) szBuffer) ;
SendMessage (hwndEdit, EMSCROLLCARET, 0, 0) ;
)
NETTIME.RC (fragmenty) .
//Microsoft Developer Studio generated resource script.
ilinclude "resource.h"
tlinclude "afxres.h"
/////////////i///////////////////////////////////////////////////////////////
// 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
Rozdział 23: Smak Internetu 1263
PUSHBUTTON "Cancel",IDCANCEL,l50,181,50,14
CONTROL
"time-a.timefreq.bldrdoc.gov (132.163.135.130) NIST, Boulder, Colorado",
IDCSERVERl,"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 UTORADIOBUTTON,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
CONTROL
CONTROL
CONTROL
CONTROL
CONTROL
END
"time.nist.gov (192.43.244.18) NCAR, Boulder, Colorado",
IDCSERVER5,"Button",BS AUTORADIOBUTTON,9,75,256,16
"time-a.nist.gov (129.6.16.35) NIST, Gaithersburg, Maryland",
IDC SERVER6,"Button",BS UTORADIOBUTTON,9,92,256,16
"time-b.nist.gov (129.6.16.36) NIST, Gaithersburg, Maryland",
IDCSERVER7,"Button",BS AUTORADIOBUTTON,9,109,256,16
"time-nw.nist.gov (131.107.1.10) Microsoft, Redmond, Washington",
IDCSERVER8,"Button",BS AUTORADIOBUTTON,9,126,256,16
"utcnist.reston.mci.net (204.70.131.13) MCI, Reston, Virginia",
IDCSERVER9,"Button",BS AUTORADIOBUTTON,9,143,256,16
"nistl.data.com (209.0.72.7) Datum, San Jose, California",
IDCSERVERlO,"Button",BS AUTORADIOBUTTON,9,160,256,16
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,l83,129,80,14
PUSHBUTTON "Select Server... ,IDC_SERVER,7,129,80,14
EDITTEXT IDC_TEXTOUT,7,7,253,110,ES_MULTILINE ES_AUTOVSCROLL
ESREADONLY WS VSCROLL NOT WS TABSTOP
END
RESOURCE.H (fragmenty)
// 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
1264 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1263)
tldefine IDC_SERVER7 1007
Ildefine IDC_SERVER8 1008
tldefine IDC_SERVER9 1009
ildefine IDC_SERVERlO 1010
ipdefine IDC_SERVER 1011
Idefine IDCCLOSE 1012
Rysunek 23-1. Program NETTIME
Program NETTIME tworzy beztrybowe okno dialogowe oparte na wzorcu NET
TIME z NETITME.RC. Okno programu jest wymiarowane tak, aby okno beztry-
bowe pokrywało cały obszar roboczy programu. Okno dialogowe składa się z pola
edycji działającego w trybie tylko do odczytu (do którego program wpisuje in-
formacje tekstowe) oraz przycisków: Select Server, Set Correct Time i Close. Przy-
cisk Close kończy działanie programu.
Do przechowywania adresu serwera służy zmienna szIPAddr z MainDlg. Domyśl-
nie jest tam wpisany ciąg 132.163.135.130. Przycisk Select Server wywołuje okno
dialogowe oparte na szablonie SERVERS z pliku NETTIME.RC. Zmienna szIPAddr
jest przekazywana jako ostatni argument do DialogBoxParam. Okno dialogowe
Serwery zawiera listę dziesięciu serwerów (skopiowaną prawie znak w znak ze
strony WWW instytutu NIST) oferujących interesującą nas usługę. Kiedy użyt-
kownik wybierze jeden z nich, ServerDlg przetwarza tekst przycisku, aby uzy-
skać adres IP. Nowy adres jest zapisywany w zmiennej szIPAddr.
Kiedy użytkownik naciśnie przycisk Set Correct Time, generowany jest komuni-
kat WMCOMMAND z młodszym słowem wParam równym IDOK. Przetwarza-
nie IDOK w MainDlg odbywa się tam, gdzie większość początkowych operacji
związanych z gniazdami.
Pierwsza funkcja, która musi być wywołana przez każdy program Windows uży-
wający Windows Sockets API, to:
iError = WSAStartup (wVersion, &WSAData);
W NETTIME jej pierwszy argument jest ustawiany na 0x0200 (co oznacza wersję
2.0). Po powrocie struktura WSAData zawiera dane o implementacji Windows
Sockets, a NETTIME wyświetla łańcuch szDescription. W ten sposób otrzymuje-
my po prostu informację o wersji.
NETTIME wywołuje następnie funkcję socket, mniej więcej tak:
sock = socket (AFINET, SOCK STREAM, IPPROTO TCP);
Pierwszym argumentem jest rodzina adresów, oznaczona tutaj jako pewien ro-
dzaj adresu internetowego. Drugi argument oznacza, że dane mają zostać zwró-
cone w postaci strumienia, a nie w datagramach (dane, których oczekujemy, to
tylko 4 bajty; datagramów używa się w przypadku większych porcji danych).
Ostatni argument to protokół. W programie używamy protokołu internetowego
o nazwie TCP (Transmission Control Protocol), jednego z dwóch wymienionych
w dokumencie RFC-868. Zwrotna wartość funkcji socket jest zapisywana w zmien-
nej typu SOCKET. Jest ona następnie wykorzystywana w późniejszych wywoła-
niach funkcji.
Rozdział 23: Smak Internetu 1265
Program NETTIME wywohzje następnie WSAAsynchSelect, kolejną funkcję gniaz-
dową charakterystyczną dla Windows, aby uniknąć zawieszenia aplikacji spo-
wodowanego długim czasem odpowiedzi Intemetu. W dokumentacji WinSock
niektóre funkcje są nazywane "blokującymi". Oznacza to, że te nie oddają stero-
wania do programu, dopóki nie zrealizują zleconego im zadania. Funkcja WSA-
AsyncSelect ma za zadanie wymusić na funkcjach, które normalnie są blokujące,
aby stały się nieblokujące, czyli zwracały sterowanie do programu, zanim ukoń-
czą wykonywanie operacji. Wynik funkcji nieblokującej wraca do programu w
postaci komunikatu. Funkcja WSAAsyncSelect pozwala aplikacji określić wartość
liczbową komunikatu oraz okno, które ma otrzymać komunikat. Funkcja ma na-
stępującą ogólną składnię:
WSAAsyncSelect (sock, hwnd, message, iConditions);
W NETTIME zastosowano do tego celu komunikat niestandardowy WMSOC-
KET NOTIFY. Ponadto określono za pomocą ostatniego argumentu WSAAsync-
Select warunki, w jakich ma zostać wysłany komunikat: nawiązanie połączenia
i odebranie danych (FD CONNECT I FD READ).
Kolejna funkcja WinSock w NETTIME to connect. Wymaga ona wskaźnika do
struktury z adresem gniazda, która może być różna dla różnych protokołów.
NETTIME używa wersji struktury przeznaczonej dla TCP/IP:
struct sockaddr in
(
short sin_family;
ushort sinport;
struct in "ddr sinaddr;
char sin zero[8];
? ;
przy czym in "ddr to unia, w której adres internetowy można określić jako 4 baj-
. ty, jako 2 zmienne unsigned short lub jako zmienną unsigned long.
W NETTIME pole sin'amily jest równe AF INET, co oznacza rodzinę adresów.
Pole sinport ma wartość numeru portu, w tym przypadku numeru portu dla
protokołu Time Protocol, który w dokumencie RFC-868 określono na 37. Nie na-
leży jednak po prostu przypisać temu polu wartości 37, jak to pierwotnie zrobi-
łem. Jak większość liczb przesyłanych przez Intemet, pole z numerem portu musi
być w formacie big-endian: najbardziej znaczący bit pierwszy. W procesorach
Intela stosowany jest format little-endian. Na szczęście jest funkcja htons (host-to-
network short), która zamienia bajty, więc polu sinport struktury sockaddr in na-
leży przypisać wartość:
htons (IPPORT TIMESERVER)
Powyższa stała jest w WINSOCK2.H zdefiniowana jako 37. Program dokonuje za
pomocą funkcji inet "ddr konwersji adresu serwera zapisanego w łańcuchu szIPAddr
na liczbę typu unsigned long, którą następnie przypisuje polu sin "ddr tej struktury.
Jeżeli jakaś aplikacja wywołuje w Windows 98 funkcję connect, a Windows nie
jest w danej chwili podłączony do Internetu, ukazuje się okno kreatora połączeń.
Jest to funkcja znana jako AutoDial. Nie ma jej w Windows NT 4.0, jeżeli więc
używa się NT, przed uruchomieniem NETTIME należy uaktywnić połączenie
z Internetem.
1266 CzęśE III: Zagadnienia zaawansowane
Funkcja connect jest zazwyczaj blokująca, ponieważ zanim zostanie nawiązane po-
łączenie, upływa na ogół sporo czasu. Ponieważ jednak wywołana została funkcja
WSAAsyncSelect, funkcja connect nie będzie czekała na połączenie, lecz natychmiast
zwróci sterowarue i kod błędu SOCKET ERROR. Nie jest to w rzeczywistości ża-
den błąd - funkcja informuje jedynie w ten sposób o tym, że nie zostało nawiąza-
ne żadne połączenie. W NETTIME nie sprawdzamy nawet kodu powrotu, tylko
wywołujemy funkcję WSAGetLnstError. Jeżeli zwróci ona wartość WSAEWOULD-
BLOCK (oznaczającą, że spowodowałaby zablokowanie programu, ale tego nie
zrobi), oznacza to, że wszystko gra. NETTIME zmienia przycisk Set Correct Time
na przycisk Anuluj i ustawia zegar na 1 sekundę. Przetwarzanie komunikatu
WMTIMER polega na wyświetlaniu w oknie programu kropek, informujących
użytkowruka, że coś się dzieje i że program się nie zawiesił.
Kiedy w końcu zostanie nawiązane połączenie, procedura MainDlg otrzyma ko-
munikat WM SOCKET NOTIFY - zdefiniowany wcześniej w programie za
pomocą funkcji WSAAsyncSelect. Młodsze słowo IParam będzie równe FD CON-
NECT, a starsze może wskazywać na błąd. Błąd na tym etapie oznacza, że pro-
gram nie mógł uzyskać połączenia z serwerem. NETTIME pozwala wybrać je-
den z dziesięciu serwerów, więc można sobie popróbować!
Jeżeli wszystko pójdzie dobrze, NETTIME wywoła funkcję recv (receive), która
ma odebrać dane:
recv (sock, (char *) &ulTime, 4, MSGPEEK);
Oznacza to, że do zmiennej ulTime mają trafić 4 bajty. Ostatni argument określa,
że dane mają zostać tylko odczytane, a nie usunięte z kolejki wejściowej. Podob-
nie jak connect, funkcja recv zwraca kod błędu ozrtaczający, że normalnie funkcja
zablokowałaby program, ale nie zrobi tego ze względu na wcześniejsze zalece-
nia programu. Teoretycznie (choć jest to mało prawdopodobne) funkcja może
zwrócić tylko część danych. Musiałaby wówczas zostać wywołana ponownie
w celu pobrania brakujących bajtów 32-bitowej wartości. Dlatego właśnie jest wy-
woływana z opcją MSG PEEK.
Funkcja recv, również podobnie jak connect, generuje komunikat WM SOC-
KET NOTIFY, tym razem z kodem zdarzenia równym FD READ. NETTIME
odpowiada na to ponownym wywołaniem recv, tym razem z ostatnim argumen-
tem 0, aby usunąć dane z kolejki. Teraz opiszę pokrótce, co program robi z otrzy-
maną wartością ulTime. Zauważ, że NETTIME kończy przetwarzanie komunika-
tu wysłaniem do siebie samego komunikatu WM COMMAND z parametrem
wParam równym IDCANCEL. Procedura okna dialogowego odpowiada na to
wywołaniem closesocket i WSACleanup.
Przypomnij sobie, że 32-bitowa wartość ulTime, którą otrzymuje NETITME, jest liczbą
sekund, jakie upłynęły od 0:00 UTC 1 stycznia 1900 roku. Ale najbardziej znaczący
bajt jest pierwszy, więc liczbę trzeba przepuścić przez funkcję ntohl (network-to-
host long), zmieniającą kolejność bajtów do postaci akceptowalnej dla mikroproce-
sorów Intela. NETTIME wywołuje następnie funkcję ChangeSystemTime.
Funkcja ChangeSystemTime zaczyna się od uzyskania bieżącego czasu lokalnego
- to znaczy bieżącego czasu systemowego wyrażonego w formacie właściwym
dla strefy czasowej użytkownika i uwzględniającym czas letni/zimowy. Następ-
Rozdział 23: Smak Internetu 1267
nie inicjowana jest struktura SYSTEMTIME. Program przypisuje jej czas godziny
zero z 1 stycznia 1900 roku. Struktura SYSTEMTIME jest przekazywana do funk-
cji SystemTimeToFileTime, która dokonuje konwersji danych na strukturę FILETI-
ME. FILETIME to po prostu dwa podwójne słowa (DWORD, liczby 32-bitowe)
tworzące łącznie wartość 64 bitową, która oznacza liczbę 100-nanosekundowych
jednostek, które upłynęły od 1 stycznia 1601 roku.
W funkcji ChangeSystemTime struktura FILETIME jest rzutowana na LARGĘ IN-
TEGER, czyli na unię, za pomocą której do liczby 64-bitowej można uzyskać do-
stęp poprzez dwie liczby 32-bitowe albo poprzez jedną liczbę 64-bitową typu
intó4 (ten typ to wprowadzone przez Microsoft rozszerzenie standardu ANSI
C). Tak więc wartość ta stanowi liczbę 100-nanosekundowych jednostek czasu
między 1 stycznia 1601 roku i 1 stycznia 1900 roku. Do wartości tej dodawana
jest liczba 100-nanosekundowych jednostek czasu od 1 stycznia 1900 roku do dziś
-10 000 000 razy ulTime.
Wynikowa wartość FILETIME jest następnie przekształcana z powrotem na struk-
turę SYSTEMTIME (za pomocą wywołania FileTimeToSystemTime). Ponieważ Time
Protocol zwraca bieżący czas UTC, program NETTIME ustawia czas za pomocą
wywołania SetSystemTime, funkcji operującej również na czasie w formacie UTC.
Aby go wyświetlić, program uzyskuje zaktualizowany czas, wywołując funkcję
GetLocalTime. NETTIME przekazuje pierwotny czas lokalny oraz nowy czas do
funkcji FormatUpdatedTime, która za pomocą funkcji GetTimeFormat i GetDateFor-
mat przekształca obie wartości do postaci przystępnych łańcuchów znaków ASCII.
Funkcja SetSystemTime może zakończyć się błędnie, jeżeli program zostanie uru-
chomiony w Windows NT, a użytkownik nie będzie miał stosownych uprawnień
do modyfikowania czasu. W takim przypadku program powiadomi użytkowni-
ka o problemie za pomocą komunikatu informującego, iż czas systemowy nie
został zmieniony.
Winlnet i FTP
WinInet (Windows Internet) API to zbiór wysokopoziomowych funkcji pomagają-
cych programiście korzystać z popularnych protokołów Internetu: HTTP (Hyper-
text Transfer Protocol, protokołu World Wide Web), FTP (File Transfer Protocol) i Go-
pher (mniej popularny system dostępu do rozproszonych informacji). Składnia
funkcji WinInet przypomina składnię zwykłych funkcji plikowych Windows, przez
co posługiwanie się tymi protokołami jest prawie tak samo proste jako korzysta-
nie z plików z dysku lokalnego. Winlnet API udokumentowano w /Platform SDK/
Internet, Intranet, Extranet Services/Internet Tools and Technologies/Winlnet API.
Program przykładowy UPDDEMO zaprezentuje, jak korzystać z elementów Wi-
nInet API zapewniających obsługę FTP Wiele firm posiadających strony WWW
oferuje również dostęp do anonimowego serwera FTP, z którego użytkownicy
mogą ściągać pliki bez logowania. Jeżeli w polu adresowym programu Internet
Explorer wpiszesz na przykład adres ftp://ftp.microsoft.com, uzyskasz dostęp do
anonimowego FTP firmy Microsoft. Jeżeli użyjesz adresu ftp://ftp.cpetzold.com/
cpetzold.com/ProgWin/UpdDemo, otrzymasz listę plików dostępnych z mojego
1268 Czgść III: Zagadnienia zaawansowane
anonimowego FTP (związanych z programem przykładowym, który niebawem
omówię).
Serwery FTP są dziś uważane przez wielu użytkowników Internetu za niezbyt
przyjazne, ale wciąż są bardzo przydatne. Program użytkowy może pobierać dane
FTP prawie automatycznie, bez interwencji użytkownika. Na podstawie takiego
pomyshz napisałem program UPDDEMO (update demonstration), którym zajmie-
my się za chwilę.
Przegląd FTP API
Program korzystający z WinInet musi w każdym pliku źródłowym z wywoła-
niem funkcji WinInet zawierać dyrektywy włączające plik nagłówkowy WINI-
NET.H. Musi być ponadto skonsolidowany z biblioteką WININET.LIB. Sprawę
konsolidacji z biblioteką można załatwić w Microsoft Visual C++, na karcie Link
w oknie dialogowym Project Settings. W czasie wykonywania program jest kon-
solidowany z biblioteką dynamiczną WININET.DLL.
W poniższym omówieniu nie będę się wdawał w szczegóły dotyczące składni
funkcji, ponieważ niektóre funkcje mają skomplikowaną postać, z wieloma róż-
nymi opcjami. Aby rozpocząć używanie WinInet, możesz wykorzystać kod źró-
dłowy programu UPDDEMO jako swego rodzaju książkę kucharską. Na razie
najważniejsze jest zrozumienie poszczególnych etapów związanych z używaniem
funkcji FTP.
Chcąc skorzystać z dowolnej z funkcji Windows Internet API, należy najpierw
wywołać InternetOpen. Protokołów obshxgiwanych przez WinInet można używać
dopiero po wywołaniu tej funkcji. InternetOpen zwraca uchwyt do sesji interneto-
wej; jego wartość należy zapisać w zmiennej typu HINTERNET. Po zakończeniu
używania WinInet API uchwyt ten należy zamknąć, wywohxjąc funkcję InternetC-
IoseHandle.
Aby użyć FTP, należy wywołać funkcję InternetConnect. Wymaga ona uchwytu
sesji internetowej uzyskanego z InternetOpen, a zwraca uchwyt do sesji FTP. Tego
uchwytu używa się z kolei jako pierwszego argumentu we wszystkich wywoła-
niach funkcji z prefiksem Ftp. W argumentach funkcji InternetConnect określa się
zapotrzebowanie na korzystanie z FTP oraz nazwę serwera, na przykład ftp.cpet-
zold.com. Funkcja wymaga również podania nazwy i hasła użytkownika. Jako
argumenty te można wysłać wartości NULL, jeśli serwer FTP zapewnia dostęp
anonimowy. Jeżeli komputer jest podłączony do Internetu, w chwili wywołania
funkcji InternetConnect Windows 98 wyświetli okno kreatora połączenia. Po za-
kończeniu pracy z FTP program powinien zamknąć uchwyt za pomocą funkcji
InternetCIoseHandle.
Teraz można rozpocząć wywoływanie funkcji z prefiksem Ftp. Jak się okaże, są
one bardzo podobne do zwykłych funkcji plikowych Windows. Aby uniknąć
powtarzania się tych samych funkcji dla różnych protokołów, przewidziano kil-
ka funkcji z prefiksem Internet; można ich również używać do obshxgi FTP.
Następujące cztery funkcje pozwalają wykonywać operacje na katalogach:
Rozdział 23: Smak Internetu 1269

fSuccess = FtpCreateDirectory (hFtpSession, szDirectory);
fSuccess = FtpRemoveDirectory (hFtpSession, szDirectory);
fSuccess = FtpSetCurrentDirectory (hFtpSession, sz0irectory);
fSuccess = FtpGetCurrentDirectory (hFtpSession, szDirectory,
&dwCharacterCount);
Zauważ, że są one bardzo podobne do znanych funkcji CreateDirectory, Remove-
Directory, SetCurrentDirectory i GetCurrentDirectory, oferowanych przez Windows
do obsługi katalogów w lokalnym systemie plików.
Aplikacje korzystające z anonimowych serwerów FTP nie mogą oczywiście ani
tworzyć, ani usuwać katalogów. Nie mogą ponadto zakładać, że katalog FTP ma
taką samą strukturę drzewiastą, jaką mają systemy plików Windows. W szczegól-
ności program określający katalog za pomocą względnej ścieżki nie powinien czy-
nić żadnych założeń dotyczących pełnej ścieżki. Po wywołaniu SetCurrentDirectory
powinno następować wywołanie GetCurrentDirectory, aby program wiedział na
bieżąco, jaka jest pełna ścieżka do wynikowego katalogu. Znakowy argument funkcji
GetCurrentDirectory powinien mieścić przynajmniej MA7CPATH znaków, a ostatni
argument - wskazywać na zmienną zawierająca właściwą wartość.
Poniższe dwie funkcje umożliwiają usuwanie plików i modyfikowanie ich nazw
(nie w przypadku serwerów anonimowych):
fSuccess = FtpDeleteFile (hFtpSession, szFileName);
fSuccess = FtpRenameFile (hFtpSession, szOldName, szNewName);
Istnieje możliwość wyszukiwania pliku (lub plików określonych maską z symbo-
lami wieloznacznyrni). W tym celu należy najpierw wywołać funkcję FtpFindFirst-
File. Jest ona bardzo podobna do funkcji FindFirstFile i używa nawet tej samej struk-
tury WIN32 FINDDATA. Funkcja zwraca uchwyt pliku. Przekazuje się go do funk-
cji InternetFindNextFile, za pomocą której można uzyskać dalsze wystąpienia pli-
ków. Na końcu uchwyt należy zamknąć, wywołując InternetCloseHandle.
Aby otworzyć plik, należy wywołać funkcję FtpFileOpen. Zwraca ona uchwyt (pli-
ku), którego można użyć w funkcjach InternetReadFile, InternetReadFileEx, Interne-
tWrite i InternetSetFilePointer. Znów na końcu należy zamknąć uchwyt, wywołu-
jąc uniwersalną funkcję InternetCIoseHandle.
Teraz jeszcze dwie inne szczególnie przydatne funkcje wysokopoziomowe: Ftp-
GetFile umożliwia kopiowanie plików z serwera FTP do lokalnego katalogu. Jej
działanie oparte jest na funkcjach: FtpFileOpen, FileCreate, InternetReadFile, Write-
File, InternetCIoseHandle i CIoseHandle. Jednym z argumentów funkcji FtpGetFile
jest znacznik nakazujący funkcji przerwanie działania, jeżeli istnieje już plik lo-
kalny o identycznej nazwie. Podobnie działa funkcja FtpPutFile: ta służy jednak
do kopiowania pliku lokalnego do serwera FTP, czyli działa w drugą stronę.
Program demonstrujący ściąganie plików z serwera FTP
Rysunek 23-2 przedstawia program UPDDEMO (update demo), pokazujący, jak
za pomocą funkcji WinInet FTP ulokowanych w osobnym wątku można ściągać
pliki z anonimowego serwera FTP.
UPDDEMO.C
,
1270 Część III: Zagadnienia zaawansowane
/*
UPDDEMO.C - Demonstracja anonimowego FTP
(c) Charles Petzold, 1998
*/
include
include
include
4include "resource.h"
// komunikat niestandardowy używany w WndProc
define WM USER CHECKFILES (WM USER + 1)
define WM USER GETFILES (WM USER + 2)
// informacje dla operacji ściągania
4define FTPSERVER TEXT ("ftp.cpetzold.com")
4define DIRECTORY TEXT ("cpetzold.com/ProgWin/UpdDemo")
define TEMPLATE TEXT ("UD??????.TXT")
// struktury używane do pamiętania nazw plików i treści
typedef struct
{
TCHAR * szFilename ;
char * szContents ;
)
FILEINFO ;
typedef struct
(
int iNum ;
FILEINFO info[1] ;
)
FILELIST ;
// struktura używana przez drugi wątek
typedef struct
BOOL bContinue ; i
HWND hwnd ;
)
PARAMS ; ,
// deklaracje wszystkich funkcji programu
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM) ;
VOID FtpThread (PVOID) ;
VOID ButtonSwitch (HWND, HWND, TCHAR *) ;
FILELIST * GetFileList (VOID) ;
int Compare (const FILEINFO *, const FILEINFO *) ;
// kilka zmiennych globalnych
f
Rozdziat 23: Smaic tarneiu 1271
HINSTANCE hInst ;
TCHAR szAppNameC] = TEXT ("UpdDemo"> ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
(
HWND hwnd ;
MSG msg ;
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 = GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
f
c MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MBICONERROR) ;
return 0 ;
hwnd = CreateWindow (szAppName TEXT ("Update Demo with Anonymous FTP"),
WS OVERLAPPEDWINDOW WS_VSCROLL,
CW USEDEFAULT, CW USEDEFAULT,
CW USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
/1 po wyświetleniu okna sprawdzenie, czy istnieje ostatni plik
SendMessage (hwnd, WM USER CHECKFILES, 0, 0) ;
while (GetMessage (&msg, NULL, 0, 0))
(
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
1
return msg.wParam ;
)
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(
static FILELIST * plist ;
static int cxClient, cyClient, cxChar, cyChar ;
HDC hdc ;
int i ;
PAINTSTRUCT ps ;
SCROLLINFO si ;
A
f
1272 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1271)
SYSTEMTIME st ;
TCHAR szFilename [MAXPATH] ;
switch (message)
(
case WM_CREATE:
cxChar = LOWORD (GetDialogBaseUnits ()) ;
cyChar = HIWORD (GetDialogBaseUnits ()) ;
return 0 ;
case WM_SIZE:
cxClient = LOWORD (lParam) ;
cyClient = HIWORD (lParam) ;
si.cbSize = sizeof (SCROLLINFO) ;
si.fMask = SIFRANGE SIF PAGE ;
si.nMin = 0 ;
si.nMax = plist ? plist->iNum - 1 : 0 ;
si.nPage = cyClient / cyChar ;
SetScrollInfo (hwnd, SB VERT, &si, TRUE) ;
return 0 ;
case WM_VSCROLL:
si.cbSize = sizeof (SCROLLINFO) ;
si.fMask = SIF POS SIF RANGE SIFPAGE ;
GetScrollInfo (hwnd, SB VERT, &si) ;
switch (LOWORD (wParam))
(
case SB_LINEDOWN: si.nPos += 1 ; break ;
case SBLINEUP: si.nPos -= 1 ; break ;
case SBPAGEDOWN: si.nPos += si.nPage ; break ;
case SB_PAGEUP: si.nPos -= si.nPage ; break ;
case SB_THUMBPOSITION: si.nPos = HIWORD (wParam) ; break ;
default: return 0 ;
)
si.fMask = SIF_POS ;
SetScrollInfo (hwnd, SB VERT, &si, TRUE) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_USER_CHECKFILES:
// pobranie daty systemowej
GetSystemTime (&st) ;
wsprintf (szFilename, TEXT ("UD%04i%02i.TXT"), st.wYear, st.wMonth) ;
// sprawdzenie, czy plik istnieje; jeżeli tak, odczytanie wszystkich plików
if (GetFileAttributes (szFilename) != (DWORD) -1)
(
SendMessage (hwnd, WM USER GETFILES, 0, 0) ;
return 0 ;
)
// W przeciwnym razie pobranie plików z Internetu.
Rozdział 23: Smak Internetu 1273
// Ale najpierw trzeba sprawdzić, czy nie kopiujemy plików na CD-ROM!
if (GetDriveType (NULL) == DRIVECDROM)
(
MessageBox (hwnd, TEXT ("Cannot run this program from CD-ROM!"),
szAppName, MB OK ( MB ICONEXCLAMATION) ;
return 0 ;
l
// zapytaj użytkownika, czy chce się polczyć z Internetem
if (IDYES == MessageBox (hwnd,
TEXT ("Update information from Internet?"),
szAppName, MB YESNO MB ICONOUESTION))
// otwarcie okna dialogowego
DialogBox (hInst, szAppName, hwnd, DlgProc) ;
// aktualizacja
SendMessage (hwnd, WM USER GETFILES, 0, 0) ;
return 0 ;
case WM_USER GETFILES:
SetCursor (LoadCursor (NULL, IDCWAIT)) ;
ShowCursor (TRUE) ;
// odczytanie wszystkich plików
plist = GetFileList () ;
ShowCursor (FALSE) ; '
SetCursor (LoadCursor (NULL, IDC ARROW)) ;
// symulacja komunikatu WMSIZE w celu zmiany polożenia
// paska przewijania i przemalowania ekranu
SendMessage (hwnd, WM_SIZE, 0, MAKELONG (cxClient, cyClient)) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SetTextAlign (hdc, TA UPDATECP) ;
si.cbSize = sizeof (SCROLLINFO) ;
si.fMask = SIF_POS ;
GetScrollInfo (hwnd, SB VERT, &si) ;
if (plist)
for (i = 0 ; i < plist->iNum ; i++)
f
MoveToEx (hdc, cxChar, (i - si.nPos) * cyChar, NULL) ;
TextOut (hdc, 0, 0, plist->info[i].szFilename,
lstrlen (plist->info[i].szFilename)) ;
TextOut (hdc, 0, 0, TEXT (": "), 2) ;
TextOutA (hdc, 0, 0, plist->info[i].szContents,
1274 Czść M: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1273)
strlen (plist->infoCi).szContents)) ;
1
)
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostOuitMessage (0) ;
return 0 ;
1
return DefWindowProc (hwnd, message, wParam, lParam) ;
l
BOOL CALLBACK DlgProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
,
static PARAMS params ;
switch (message)
ł
case WM_INITDIALOG:
params.bContinue = TRUE ;
params.hwnd = hwnd ;
beginthread (FtpThread, 0, ¶ms) ;
return TRUE ;
case WM COMMAND:
switch (LOWORD (wParam))
case IDCANCEL: // przycisk pozwalajacy przerwać ściaganie
params.bContinue = FALSE ;
return TRUE ;
case IDOK: // przycisk zamykajacy okno
EndDialog (hwnd, 0) ;
return TRUE ;
)
)
return FALSE ;
1
/*
FtpThread: odczytuje pliki z serwera FTP i kopiuje je na dysk lokalny
*/
void FtpThread (PVOID parg)
f
BOOL bSuccess ;
HINTERNET hIntSession, hFtpSession, hFind ; !
HWND hwndStatus, hwndButton ;
PARAMS * pparams ;
TCHAR szBuffer [64 ;
WIN32FIND DATA finddata ;
pparams = parg ;
hwndStatus = GetDlgItem (pparams->hwnd, IDC STATUS) ;
Rozdział 23: Smak tntemetu 1275
hwndButton = GetDlgItem (pparams->hwnd, IDCANCEL) ;
// otwarcie sesji internetowej
hIntSession = InternetOpen (szAppName, INTERNET_OPEN TYPE_PRECONFIG,
NULL, NULL, INTERNETFLAG ASYNC) ;
if (hIntSession = NULL)
(
wsprintf (szBuffer, TEXT ("InternetOpen error %i"), GetLastError ()) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
ęndthread () ;
)
SetWindowText (hwndStatus, TEXT ("Internet session opened...")) ;
// sprawdź, czy użytkownik nacisndł Anuluj
if (!pparams->bContinue)
(
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
ęndthread () ;
1
// otwarcie sesji FTP
hFtpSession = InternetConnect (hIntSession, FTPSERVER,
INTERNET_DEFAULT_FTP_PORT,
NULL, NULL, INTERNET SERVICE_FTP, 0, 0) ;
if (hFtpSession = NULL)
t
InternetCloseHandle (hIntSession) ;
wsprintf (szBuffer, TEXT ("InternetConnect error %i"),
GetLastError ()) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
endthread () ;
)
SetWindowText (hwndStatus, TEXT ("FTP Session opened...")) ;
// sprawdź, czy użytkownik nacisnął Anuluj
if (!pparams->bContinue)
(
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
endthread () ;
f )
// przejdź do katalogu
bSuccess = FtpSetCurrentDirectory (hFtpSession, DIRECTORY) ;
if (!bSuccess)
f
InternetCloseHandle (hFtpSession) ;
1276 Część III: Zagadnienia zaawansowane
(ciąg dalszy ze strony 1275)
InternetCloseHandle (hIntSession) ;
wsprintf (szBuffer, TEXT ("Cannot set directory to %s"),
DIRECTORY) ;
ButtonSwitch (hwndStatus, hwndButton, szBuffer) ;
endthread () ;
SetWindowText (hwndStatus, TEXT ("Directory found...")) ;
// sprawdź, czy użytkownik nacisnąl Anuluj
if (!pparams->bContinue)
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hlntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
endthread () ;
I
// pobranie pierwszego pliku odpowiadającego szablonowi
hFind = FtpFindFirstFile (hFtpSession, TEMPLATE,
&finddata, 0, 0) ;
if (hFind == NULL)
(
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, TEXT ("Cannot find files")) ;
endthread () ;
)
do
f
// sprawdź, czy użytkownik nacisnął Anuluj
if (!pparams->bContinue)
(
InternetCloseHandle (hFind) ;
InternetCloseHandle (hFtpSession) ;
InternetCloseHandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, NULL) ;
endthread () ;
1
// kopiowanie pliku z Internetu na dysk lokalny, ale nie wtedy,
// gdy plik już istnieje na dysku
wsprintf (szBuffer, TEXT ("Reading file %s..."), finddata.cFileName) ;
SetWindowText (hwndStatus, szBuffer) ;
FtpGetFile (hFtpSession,
finddata.cFileName, finddata.cFileName, TRUE,
FILĘ ATTRIBUTE NORMAL, FTP TRANSFER TYPE BINARY, 0) ; ł
)
while (InternetFindNextFile (hFind, &finddata)) ;
InternetCloseHandle (hFind) ;
Rozdział 23: Smak Internetu
1277
InternetCloseHandle (hFtpSession) ;
InternetCloseNandle (hIntSession) ;
ButtonSwitch (hwndStatus, hwndButton, TEXT ("Internet Download Complete"));
1
/*
ButtonSwitch: wyświetla ostateczny komunikat o stanie i zmienia Anuluj w OK
*/
VOID ButtonSwitch (HWND hwndStatus, HWND hwndButton, TCHAR * szText)
(
if (szText)
SetWindowText (hwndStatus, szText) ;
else
SetWindowText (hwndStatus, TEXT ("Internet Session Cancelled")) ;
SetWindowText (hwndButton, TEXT ("OK")) ;
SetWindowLong (hwndButton, GWL ID, IDOK) ;
/*
GetFileList: odczytuje pliki z dysku i zapisuje ich nazwy oraz zawartości
FILELIST * GetFileList (void)
(
DWORD dwRead ;
FILELIST * plist ;
HANDLE hFile, hFind ;
int iSize, iNum
WIN32FINDDATA finddata ;
hFind = FindFirstFile (TEMPLATE, &finddata) ;
if (hFind == INVALID HANDLEVALUE)
return NULL ;
plist = NULL ;
iNum = 0 ;
do
f
// otwarcie pliku i odczytanie jego rozmiaru
hFile = CreateFile (finddata.cFileName, GENERIC_READ, FILE_SHARE READ,
NULL, OPENEXISTING. 0, NULL) ;
if (hFile - INVALID HANDLE VALUE)
continue ;
iSize = GetFileSize (hFile, NULL) ;
if (iSize = (DWORD) -1)
f
CloseHandle (hFile) ;
continue ;
)
*/
1278 C:ść l: Zagadnienia zaawansowane
(ciąg dalszy ze strony 2277)
// realokacja struktury FILELIST (miejsce na nowy element)
plist = realloc (plist, sizeof (FILELIST) + iNum * sizeof (FILEINFO));
// rezerwacja pamięci na nazwę pliku i zapamiętanie tej nazwy
plist->infoCiNum].szFilename = malloc (lstrlen (finddata.cFileName) +
sizeof (TCHAR)) ;
lstrcpy (plist->info[iNum].szFilename, finddata.cFileName) ;
// rezerwacja miejsca i zapisanie treści pliku
plist->infoCiNum].szContents = malloc (iSize + 1> ;
ReadFile (hFile, plist->infoCiNum].szContents, iSize, &dwRead, NULL);
plist->info[iNum].szContentsCiSize] = 0 ;
CloseHandle (hFile) ;
iNum ++ ;
while (FindNextFile (hFind, &finddata)) ;
FindClose (hFind) ;
// sortowanie plików wedlug nazw
qsort (plist->info, iNum, sizeof (FILEINFO), Compare) ;
plist->iNum = iNum ;
return plist ;
1
/*
Funkcja porównujdca dla qsort
*/
int Compare (const FILEINFO * pinfol, const FILEINFO * pinfo2)
return lstrcmp (pinfo2->szFilename, pinfol->szFilename) ;
1 i
UPDDEMO.RC (fragmenty)
//Microsoft Developer Studio generated resource script.
include "resource.h"
include "afxres.h"
/////////////////////////////////////////////l///////////////////////////////
// Dialog
UPDDEMO DIALOG DISCARDABLE 20, 20, 186, 95
STYLE DS MODALFRAME WSPOPUP WS CAPTION WS SYSMENU
Ro:dzial 23: Snk Interr 1279
CAPTION "Internet Download"
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "Cancel",IDCANCEL,69,74,50,14
CTEXT "",IDCSTATUS,7,29,172,21
END
RESOURCE.H (fragmenty
// Microsoft Developer Studio generated include file.
// Used by UpdDemo.rc
define IDCSTATUS 40001
Rysuk 23-2 Program UPDDEMO
4V programie UPDDEMO używane są pliki o nazwach UyYYynm.TXT, gdzie
yyyy to czterocyfrowy rok (prograrn jest oczywiście przygotowany na rok 2000),
, a mm to dwucyfrowy mżesiąe. Poczyniono tu założenie, że program aktualizuje
pliki raz na miesiąc. Możemy sobie wyobrazić, że ściąga co rniesiąc kolejne wy-
dania jakiegoś miesięcznika.
Tak więc WinMain, po wywołaniu ShowWindow i LIpdateWindow w celu wyświe-
tlerua głównego alaia programu UPDDEMO, wysyła do WndPrac komunikat nie-
standardowy WM IJSER CHECKFILES. Procedura WndPrac przetwarza go, po-
bierając bieżący rok i rniesiąc, a następnie sprawdzając, czy w domyslnym kata-
logu znajduje się plik UDYYyY'n,''XT dla tego roku i miesiąca. Jeżeli płik istnie-
je, oznacza to, że UPDDEMO jest zaktualizowanY, (Alie do końca. Może brako-
wać jakichś wcześniejszych plików. tAl tym miejscu można rozbudować algorytm
sprawdzania). tN takim wypadku program wYsyła do siebie komunikat
WMUSER GETFILES, który następnie obsh.guje, wywolując fumkcję GetFrleList.
Jest to przydługa i mało interesującafunkeja UPDDEMO.C. Odczytuee ona tYlko
wszystkie pliki UDyyYymrn.TXT do alokowanej dynamicznie struktury typu FI-
LELIST, zdefiruowarej na początku programu. Prograrn wYświetla następnie za-
wartość tych plików w swoim obszarze klienckim.
Jeżeli UPDDEMO n'te odnajdzie najbardziej aktualnego pliku, musi sięgnąć po
niego do Internetu. Program pyta najpierw użYtkownika, ezY może to zbić. Je-
żeli odpowiedź jest twierdząca, wyświetla proste ołcno dialogowe z przyciskiem
Anuluj i statycznym polem tekstowym zawierającym identyfikator z IDC_STA-
TUS. Okno będzie inforrnowało użytkownika o stanie ciągania i pozwalało mu
przerwać cały proces.
Procedura okna nazywa się tgProc i jest bardzo krótka, obejmuje bowiem irucja-
cję stnxktury PARAMS zawierającej uchwyt jej własnego olena oraz zmienną typu
BOOL o na.zwie óContinue, a także wywołanie funkeji beginthread w celu uru-
chomienia drugiego wątku.
Samym transferem zajmuje się funkcja FtpThread, wykorzYstująca funkcj,e: Fnter-
1280 Część III: Zagadnienia zaawansowane
netOpen, InternetConnect, FtpSetCurrentDirectory, FtpFindFirstFile, InternetFindNe-
xtFile, FtpGetFile i InternetCloseHandle (trzy razy). Jak bywa z większością kodu,
procedura wątku mogłaby być o wiele prostsza, gdyby nie musiała ciągle spraw-
dzać błędów, informować użytkownika o postępie prac i pozwalać mu przery-
wać działanie. Funkcja FtpThread powiadamia użytkownika o postępie prac, uży-
wając wywołań SetWindowText z uchwytem hwndStatus, odnoszącym się do sta-
tycznego pola tekstowego w środku okna.
Wątek może się zakończyć na trzy sposoby:
Po pierwsze, FtpThread może napotkać w jednej z funkcji WinInet kod powrotu
oznaczający błąd. W takim wypadku funkcja posprząta, sformatuje ciąg komuni-
katu o błędzie i przekaże go (razem z uchwytami okna dialogowego, pola teksto-
wego i przycisku Anuluj) do funkcji ButtonSwitch. ButtonSwitch to mała funkcja
wyświetlająca łańcuch znaków i zmieniająca przycisk Anuluj w przycisk OK -
nie tylko napis przycisku, lecz także identyfikator. Umożliwia to użytkownikowi
naciśnięcie przycisku OK i zamknięcie okna dialogowego.
Po drugie, FtpThread może zakończyć działanie bezbłędnie. Taki przypadek jest
obsługiwany tak samo jak przypadek, kiedy funkcja napotka błędy, tylko że
w oknie dialogowym wyświetla tekst "Internet Download Complete".
Po trzecie, ściąganie może anulować użytkownik. W takiej sytuacji procedura
DlgProc nadaje polu bContinue struktury PARAMS wartość FALSE. Funkcja Ftp-
Thread okresowo sprawdza tę wartość. Jeżeli bContinue ma wartość FALSE, funk-
cja sprząta po sobie i wywołuje ButtonSwitch z argumentem tekstowym równym
NLlLL, co oznacza, że zostanie wyświetlony ciąg "Internet Session Cancelled".
Użytkownik musi nacisnąć przycisk "OK", aby pozbyć się okna.
Choć UPDDEMO ma wyświetlać tylko po jednej lin z każdego pliku, można
program rozbudować. Za pomocą tego programu mógłbym na przykład (ja, au-
tor tej książki) informować cię (ciebie, czytelnika) o ewentualnych uaktualnieniach
treści tego podręcznika albo dostarczać ci informacji z mojej strony WWW. W ten
sposób UPDDEMO mógłby stać się sposobem rozsyłania informacji i kontynu-
owania tej książki poza stronami papierowymi...


Wyszukiwarka

Podobne podstrony:
Programowniae windows petzold Petzold01
Programowniae windows petzold Petzold05
Programowniae windows petzold Petzold08
Programowniae windows petzold Petzold09
Programowniae windows petzold Petzold13
Programowniae windows petzold Petzold02
Programowniae windows petzold Petzold21
Programowniae windows petzold Petzold22
Programowniae windows petzold Petzold14
Programowniae windows petzold Petzold04
Programowniae windows petzold Petzold20
Programowniae windows petzold Petzold03
Asembler Podstawy programowania w Windows
2 Podstawy programowania Windows (2)
Visual Studio 05 Programowanie z Windows API w jezyku C vs25pw
informatyka usb praktyczne programowanie z windows api w c andrzej daniluk ebook

więcej podobnych podstron