Rozdział 11. Aplikacje sieciowe
Adam Boduch
Czy nie zauważyłeś, drogi Czytelniku, że programy, z których korzystamy na co dzień, posiadają coraz więcej opcji umożliwiających integrację z serwerami producenta? Programy sprawdzają, czy w Internecie jest już dostępna nowa wersja , umożliwiają aktualizację modułów, a nawet ?szpiegowanie? Twojego komputera. W tym rozdziale zajmiemy się wykorzystaniem dostępnych w Delphi komponentów, aby choć trochę usprawnić działanie naszych programów.
1 Z czego będziemy korzystać?
2 Odrobinę teorii
2.1 IP
2.2 TCP
2.3 Porty
2.4 HTTP
2.5 HTTPS
2.6 FTP
2.7 SMTP
3 Biblioteka WinInet.dll
3.8 Ustanawianie połączenia
3.9 Otwieranie konkretnego adresu URL
3.10 Odczyt pliku
3.11 Pobieranie rozmiaru pliku
4 Sprawdzanie połączenia
5 Sprawdzanie IP
5.12 Zainicjowanie biblioteki
5.13 Pobieranie adresu IP
6 Łączenie przy użyciu gniazd
6.14 Czego użyć?
6.15 Łączenie pomiędzy komputerami
6.16 Wymiana danych
6.16.1 Tworzenie komend
6.17 Jak działają ?konie trojańskie??
7 Pingi
7.18 Wysyłanie sygnału ping
7.19 Odpowiedzi z serwera
8 Kontrolka TWebBrowser
8.20 Ładowanie strony
8.21 Odświeżanie
8.22 Następna i poprzednia strona
8.23 Pozostałe kody
8.23.2 Wyświetlanie adresu załadowanej strony
8.23.3 Wyświetlanie tytułu strony
8.23.4 Sprawdzenie, czy strona jest chroniona (SSL)
8.23.5 Ustawienie koloru tła strony
9 Protokół SMTP
9.24 Interfejs programu
9.25 Działanie programu
9.26 Zdarzenia komponentu
9.26.6 Kod źródłowy
10 Protokół HTTP
10.27 Łączenie z serwerem
10.28 Wymiana danych
10.28.7 Metoda POST
10.28.8 Metoda GET
10.29 Pobieranie kodu strony WWW
10.30 Wysyłanie danych przez skrypt PHP
10.30.9 Wysyłanie danych do skryptu PHP
11 Praktyczne przykłady wykorzystania HTTP
11.31 Sprawdzenie nowej wersji programu
11.31.10 Jak to działa?
11.31.11 Budowa programu
11.32 Korzystanie z zewnętrznej wyszukiwarki
11.32.12 Jak to działa?
11.32.13 Struktura kodu HTML
11.32.14 Analiza kodu HTML
11.32.15 Kod źródłowy programu
12 Protokół FTP
13 Podsumowanie
W tym rozdziale:
*omówię zasady działania najpopularniejszych protokołów sieciowych;
*dowiesz się, jak wykrywać, czy istnieje połączenie internetowe;
*napiszesz program służący do pobierania plików z Internetu;
*nauczysz się obsługiwać protokół SMTP i wysyłać e-maile;
*dowiesz się, jak ? korzystając z protokołu HTTP ? wysyłać dane do skryptów lub odczytywać zawartość strony WWW.
Z czego będziemy korzystać?
Windows posiada bibliotekę DLL o nazwie WinSock.dll, która realizuje bardzo wiele czynności związanych z dostępem do Internetu. Wykorzystanie funkcji z owej biblioteki może być trochę trudne, dlatego nie warto wyważać otwartych drzwi ? można skorzystać z komponentów oferowanych nam przez firmę Borland.
W zakładce Internet znajdują się np. takie komponenty, jak TTcpClient
i TTcpServer
, przeznaczone do łączenia się ze sobą za pomocą protokołu TCP/IP. Na tej samej zakładce znajduje się także komponent TudpSocket
do łączenia się za pomocą protokołu UDP/IP. Zrezygnowano natomiast z komponentów, do których wiele ludzi mogło się już przyzwyczaić ? TClientSocket
oraz TServerSocket
. Teraz ich funkcję mają pełnić właśnie komponenty TTcpClient
i TTcpServer
.
Do Delphi dołączony jest bardzo popularny pakiet komponentów Indy, ułatwiających wykorzystanie różnych protokołów sieciowych. Do obecnej wersji Delphi dołączono Indy 9, w efekcie czego powstało pięć nowych zakładek: Indy Clients, Indy Servers, Indy Intercepts, Indy I/O Handlers i Indy Misc. Komponenty te udostępniane są na zasadzie Open Source. Najnowszą wersję możesz znaleźć na stronie www.nevrona.com/Indy/.
Nie musimy korzystać wyłącznie z pakietu Indy ? ze strony www.overbyte.be możemy ściągnąć równie popularny pakiet ICS, stworzony przez belgijskiego programistę François Piette. Zdaniem wielu programistów pakiet ICS jest lepszy od Indy, lecz nieco trudniejszy w użytkowaniu.
Odrobinę teorii
Internet jest pełen fachowych określeń i niezbyt zrozumiałych skrótów. Dla zwykłego, ?szarego? użytkownika nie jest istotne, co oznacza i jak działa IP czy protokół TCP. Rozpoczynając pisanie aplikacji korzystających z Internetu, powinieneś być zorientowany przynajmniej w podstawach funkcjonowania protokołów internetowych. Dlatego też parę najbliższych stron poświęcimy omówieniu podstawowych zagadnień z tym związanych.
Jeżeli Cię to nie interesuje lub znasz zasadę działania protokołów internetowych, to możesz śmiało ominąć te strony.
IP
Z pojęciem adres IP zetknąłeś się pewnie nieraz. Jest to skrót od angielskiego słowa Internet Protocol. Najprościej mówiąc, adres IP jest to adres komputera w sieci. Internet to ?pajęczyna?, w której połączonych jest wiele komputerów. IP jest 32-bitową liczbą, informującą, do jakiej sieci włączony jest dany komputer. Liczba ta zapisywana jest w postaci czterech liczb, oddzielonych od siebie kropką. Przykładowy adres IP wygląda np. tak: 223.125.100.155. Adresy IP podzielone są na klasy ? patrz tabela 11.1.
Tabela 11.1. Klasy IP
Klasa | Najmniejszy adres | Największy adres |
---|---|---|
A | 0.1.0.0 | 126.0.0.0 |
B | 128.0.0.0 | 191.255.0.0 |
C | 192.0.1.0 | 223.255.255.0 |
D | 224.0.0.0 | 239.255.255.255 |
E | 240.0.0.0 | 247.255.255.255 |
Tabela 11.1 nie zawiera jednak m.in. adresów IP od 127.0.0.0 do 127.255.255.255. Powód jest prosty ? są to adresy lokalne, które nie istnieją w Internecie.
W celu zapewnienia zgodności adresy są przydzielane przez jedną organizację o nazwie Internet Network Information Center. Organizacja przydziela adresy wielkim sieciom, które z kolei rozdzielają przyznane adresy między swoich użytkowników.
Ilość komputerów w sieci stale rośnie. Aby każdemu z nich zagwarantować stały, niepowtarzalny numer, trzeba było rozbudować protokół IP ? nowa wersja nosi nazwę IPv6. Wykorzystywane w niej są liczby 128-bitowe, a nie ? jak dotychczas ? 32-bitowe.
TCP
Protokół TCP (ang. Transmission Control Protocol) jest drugą co do ważności usługą w sieci (po IP). Polega ona na przesyłaniu danych (pakietów) pomiędzy dwoma komputerami. Połączenie z dwoma komputerami odbywa się poprzez podanie adresu IP. Wtedy jeden z komputerów na podstawie adresu IP oraz portu (o portach czytaj dalej) łączy się z drugim komputerem. Protokół TCP jest często nazywany niezawodną usługą przesyłania danych ? dlatego, że po wysłaniu pakietu komputer czeka na otrzymanie potwierdzenia, tzw. ACK (patrz rysunek 11.1).
Rysunek 11.1. Zasada wysyłania pakietów TCP/IP
Jeżeli komputer nie otrzyma potwierdzenia ACK, czeka określoną ilość czasu, po czym uznaje, iż pakiet nie dotarł ? następuje przesyłanie po raz kolejny.
Na TCP opiera się wiele innych protokołów ? np. FTP czy SMTP. Także sieci lokalne LAN korzystają z tego protokołu.
Porty
Komputer, który próbuje się połączyć z innym komputerem, to klient. Natomiast komputer po drugiej stronie, który oczekuje na przyłączenie klienta, jest nazywany serwerem. Jednak na jednym komputerze może być uruchomionych wiele aplikacji ? skąd mamy wiedzieć, do której klient chce się połączyć? Do tego służą porty, które stanowią swoiste ?furtki?. Jest to liczba z zakresu od 1 do 65 000; gdy chcemy się połączyć z innym komputerem, wraz z adresem IP musimy podać numer portu. Tak więc port aplikacji klienta oraz aplikacji serwera musi być taki sam, aby doszło do połączenia.
HTTP
Niezwykle popularny protokół HTTP (Hyper-Text Transfer Protocol) służy do transmisji stron internetowych WWW z serwera do przeglądarki. Na podstawie wpisanego w przeglądarce adresu następuje lokalizacja adresu IP serwera (adres IP jest ustalany na podstawie domeny) ? stamtąd przesyłane są informacje do przeglądarki użytkownika. Możliwe jest zwrócenie przez serwer komunikatu o błędzie ? np. błąd o numerze 404 to brak strony, którą próbujemy załadować. W tabeli 11.2 przedstawiłem najczęstsze numery błędów.
Tabela 11.2. Numery błędów HTTP
Numer błędu Opis
200 OK
206 Częściowa zawartość
301 Przeniesiono na stałe
302 Przeniesiono tymczasowo
304 Niezmodyfikowany
400 Błędne żądanie
403 Zakazane (brak dostępu)
404 Brak strony
405 Nieuznawania metoda
412 Warunki niespełnione
500 Błąd serwera
HTTPS
Protokół HTTP może nie zapewniać wystarczającego poziomu bezpieczeństwa dla szczególnie ważnych operacji internetowych ? np. zakupów, operowania pieniędzmi w bankach itp. HTTPS jest rozbudowaną wersją protokołu HTTP, umożliwiającą szyfrowanie danych przepływających z komputera klienta do serwera WWW i odwrotnie. Takie szyfrowanie nosi nazwę SSL (Secure Socket Layer). To, czy strona jest szyfrowana, czy też nie, określa adres internetowy ? adresy stron wykorzystujących SSL zaczynają się od https://.
FTP
FTP to skrót od angielskiego słowa File Transfer Protocol. Jest to niezwykle stary protokół, obecny praktycznie od początku istnienia Internetu. Polega on na połączeniu dwóch komputerów na zasadzie klient-serwer. Połączenie zazwyczaj odbywa się poprzez port 21. (popularne usługi, np. FTP, zawsze mają taki sam port, aby wystarczyło znać tylko sam adres). Tak więc klient łączy się z danym komputerem w wyniku podania jego adresu IP. Po połączeniu może odbywać się dwustronna komunikacja na zasadzie przesyłania plików.
SMTP
Jeżeli korzystasz z Internetu, to pewnie nieraz słyszałeś o tym protokole. Służy on do wysyłania listów elektronicznych. Komunikacja odbywa się na zasadzie klient-serwer ? klient łączy się z określonym adresem IP przez określony port (zazwyczaj 25.). Wysyła do serwera komendę MAIL, po czym, gdy otrzyma odpowiedź, następuje transmisja listu do serwera (najpierw nagłówek, a potem treść wiadomości). Niektóre serwery wymagają tzw. autoryzacji. Zabezpieczają się w ten sposób przed wysyłaniem spamu. Przy zakładaniu konta pocztowego ? niezależnie od tego, na którym serwerze ? musimy podać nazwę użytkownika (login) oraz hasło. Podczas konfiguracji programu pocztowego należy wpisać w odpowiednie pola obie te wartości. Podczas połączenia te dane przesyłane są do serwera; serwer sprawdza, czy są prawdziwe (tj. czy taka skrzynka e-mail jest rzeczywiście założona na tym serwerze) i, jeżeli wszystko się powiedzie, odbywa się transmisja.
Biblioteka WinInet.dll
Na samym początku tego rozdziału zaczniemy od korzystania z biblioteki WinInet.dll, a nie z komponentów. To może być trudny początek, gdyż korzystanie z biblioteki WinInet.dll wcale nie jest takie proste. To dlatego tak popularne są pakiety komponentów w stylu Indy, gdyż ułatwiają znacznie pracę z programem. Chciałbym jednak pokazać, na czym opiera się tworzenie programów z użyciem tej biblioteki, gdyż ogólnie dostępne informacje na ten temat są raczej skąpe. Przy okazji czytania tego rozdziału napiszemy program umożliwiający pobieranie plików z Internetu (rysunek 11.2).
Rysunek 11.2. Program pobierający plik z Internetu
Do skorzystania z funkcji biblioteki WinInet.dll będzie Ci potrzebny moduł WinInet.pas
? dodaj jego nazwę do listy modułów uses.
Ustanawianie połączenia
W celu zainicjowania biblioteki Wininet.dll będziemy musieli skorzystać z funkcji InternetOpen
:
function InternetOpen(lpszAgent: PChar; dwAccessType: DWORD;
lpszProxy, lpszProxyBypass: PChar; dwFlags: DWORD): HINTERNET; stdcall;
Znaczenie poszczególnych parametrów jest następujące:
*lpszAgent
? nazwa programu (przeglądarki), jaka będzie identyfikowana przez serwer.
*dwAccessType
? dostęp do sieci Internet ? patrz tabela 11.3.
*lpszProxy
? serwer pośredniczący (proxy), z którym będziemy się łączyli.
*lpszProxyBypass
? lista adresów IP, które z jakiegoś powodu nie powinny mieć dostępu do serwera proxy.
*dwFlags
? dodatkowe flagi, używane wraz z połączeniem.
Tabela 11.3. Tryby dostępu do sieci Internet
Tryb | Opis |
---|---|
INTERNET_OPEN_TYPE_DIRECT | Połączenie lokalne |
INTERNET_OPEN_TYPE_PRECONFIG | Typ połączenia oraz ewentualny adres serwera proxy jest pobierany z rejestru |
INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY | Połączenie jest odczytywane z plików systemowych Windows |
INTERNET_OPEN_TYPE_PROXY | Połączenie poprzez serwer proxy |
W naszym przypadku inicjowanie biblioteki może wyglądać w sposób następujący:
hSession := InternetOpen('Fast Download', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
Funkcja InternetOpen
zwraca uchwyt sesji w postaci zmiennej hInternet
? ów uchwyt będzie nam potrzebny w kolejnych operacjach. W ostatnim parametrze możesz dodać także flagę INTERNET_FLAG_FROM_CACHE
, dzięki której plik już raz pobrany będzie odczytywany z pamięci podręcznej ? tzw. cache?u.
Każda zmienna typu hInternet
musi zostać zwolniona po zakończeniu korzystania. Realizuje to funkcja InternetCloseHandle
.
Otwieranie konkretnego adresu URL
Do odczytania konkretnego pliku będziemy musieli skorzystać z funkcji InternetOpenURL
:
function InternetOpenUrl(hInet: HINTERNET; lpszUrl: PChar;
lpszHeaders: PChar; dwHeadersLength: DWORD; dwFlags: DWORD;
dwContext: DWORD): HINTERNET; stdcall;
W pierwszym parametrze będziemy musieli podać uchwyt uzyskany dzięki funkcji InternetOpen
. Kolejny parametr określa adres strony, którą próbujemy otworzyć. Kolejne dwa parametry są związane z nagłówkami, które możemy wysłać. W naszym przykładzie nie będziemy wysyłać żadnych nagłówków, więc pozostawimy te wartości puste (nil
i 0). Kolejny parametr może określać dodatkowe parametry połączenia ? np. połączenie za pomocą trybu bezpiecznego (HTTPS) czy np. autoryzację. Przykład wywołania tej funkcji:
hURL := InternetOpenURL(hSession, PChar(URL), nil, 0, 0, 0);
Funkcja InternetOpenURL
także zwraca uchwyt w postaci zmiennej hInternet
, która będzie nam potrzebna w czasie pobierania pliku.
Odczyt pliku
Wreszcie nadchodzi kolej na odczytanie pliku, tj. pobranie jego części i zapisanie na dysku. W tym celu skorzystamy z funkcji InternetReadFile
:
function InternetReadFile(hFile: HINTERNET; lpBuffer: Pointer;
dwNumberOfBytesToRead: DWORD; var lpdwNumberOfBytesRead: DWORD): BOOL; stdcall;
Pobieranie pliku będzie następować partiami, po 1 kB każda. Pierwszy parametr musi zawierać wskazanie (uchwyt) zmiennej typu hInternet
. Wartość tę uzyskaliśmy w czasie wywoływania funkcji InternetOpenURL
. Kolejne dwa parametry są związane z buforem, który będzie przechowywać pobrane fragmenty pliku. Parametr lpBuffer
musi zawierać wskazanie bufora, a dwNumberOfBytesToRead
rozmiar owego bufora (ilość danych, które mają zostać pobrane). Ostatni parametr przekazuje ilość rzeczywiście odczytanych bajtów. Pobieranie pliku z Internetu może wyglądać następująco:
AssignFile(F, FileName);
try
TotalRead := 0; // inicjowanie zerem. Jeżeli tego nie zrobimy zmienna będzie zawierać przypadkową liczbę, co będzie powodem błędnego obliczania postępu.
Rewrite(F, 1);
repeat
if Broken then Break;
{ pobieranie kolejnych fragmentów pliku }
InternetReadFile(hURL, @Buffer, SizeOf(Buffer), dwRead);
BlockWrite(F, Buffer, dwRead); // zapisanie buforu w pliku
TotalRead := TotalRead + dwRead;
Application.ProcessMessages;
{ wyświetlenie postępu }
lblProgress.Caption := 'Ściągam ' + IntToStr(TotalRead div 1024) + ' kB z ' + IntToStr(dwSize div 1024) + ' kB';
ProgressBar.Position := TotalRead div 1024;
until dwRead = 0;
finally
CloseFile(F);
end;
Część z tych instrukcji na pewno jest Ci znana, chociażby z poprzednich rozdziałów dotyczących obsługi plików. W skrócie można powiedzieć, że w powyższym kodzie w pętli następuje cykliczne odczytywanie fragmentów pliku i przypisywanie ich zmiennej Buffor. Następnie zawartość tej zmiennej zostaje wstawiona do pliku. Zakończenie operacji sygnalizuje zmienna dwSize, która przybiera wartość 0 w przypadku, gdy pobieranie zostało zakończone.
Pobieranie rozmiaru pliku
W gruncie rzeczy z fragmentów kodów, które przedstawiłem wyżej, można by było skonstruować program do ściągania plików. Żeby wszystko wyglądało lepiej, a program był bardziej użyteczny, będziemy musieli także dowiedzieć się, jaki jest rozmiar pliku, który próbujemy pobrać. Pozwoli to nam ukazać na komponencie TProgressBar
postęp w ściąganiu pliku. Skorzystamy z funkcji HTTPQueryInfo
, która umożliwia uzyskanie wielu informacji na temat pliku (nazwa serwera, rozmiar, rodzaj pliku itp.):
function HttpQueryInfo(hRequest: HINTERNET; dwInfoLevel: DWORD;
lpvBuffer: Pointer; var lpdwBufferLength: DWORD;
var lpdwReserved: DWORD): BOOL; stdcall;
W pierwszym parametrze musi się znaleźć uchwyt uzyskany w wyniku działania funkcji InternetOpenURL
. Drugi parametr musi zawierać flagę informującą o typie informacji, jaką chcemy uzyskać. My w naszym programie musimy wpisać tutaj wartość: HTTP_QUERY_CONTENT_LENGTH
. Uzyskana informacja jest zwracana w formie wskaźnika:
dwBufLen := 1024;
dwIndex := 0;
GetMem(pBuf, dwBufLen);
{ pobranie informacji na temat wielkości pliku }
HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH,
pBuf, dwBufLen, dwIndex);
dwSize := StrToInt(StrPas(pBuf));
ProgressBar.Max := (dwSize div 1024);
FreeMem(pBuf, dwBufLen);
Ważne jest to, że parametry podawane w funkcji HTTPQueryInfo
muszą być zmiennymi. Dlatego też zadeklarowałem zmienne dwIndex i dwBufLen. Na początku należało przeznaczyć 1 kB pamięci na poczet wskaźnika, który będzie zawierał informację o rozmiarze pliku. Po wykonaniu funkcji zmienna pBuf zawierała informacje, które są nam potrzebne ? nie pozostało nic innego, jak przedstawić je w formie liczby (wartość Integer) i zwolnić pamięć.
W listingu 11.1 znajduje się cały kod źródłowy programu.
Listing 11.1. Pobieranie pliku za pomocą biblioteki Wininet.dll
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TMainForm = class(TForm)
ProgressBar: TProgressBar;
btnDownload: TButton;
GroupBox1: TGroupBox;
Label1: TLabel;
Label2: TLabel;
edtURL: TEdit;
edtFile: TEdit;
lblProgress: TLabel;
procedure btnDownloadClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
Broken : Boolean;
procedure Download(const URL : String; FileName : String);
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
{ TMainForm }
uses WinInet;
procedure TMainForm.Download(const URL: String; FileName: String);
var
Buffer : array[1..1024] of Byte; // bufor zawierający pobrany fragment pliku
hSession, hURL : HINTERNET;
dwRead : DWORD; // ilość odczytanych danych
dwSize : DWORD; // rozmiar pliku
F : File;
pBuf : Pointer;
dwBufLen : DWORD;
dwIndex : DWORD;
TotalRead : Integer;
begin
{ otwieranie połączenia }
hSession := InternetOpen('Fast Download',
INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
Application.ProcessMessages;
lblProgress.Caption := 'Łączenie z serwerem...';
btnDownload.Enabled := False;
try
{ otwarcie podanego adresu URL }
hURL := InternetOpenURL(hSession, PChar(URL), nil, 0, 0, 0);
Application.ProcessMessages;
lblProgress.Caption := 'Czekanie na odpowiedź...';
dwBufLen := 1024;
dwIndex := 0;
GetMem(pBuf, dwBufLen);
{ pobranie informacji na temat wielkości pliku }
HttpQueryInfo(hURL, HTTP_QUERY_CONTENT_LENGTH,
pBuf, dwBufLen, dwIndex);
dwSize := StrToInt(StrPas(pBuf));
ProgressBar.Max := (dwSize div 1024);
FreeMem(pBuf, dwBufLen);
try
AssignFile(F, FileName);
try
TotalRead := 0; // inicjowanie zerem. Jeżeli tego nie zrobimy zmienna będzie zawierać przypadkową liczbę, co będzie powodem błędnego obliczania postępu.
Rewrite(F, 1);
repeat
if Broken then Break;
{ pobieranie kolejnych fragmentów pliku }
InternetReadFile(hURL, @Buffer, SizeOf(Buffer), dwRead);
BlockWrite(F, Buffer, dwRead); // zapisanie buforu w pliku
TotalRead := TotalRead + dwRead;
Application.ProcessMessages;
{ wyświetlenie postępu }
lblProgress.Caption := 'Ściągam ' + IntToStr(TotalRead div 1024) + ' kB z ' + IntToStr(dwSize div 1024) + ' kB';
ProgressBar.Position := TotalRead div 1024;
until dwRead = 0;
finally
CloseFile(F);
end;
finally
InternetCloseHandle(hSession);
end;
finally
InternetCloseHandle(hURL);
btnDownload.Enabled := False;
end;
lblProgress.Caption := 'Pobrano';
end;
procedure TMainForm.btnDownloadClick(Sender: TObject);
begin
Download(edtURL.Text, edtFile.Text);
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Broken := True;
end;
end.
Być może cały kod wygląda nieco odstraszająco, lecz po opanowaniu podstawowych funkcji modułu WinInet nie powinieneś mieć problemów z jego odczytaniem.
Równie dobrze do realizacji tego zadania mógłbyś skorzystać z gotowego komponentu, których jest wiele w Internecie. Czy nie lepiej jednak samemu podjąć takie wyzwanie? Moim zdaniem jest to dobry sposób nauczenia się, jak przebiega cały ten proces związany z pobraniem pliku.
Więcej informacji na temat budowy funkcji przedstawionych w tym podpunkcie możesz znaleźć na stronie http://msdn.microsoft.com.
Sprawdzanie połączenia
?Jak sprawdzić, czy komputer jest w danym momencie podłączony do sieci??. To pytanie pojawia się bardzo często. Wiele jest rozwiązań tego problemu. Można np. sprawdzać IP (patrz punkt ?Sprawdzanie IP?) i ? jeżeli nie równa się ono 127.0.0.1 lub 0.0.0.0 ? można stwierdzić, że istnieje połączenie z Internetem. Można także spróbować połączenia z jakimś serwerem ? jeżeli ta próba się powiedzie, oznacza to, że komputer jest podłączony do sieci. Takie rozwiązanie jest jednak zawodne, gdyż serwer może w danym momencie nie działać (awarie się zdarzają), a to wcale nie musi oznaczać, że komputer użytkownika nie jest podłączony do Internetu.
Mówiąc o próbie połączenia z danym serwerem miałem na myśli wysyłanie do niego pakietów ICMP za pomocą programu ping ? możesz o tym przeczytać w dalszej części tego rozdziału.
Ja natomiast chciałem Ci przedstawić rozwiązanie z użyciem biblioteki WinInet.dll i funkcji InternetGetConnectedState
. Sprawdzanie, czy komputer jest połączony z Internetem, może wyglądać w ten sposób:
uses WinInet;
procedure TMainForm.btnCheckClick(Sender: TObject);
var
dwConnection : DWORD;
begin
{ flagi }
dwConnection := INTERNET_CONNECTION_MODEM + INTERNET_CONNECTION_LAN +
INTERNET_CONNECTION_PROXY;
{ sprawdź, czy jest połączenie }
if not InternetGetConnectedState(@dwConnection, 0) then
lblResult.Caption := 'Brak połączenia'
else lblResult.Caption := 'Jest połączenie';
end;
Zmienna dwConnection zawiera flagi informujące o typie połączenia, jakie ma być uwzględniane przy wywołaniu funkcji InternetGetConnectedState
. Kompletny kod źródłowy tego programu możesz znaleźć na dołączonej do książki płycie CD-ROM w katalogu ../listingi/11/GetINetState/GetINet.dpr.
Sprawdzanie IP
Chcąc dowiedzieć się o obecnym adresie IP komputera, na którym jest uruchomiony program, możesz oczywiście skorzystać z gotowych komponentów. Ja pokażę, jak zrobić to w sposób ?programowy?, korzystając z biblioteki Windows: WinSock.dll. Na samym początku dodaj więc do listy uses
moduł WinSock
.
Ogólnie rzecz biorąc, do pobierania adresu IP służy funkcja iNet_ntoa
, w której jako parametr należy podać wskaźnik struktury InAddr
.
Zainicjowanie biblioteki
Zanim skorzystamy z biblioteki WinSock.dll, należy ją zainicjować. Służy do tego polecenie WSAStartup
. Po zakończeniu korzystania z biblioteki należy ją zwolnić ? poleceniem WSACleanup
. Inicjowanie i zakończenie może wyglądać tak:
procedure TMainForm.GetIPAndName(var IPAddress, ACompName: PCHar);
var
VER : WORD;
Data : TWSAData;
begin
// Ładujemy bibliotekę Winsock
VER := MAKEWORD(1, 0);
WSAStartup(VER, Data);
try
finally
WSACleanup; // zwolnij bibliotekę Winsock
end;
end;
Parametry podawane funkcji WSAStartup
muszą być przekazywane poprzez zmienne, czyli uprzednio należy zadeklarować zmienną typu WORD
oraz TWSAData
. Pierwszy parametr funkcji WSAStartup
musi zawierać numer wersji biblioteki WinSock.dll ? utworzony przy użyciu MAKEWORD
(funkcja na podstawie dwóch liczb tworzy liczbę typu Word
? działa odwrotnie niż funkcje HiWord
oraz LoWord
, przedstawione w rozdziale 5.).
Pobieranie adresu IP
Cała procedura pobrania nazwy zalogowanego użytkownika oraz adresu IP przedstawiona jest poniżej:
procedure TMainForm.GetIPAndName(var IPAddress, ACompName: PCHar);
var
Host : PHostEnt;
CompName : array[0..MAX_PATH] of char;
IP : PChar; // adres IP komputera
VER : WORD;
Data : TWSAData;
begin
// Ładujemy bibliotekę Winsock
VER := MAKEWORD(1, 0);
WSAStartup(VER, Data);
try
// Pobieramy nazwę komputera i przypisujemy ją zmiennej "CompName"
GetHostName(@CompName, MAX_PATH);
Host := GetHostByName(@CompName); // uzyskanie nazwy użytkownika
ACompName := Host^.h_name;// przypisanie zmiennej "ACompName" nazwy użytkownika
// Pobieramy jego adres IP ( użyte tu zostało rzutowanie )
IP := iNet_ntoa(PInAddr(Host^.h_addr_list^)^);
IPAddress := IP; // przypisanie zmiennej "IPAddress" nazwy IP
finally
WSACleanup; // zwolnij bibliotekę Winsock
end;
end;
Funkcja eksportuje dwie zmienne: IPAddress oraz ACompName. Pierwsza z nich zawierać będzie adres IP aktualnego połączenia, a druga ? w przypadku połączenia modemowego np. z siecią TPSA ? nazwę użytkownika, czyli PPP.
Pierwsze funkcje ? GetHostName
i GetHostByName
? mają na celu pobranie nazwy sieciowej lokalnego komputera, która od tej pory jest zawarta w strukturze PHostEnt
. Samo pobranie adresu IP następuje za sprawą iNet_ntoa
.
W kodzie umieściłem taką wartość: MAX_PATH
. W rzeczywistości jest to stała, oznaczająca maksymalną wartość (długość) tablicy. Często programiści zamiast rozmiaru tablicy podają właśnie stałą MAX_PATH
, gdy nie są w stanie określić, jaka będzie wielkość tablicy w trakcie działania programu.
Pełen kod źródłowy programu możesz znaleźć na dołączonej do książki płycie CD-ROM w katalogu ../listingi/11/GetIp/GetIp.dpr.
Łączenie przy użyciu gniazd
Połączenie pomiędzy dwoma komputerami w sieci jest możliwe dzięki zastosowaniu tzw. gniazd (ang. sockets). Z punktu widzenia systemu gniazdo jest zwykłym uchwytem. Połączenie może nastąpić wówczas, gdy obydwa programy są uruchomione na różnych maszynach i działają na tym samym porcie. Jak napisałem na początku tego rozdziału, port jest ?furtką?, dzięki której oba komputery mogą przesyłać informacje.
Połączenie może być inicjowane na dwa sposoby. Pierwszy sposób to oczekiwanie na połączenie. Program jest uruchamiany, port jest otwarty i następuje oczekiwanie na połączenie z zewnątrz ? program pełni w ten sposób funkcję serwera. Druga metoda to podanie adresu IP (wraz z numerem portu), gdzie oczekuje aplikacja-serwer. Tak więc jeżeli mamy te same programy znajdujące się na dwóch różnych maszynach, to nasza aplikacja może być albo klientem, albo serwerem.
Czego użyć?
Do zrealizowania prostego zadania, jakim jest połączenie i wymiana informacji tekstowych, można użyć komponentów Indy: TidTCPClient
oraz TidTCPServer
(zakładki Indy Clients i Indy Servers). Dzięki tym dwóm kontrolkom będziemy mogli zrealizować połączenie TCP/IP między dwoma komputerami. Za pomocą tych komponentów napiszemy prostą aplikację służącą do komunikacji (rysunek 11.3).
Rysunek 11.3. Interfejs programu
Łączenie pomiędzy komputerami
Po uruchomieniu programu będzie on działał jako klient. Dopiero po zaznaczeniu opcji Oczekuj na połączenie uaktywni się jako serwer i będzie mógł przyłączyć klienta.
Natomiast klient po wpisaniu adresu IP i naciśnięciu przycisku wywoła zdarzenie OnClick:
procedure TMainForm.btnConnectClick(Sender: TObject);
begin
if Client.Connected then // w przypadku, gdy zestawione jest połączenie...
begin
Client.Disconnect; //...rozłącz z serwerem
btnConnect.Caption := 'Połącz z komputerem';
end else
begin //...w przeciwnym wypadku...
Server.Active := False; //...dezaktywuj serwer...
Client.Host := edtIP.Text; //...pobierz IP z kontrolki
Client.Connect; //...połącz
btnConnect.Caption := 'Rozłącz';
end;
end;
Przycisk btnConnect
służy zarówno do łączenia, jak i rozłączania z serwerem. Pierwsza instrukcja if
sprawdza, czy program jest połączony (a ściślej mówiąc, czy połączony jest komponent Client
) ? jeżeli tak, rozłącza go.
Wymiana danych
Oczywiście pomiędzy komponentami TidTCPClient
oraz TidTCPServer
istnieje możliwość wymiany danych tekstowych ? jest to proces podobny do działania komunikatora. My jednak zajmiemy się tworzeniem poleceń. Komponenty Indy umożliwiają tworzenie poleceń, które służą wymianie informacji. Przykładowo klient może wysłać do aplikacji-serwera polecenie HELLO
, a serwer może odpowiednio zareagować i ewentualnie wysłać odpowiedź.
Tworzenie komend
1.Zaznacz komponent TidTCPServer
i odszukaj właściwość CommandHandlers
. Zostanie wyświetlone okno Editiing Server.CommandHandlers. (rysunek 11.4).
Rysunek 11.4. Tworzenie nowego polecenia serwera
2.Naciśnij przycisk Insert ? zostanie utworzony nowy rekord.
3.We właściwości Command w Inspektorze Obiektów wpisz słowo HELLO
.
4.We właściwości Name
wpisz HelloCmd
.
5.Rozwiń właściwość ReplyNormal
.
6.Właściwościom NumericCode
oraz TextCode
nadaj wartość 200.
7.Właściwości Text
nadaj wartość Witam! Polecenie przesłane prawidłowo!
Od tej pory nowym poleceniem będzie HELLO
. W wyniku odebrania owego polecenia serwer wyśle klientowi odpowiedź Witam! Polecenie przesłane prawidłowo! Właściwości NumericCode
oraz TextCode
mogą zawierać numer odpowiedzi zwracanej przez serwer.
Teraz kliknij zakładkę Events w Inspektorze Obiektów i wygeneruj zdarzenie OnCommand
. Zdarzenie to będzie występować w przypadku, gdy polecenie zostanie przez serwer odebrane. Procedura zdarzeniowa może wyglądać w sposób następujący:
procedure TMainForm.ServerHelloCmdCommand(ASender: TIdCommand);
begin
ASender.SendReply; // wyślij odpowiedź
Memo.Lines.Add('Otrzymałem polecenie "HELLO"'); // w Memo wyświetl informacje o otrzymaniu komendy
end;
W pierwszym wierszu procedury następuje wywołanie metody SendReply
? w tym momencie klientowi zostaje wysyłana odpowiedź z komunikatem oraz numerem odpowiedzi.
Wysyłanie polecenia HELLO
przez aplikację-klienta może wyglądać w ten sposób:
procedure TMainForm.CmdBoxClick(Sender: TObject);
begin
Client.SendCmd(CmdBox.Items[CmdBox.ItemIndex]); // wysłanie polecenia
{ wyświetlenie rezultatu w komponencie Memo }
Memo.Lines.Add(Client.LastCmdResult.TextCode + '?' + Client.LastCmdResult.Text.Text);
end;
Wysyłanie komendy jest realizowane przez SendCmd
. W procedurze tej należy podać nazwę komendy, która ma zostać przesłana.
Kolejny wiersz to wyświetlenie w komponencie Memo
odpowiedzi, jaką otrzymaliśmy z serwera. Wszystko to dzięki właściwości LastCmdResult
, która zwraca informacje otrzymane od serwera. Na rysunku 11.5 przedstawiony jest program w trakcie działania. Cały kod programu znajduje się w listingu 11.2.
Rysunek 11.5. Działanie programu
Listing 11.2. Kod źródłowy programu
{
Copyright (c) 2002 by Adam Boduch <adam@4programmers.net>
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Sockets, StdCtrls, IdTCPConnection, IdTCPClient,
IdBaseComponent, IdComponent, IdTCPServer, IdIOHandler, IdIOHandlerSocket;
type
TMainForm = class(TForm)
btnConnect: TButton;
edtIP: TEdit;
lblIP: TLabel;
GroupBox1: TGroupBox;
Memo: TMemo;
ServerChecked: TCheckBox;
Client: TIdTCPClient;
Server: TIdTCPServer;
CmdBox: TListBox;
procedure ServerConnect(AThread: TIdPeerThread);
procedure ServerDisconnect(AThread: TIdPeerThread);
procedure btnConnectClick(Sender: TObject);
procedure ServerCheckedClick(Sender: TObject);
procedure ClientConnect(Sender: TObject);
procedure ClientDisconnect(Sender: TObject);
procedure ServerHelloCmdCommand(ASender: TIdCommand);
procedure CmdBoxClick(Sender: TObject);
private
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.ServerConnect(AThread: TIdPeerThread);
begin
Memo.Lines.Add('Klient połączony...');
end;
procedure TMainForm.ServerDisconnect(AThread: TIdPeerThread);
begin
Memo.Lines.Add('Klient rozłączony...');
end;
procedure TMainForm.btnConnectClick(Sender: TObject);
begin
if Client.Connected then // w przypadku, gdy zawarte jest połączenie...
begin
Client.Disconnect; //...rozłącz z serwerem
btnConnect.Caption := 'Połącz z komputerem';
end else
begin //...w przeciwnym wypadku...
Server.Active := False; //...dezaktywuj serwer...
Client.Host := edtIP.Text; //...pobierz IP z kontrolki
Client.Connect; //...połącz
btnConnect.Caption := 'Rozłącz';
end;
end;
procedure TMainForm.ServerCheckedClick(Sender: TObject);
begin
// aktywuj serwer w razie, gdy pozycja jest zaznaczona
Server.Active := ServerChecked.Checked;
end;
procedure TMainForm.ClientConnect(Sender: TObject);
begin
Memo.Lines.Add('Połączony...'); // informacja w komponencie Memo
end;
procedure TMainForm.ClientDisconnect(Sender: TObject);
begin
Memo.Lines.Add('Rozłączony...'); // informacja w komponencie Memo
end;
procedure TMainForm.ServerHelloCmdCommand(ASender: TIdCommand);
begin
ASender.SendReply; // wyślij odpowiedź
Memo.Lines.Add('Otrzymałem polecenie "HELLO"'); // w Memo wyświetl informacje o otrzymaniu polecenia
end;
procedure TMainForm.CmdBoxClick(Sender: TObject);
begin
Client.SendCmd(CmdBox.Items[CmdBox.ItemIndex]); // wysłanie polecenia
{ wyświetlenie rezultatu w komponencie Memo }
Memo.Lines.Add(Client.LastCmdResult.TextCode + '?' + Client.LastCmdResult.Text.Text);
end;
end.
Jak działają ?konie trojańskie??
?Konie trojańskie?, inaczej zwane backdoors, działają na tej samej zasadzie. Nie zamierzam tutaj podawać przepisu na stworzenie ?konia trojańskiego?, ale prawdą jest, że programy te komunikują się za pomocą protokołu TCP/IP.
Aplikacja-serwer jest zazwyczaj dołączana do innego programu, po czym zagnieżdża się gdzieś w systemie i jest uruchomiona przez cały czas. ?Konie trojańskie? bardzo często mają także w sobie funkcje zwaną KeySpy.
KeySpy dosłownie można przetłumaczyć na polski jako szpieg klawiszy. Jest to aplikacja, która monitoruje naciskane przez Ciebie klawisze, a następnie cały pisany za pomocą klawiatury tekst zapisuje gdzieś w pliku tekstowym. Podczas połączenia z Internetem może np. wysłać autorowi takiego programu na jego adres e-mail wszystko to, co pisałeś na swoim komputerze.
Po połączeniu z Internetem aplikacja-klient wydaje polecenia serwerowi, który je interpretuje i zgodnie z nimi dokonuje różnych ?modyfikacji? w naszym komputerze.
W powyższym przykładzie zaprezentowałem przykład wymiany danych pomiędzy aplikacjami. Taka wymiana była raczej niegroźna, lecz ?konie trojańskie? przesyłają między sobą inne komunikaty, w wyniku których projektant takiego ?konia? może otrzymać inne informacje na temat Twojego komputera ? jak np. ścieżka do katalogu Windows (patrz rysunek 11.6).
Rysunek 11.6. Program uzyskuje ścieżkę do katalogu Windows
W ten sposób autor takiego ?konia trojańskiego? uzyskuje informację o katalogu, w którym znajduje się system operacyjny. Oczywiście to był tylko przykład, gdyż inne polecenia mogą powodować inne zachowania aplikacji-serwera.
Współczesne ?konie trojańskie? potrafią przesyłać pomiędzy sobą pliki (oczywiście bez wiedzy użytkownika), sterować kursorem myszki, wpisywać na ekranie różne teksty, wysuwać szufladę napędu CD-ROM lub nawet wyłączać komputer.
Niestety wciąż wiele jest użytkowników Internetu, których pasjonuje ?włamywanie? się do czyjegoś komputera. Jak się przed tym zabezpieczyć? Najprostszą metodą jest zastosowanie tzw. firewalla (ang. ściana ogniowa), czyli programu, który nie dopuści do połączenia bezpośrednio z jakimś adresem IP bez Twojej wiedzy.
Kod programu wysyłającego ścieżkę do katalogu Windows znajduje się na płycie CD-ROM w katalogu ..listingi/11/WndCommand/25.dpr.
Pingi
Słowo ping jest związane z wysyłaniem zapytania do konkretnego komputera w sieci. Jest to sposób sprawdzenia, czy dany komputer o danym adresie IP istnieje w sieci. Wysyłanie sygnałów ping jest także sposobem na określenie czasu, jaki jest potrzebny na uzyskanie odpowiedzi. Gdy serwer otrzyma sygnał ping, natychmiast wysyła do nas odpowiedź.
Komponentem służącym do wysyłania sygnałów ping jest TIdICMPClient
z palety Indy Clients. Za jego pomocą skonstruujemy program wyglądający tak, jak na rysunku 11.7.
Rysunek 11.7. Odebrany ping
Wysyłanie sygnału ping
Wysyłanie sygnału ping z użyciem komponentu TidICMPClient
jest bardzo proste. Jedyna rzecz, jaką musimy zrobić, to przypisać adres hosta do zmiennej Host, a następnie wywołać metodę Ping
:
Ping.Host := edtIP.Text; // przypisujemy hosta
Ping.Ping; // wysyłamy sygnał ping
Wcześniej w Inspektorze Obiektów we właściwości Port
wpisałem numer 80.
Odpowiedzi z serwera
Uzyskiwanie odpowiedzi jest realizowane przez zdarzenie OnReply
komponentu TidICMPClient
.
procedure TMainForm.PingReply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
begin
// odpowiedź
btnPing.Enabled := True;
{ sprawdzamy, czy wysłanie sygnału ping nie zakończyło się niepowodzeniem }
if AReplyStatus.BytesReceived = 0 then
memReply.Lines.Add('Time Out')
else
memReply.Lines.Add(Format('%d bajtów odebranych z %s w %d ms',
[AReplyStatus.BytesReceived, AReplyStatus.FromIpAddress, AReplyStatus.MsRoundTripTime]));
end;
Odpowiedź z serwera jest dostarczana z parametrem AReplyStatus
. Wraz z tym rekordem są dostarczane różne informacje, m.in. czas, w jakim uzyskaliśmy odpowiedź, ilość odebranych bajtów czy np. adres IP. Jeżeli odebrany rekord jest wielkości 0 kB, oznacza to, iż przekroczony został czas odpowiedzi z serwera. Takie zdarzenie może wystąpić w momencie, gdy wysyłamy sygnał ping do wolnego serwera lub korzystamy z wolnego łącza. Kod źródłowy programu znajduje się w listingu 11.3.
Listing 11.3. Wysyłanie sygnału ping do serwera
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdBaseComponent, IdComponent, IdRawBase, IdRawClient,
IdIcmpClient, StdCtrls;
type
TMainForm = class(TForm)
Ping: TIdIcmpClient;
GroupBox1: TGroupBox;
Label1: TLabel;
edtIP: TEdit;
btnPing: TButton;
memReply: TMemo;
procedure btnPingClick(Sender: TObject);
procedure PingReply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.btnPingClick(Sender: TObject);
begin
btnPing.Enabled := False; // dezaktywujemy przycisk
Ping.Host := edtIP.Text; // przypisujemy hosta
Ping.Ping; // wysyłamy sygnał ping
end;
procedure TMainForm.PingReply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
begin
// odpowiedź
btnPing.Enabled := True;
{ sprawdzamy, czy wysłanie sygnału ping nie zakończyło się niepowodzeniem }
if AReplyStatus.BytesReceived = 0 then
memReply.Lines.Add('Time Out')
else
memReply.Lines.Add(Format('%d bajtów odebranych z %s w %d ms',
[AReplyStatus.BytesReceived, AReplyStatus.FromIpAddress, AReplyStatus.MsRoundTripTime]));
end;
end.
Kontrolka TWebBrowser
Niekiedy zachodzi potrzeba przedstawienia w naszym programie jakiegoś tekstu w formie strony WWW. Wówczas należy skorzystać z jakiegoś komponentu, który interpretuje kod HTML. Rozwiązanie jest blisko Ciebie ? to komponent TWebBrowser. W rzeczywistości jest to kontrolka ActiveX przeglądarki Internet Explorer, tak więc korzystając z niej, korzystasz w pewnym sensie z ?silnika? firmy Microsoft. Jeżeli użytkownik naszego programu nie będzie miał zainstalowanego programu Internet Explorer, to niestety nie będziemy mogli użyć także komponentu TWebBrowser. Nie ma jednak co narzekać ? długo szukając w Internecie, nie napotkasz drugiego tak dobrego komponentu służącego do wyświetlania stron WWW.
Kontrolki ActiveX mają zazwyczaj rozszerzenie *.ocx i są skompilowanym kodem, jakby dodatkowym komponentem. O kontrolkach ActiveX będziemy mówić w rozdziale 13. tej książki.
Na rysunku 11.8 znajduje się przykład programu wykorzystującego komponent TwebBrowser, a jego kod źródłowy możesz znaleźć w listingu 11.4
Rysunek 11.8. Program wykorzystujący komponent TWebBrowser
Ładowanie strony
Wyświetlenie strony w komponencie TWebBrowser
jest rzeczą dziecinnie prostą. Umożliwia to metoda Navigate, w której podaje się jedynie jeden parametr, jakim jest adres strony do załadowania.
procedure TMainForm.btnGoClick(Sender: TObject);
begin
{ załaduj podany URL }
WebBrowser.Navigate(edtURL.Text);
end;
Odświeżanie
Naciśnięcie przycisku Odśwież spowoduje ponowne załadowanie strony w naszym programie. Metoda realizująca to zadanie nazywa się Refresh
:
procedure TMainForm.btnRefreshClick(Sender: TObject);
begin
{ odśwież }
WebBrowser.Refresh;
end;
Następna i poprzednia strona
Przechodząc po kolei po różnych stronach WWW, nasza kontrolka rejestruje ich przebieg i zapamiętuje adresy. Nasz program jest w stanie cofnąć się do poprzedniej strony albo przejść do kolejnej. Dane w takim wypadku zostaną odczytane z dysku (strona nie zostanie przeładowana):
procedure TMainForm.btnBackClick(Sender: TObject);
begin
{ cofnij }
WebBrowser.GoBack;
end;
procedure TMainForm.btnFowardClick(Sender: TObject);
begin
{ następna strona }
WebBrowser.GoForward;
end;
Pozostałe kody
Pełen kod źródłowy programu jest przedstawiony w listingu 11.4, lecz nie wykorzystaliśmy w nim pełni możliwości komponentu TWebBrowser
.
Poniżej przedstawiam dodatkowe instrukcje, z jakich może w przyszłości skorzystasz.
Wyświetlanie adresu załadowanej strony
ShowMessage(WebBrowser.OleObject.Document.URL);
Wyświetlanie tytułu strony
ShowMessage(WebBrowser.OleObject.Document.Title);
Sprawdzenie, czy strona jest chroniona (SSL)
if WebBrowser.OleObject.Document.Location.Protocol = 'https:' then
ShowMessage('Strona chroniona');
Ustawienie koloru tła strony
WebBrowser.OleObject.Document.bgColor := '#000000';
Listing 11.4. Kod źródłowy przeglądarki
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ToolWin, ComCtrls, OleCtrls, SHDocVw, StdCtrls, Buttons, ImgList;
type
TMainForm = class(TForm)
WebBrowser: TWebBrowser;
StatusBar: TStatusBar;
CoolBar: TCoolBar;
ToolBar1: TToolBar;
edtURL: TEdit;
btnGo: TButton;
ToolBar2: TToolBar;
SpeedButton1: TSpeedButton;
ImageList1: TImageList;
btnBack: TToolButton;
btnFoward: TToolButton;
btnStop: TToolButton;
btnRefersh: TToolButton;
procedure btnGoClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
procedure btnBackClick(Sender: TObject);
procedure btnFowardClick(Sender: TObject);
procedure btnRefershClick(Sender: TObject);
procedure WebBrowserStatusTextChange(Sender: TObject;
const Text: WideString);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.btnGoClick(Sender: TObject);
begin
{ załaduj podany URL }
WebBrowser.Navigate(edtURL.Text);
end;
procedure TMainForm.btnStopClick(Sender: TObject);
begin
{ wstrzymaj ładowanie strony }
WebBrowser.Stop;
end;
procedure TMainForm.btnBackClick(Sender: TObject);
begin
{ cofnij }
WebBrowser.GoBack;
end;
procedure TMainForm.btnFowardClick(Sender: TObject);
begin
{ następna strona }
WebBrowser.GoForward;
end;
procedure TMainForm.btnRefershClick(Sender: TObject);
begin
{ odśwież }
WebBrowser.Refresh;
end;
procedure TMainForm.WebBrowserStatusTextChange(Sender: TObject;
const Text: WideString);
begin
{ wyświetl postęp w ładowaniu strony }
StatusBar.SimpleText := Text;
end;
end.
Protokół SMTP
Chcąc obsłużyć protokół SMTP, należy skorzystać z komponentu TIdSMTP
. Jeżeli chodzi o samo wysłanie listu e-mail, można to zrobić nawet przy użyciu komponentu TTcpSocket
. W takim przypadku wysyłamy do serwera odpowiednie polecenia ? jeżeli uzyskamy odpowiedź, wysyłany jest nagłówek wiadomości, a następnie treść. Jednak nie ma co wyważać otwartych drzwi ? pakiet Indy oferuje nam komponent o nazwie IdMessage, dzięki któremu w prosty sposób będziemy mogli wysłać list elektroniczny.
Interfejs programu
Program nie będzie skomplikowany. Główny człon aplikacji stanowią dwa komponenty TGroupBox
, na których znajdują się kontrolki TEdit
, służące do wpisania nazwy serwera, portu oraz np. adresu nadawcy czy treści wiadomości (rysunek 11.9).
Rysunek 11.9. Interfejs programu do wysyłania e-maili
Działanie programu
Na samym początku program odczytuje informację na temat adresu serwera, portu i nazwy użytkownika skrzynki e-mail. Niektóre serwery wymagają tzw. autoryzacji, zapobiegając w ten sposób wysyłaniu niechcianych e-mali za ich pośrednictwem. Zazwyczaj istnieje możliwość wysłania wiadomości przy wykorzystaniu dowolnego serwera obsługującego SMTP. Jednakże w niektórych przypadkach konieczne jest podanie loginu i hasła, jakie wpisaliśmy podczas zakładania skrzynki e-mail.
Kolejnym krokiem jest odczytanie informacji z drugiego komponentu TGroupBox
i przypisanie ich do TidMessage
. Kontrolki TidMessage
oraz TidSMTP
muszą działać razem ? wysyłanie e-maila wygląda w ten sposób:
SMTP.Send(Message); // wysyłanie e-maila
W parametrze metody Send
należy podać nazwę komponentu TidMessage
.
Zdarzenia komponentu
Komponent TidSMTP
(o nazwie SMTP) korzysta z trzech zdarzeń: OnConnected
, OnDisconnected
, OnStatus
. Pierwsze i drugie występuje w momencie połączenia i rozłączenia się z serwerem. Ostatnie natomiast służy do informowania użytkownika o postępie działania oraz ewentualnej reakcji.
procedure TMainForm.SMTPConnected(Sender: TObject);
begin
GroupBox1.Enabled := False;
GroupBox2.Enabled := False;
end;
procedure TMainForm.SMTPDisconnected(Sender: TObject);
begin
GroupBox1.Enabled := True;
GroupBox2.Enabled := True;
end;
procedure TMainForm.SMTPStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: String);
begin
case AStatus of
hsResolving: StatusBar.SimpleText := 'Wyszukiwanie hosta...';
hsConnecting: StatusBar.SimpleText := 'Łączenie z serwerem ' + SMTP.Host;
hsConnected: StatusBar.SimpleText := 'Połączony z serwerem';
hsDisconnecting: StatusBar.SimpleText := 'Trwa rozłączanie...';
hsDisconnected: StatusBar.SimpleText := 'Rozłączono';
end;
end;
Pierwsza dwie procedury maja na celu jedynie dezaktywację lub aktywację komponentów typu TGroupBox
, co blokuje także dostęp do pozostałych komponentów.
Natomiast ostatnie zdarzenie, OnStatus
, ma na celu poinformowanie użytkownika o obecnym stanie obiektu. Informacja jest dostarczana wraz z parametrem AStatus
, który może przybierać wartości takie, jak w tabeli 11.4.
Status | Opis |
---|---|
hsResolving | Wyszukiwanie adresu IP komputera poprzez podany adres |
hsConnecting | Trwa łączenie z uzyskanym adresem IP |
hsConnected | Trwa próba rozłączenia się z serwerem |
hsDisconnecting | Trwa próba rozłączenia się z serwerem |
hsDisconnected | Połączenie zostało zakończone |
Kod źródłowy
Cały kod źródłowy programu znajduje się w listingu 11.5.
Listing 11.5. Kod źródłowy programu do wysyłania e-maili
{
Copyright (c) 2002 by Adam Boduch <adam@4programmers.net>
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, IdMessage, IdBaseComponent, IdComponent,
IdTCPConnection, IdTCPClient, IdMessageClient, IdSMTP, StdCtrls, Buttons;
type
TMainForm = class(TForm)
SMTP: TIdSMTP;
Message: TIdMessage;
StatusBar: TStatusBar;
GroupBox1: TGroupBox;
lblFrom: TLabel;
edtFrom: TEdit;
lblTo: TLabel;
edtTo: TEdit;
lblSubject: TLabel;
edtSubject: TEdit;
lblBody: TLabel;
memBody: TMemo;
btnSend: TSpeedButton;
GroupBox2: TGroupBox;
lblHost: TLabel;
edtHost: TEdit;
lblPort: TLabel;
edtPort: TEdit;
lblLogin: TLabel;
lblPassword: TLabel;
edtLogin: TEdit;
edtPassword: TEdit;
procedure SMTPConnected(Sender: TObject);
procedure SMTPDisconnected(Sender: TObject);
procedure SMTPStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: String);
procedure btnSendClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.SMTPConnected(Sender: TObject);
begin
GroupBox1.Enabled := False;
GroupBox2.Enabled := False;
end;
procedure TMainForm.SMTPDisconnected(Sender: TObject);
begin
GroupBox1.Enabled := True;
GroupBox2.Enabled := True;
end;
procedure TMainForm.SMTPStatus(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: String);
begin
case AStatus of
hsResolving: StatusBar.SimpleText := 'Wyszukiwanie hosta...';
hsConnecting: StatusBar.SimpleText := 'Łączenie z serwerem ' + SMTP.Host;
hsConnected: StatusBar.SimpleText := 'Połączony z serwerem';
hsDisconnecting: StatusBar.SimpleText := 'Trwa rozłączanie...';
hsDisconnected: StatusBar.SimpleText := 'Rozłączono';
end;
end;
procedure TMainForm.btnSendClick(Sender: TObject);
begin
if Length(edtLogin.Text) > 0 then // Jeżeli użytkownik wpisał login...
begin
SMTP.AuthenticationType := atLogin; //...znaczy to, że serwer wymaga autoryzacji
{ przypisanie właściwości Username (użytkownik) oraz Password (hasło) }
SMTP.Username := edtLogin.Text;
SMTP.Password := edtPassword.Text;
end;
SMTP.Host := edtHost.Text; // przypisanie adresu
SMTP.Port := StrToIntDef(edtPort.Text, 25); // przypisanie IP
try
try
SMTP.Connect; // próba połączenia się z serwerem
btnSend.Enabled := False; // dezaktywacja przycisku
Message.Subject := edtSubject.Text; // temat wiadomości
Message.From.Address := edtFrom.Text; // adres nadawcy
Message.From.Text := edtFrom.Text;
Message.From.Name := edtFrom.Text;
Message.Recipients.Add;
Message.Recipients.Items[0].Address := edtTo.Text; // adres odbiorcy
Message.Recipients.Items[0].Text := edtTo.Text;
Message.Recipients.Items[0].Name := edtTo.Text;
Message.Body.Assign(memBody.Lines); // pobieranie treści wiadomości
SMTP.Send(Message); // wysyłanie e-maila
except
{ w razie wystąpienia błędu ? wyświetl komunikat }
raise Exception.Create('Błąd! Nie można połączyć się z serwerem!');
end;
finally
{ te instrukcje będą wykonywane ZAWSZE bez względu na ew. wystąpienie błędu }
btnSend.Enabled := True;
SMTP.Disconnect;
end;
end;
end.
Na samym początku procedury wysyłania wiadomości następuje sprawdzenie, czy użytkownik wpisał również login i hasło do swojego konta. Jeżeli tak, oznacza to, że serwer wymaga autoryzacji ? właściwość AuthenticationType
jest ustawiana na atLogin
. W programie skorzystałem z funkcji konwersji IntToStrDef
, która konwertuje liczbę do tekstu, ale oprócz tego posiada drugi, opcjonalny parametr ? wartość domyślną. Jeżeli konwersja nie powiedzie się, do zmiennej zostanie przypisany parametr z parametru drugiego.
Protokół HTTP
Chcąc obsłużyć protokół HTTP, możemy skorzystać z komponentu TIdHTTP
. Komponent ten znajduje się na zakładce Indy Clients. Niektóre właściwości są identyczne, jak w przypadku komponentu TIdSMTP
, ale w porównaniu z tym komponentem jest ich więcej. Dotyczy to także zdarzeń ? w komponencie TIdHTTP
jest ich więcej, a to ze względu na większą funkcjonalność protokołu HTTP.
Łączenie z serwerem
Ogólnie rzecz biorąc, połączenie się z danym serwerem HTTP polega na wpisaniu we właściwości Host komponentu TidHTTP
odpowiedniego adresu, a następnie wywołaniu metody Connect
. Ot, cała filozofia! Portem odpowiadającym usłudze HTTP jest port 80.
procedure TMainForm.btnConnectClick(Sender: TObject);
begin
HTTP.Host := edtIP.Text; // przypisanie nazwy hosta
HTTP.Connect; // połączenie
HTTP.Disconnect; // rozłączenie
end;
W pierwszym wierszu program przypisuje do właściwości Host
zawartość komponentu edtIP
. Następnie łączy się z serwerem i natychmiast rozłącza.
W przypadku udanego połączenia program wywołuje zdarzenie OnConnected
komponentu TidHTTP
, a w przypadku rozłączenia ? OnDisconnected
.
Wymiana danych
Przeglądając strony WWW, klikasz odnośniki, które prowadzą do plików umieszczonych na serwerze. Niekiedy jednak korzystanie z serwisu wymaga wypełnienia formularza, którego zawartość wędruje najczęściej do odpowiedniego skryptu PHP, ASP lub CGI. Odwiedzasz także strony tworzone dynamicznie (tzn. również przy użyciu języków skryptowych), a do adresu dołączane są jakieś parametry. Te dwa przykłady wysyłania informacji nazywane są metodami GET i POST.
Metoda POST
Załóżmy, ze przeglądasz stronę internetową. Na tej stronie znajduje się formularz, po wypełnieniu którego i naciśnięciu przycisku Wyślij informacje wędrują do autora strony. Najczęściej za taki proces odpowiada skrypt wykonywany po stronie serwera.
Skrypt to plik składający się z określonych poleceń, zapisanych w danym języku (np. PHP). Polecenia te są interpretowane przez odpowiednie oprogramowanie znajdujące się na serwerze, a rezultat jest zwracany jest postaci dokumentu HTML.
Przeglądarka przesyła dane z formularza jako nagłówek HTTP do owego skryptu. Dane te są przedstawione w takiej postaci:
nazwa_pola=wartosc_pola&nazwa_pola2=wartosc_pola2
Poszczególne elementy formularza są oddzielone od siebie znakiem &. Natomiast nazwa danego pola jest oddzielona od wartości znakiem =. W ten sposób połączone dane wędrują wraz z nagłówkiem do skryptu, który już je odpowiednio interpretuje. Istotą metody POST jest właśnie to, że dane są przekazywane w nagłówku HTTP do przeglądarki.
Metoda GET
Metoda GET różni się zasadniczo od metody POST. Tzn. dane, które są przekazywane są zbudowane tak samo ? poszczególne pola są oddzielone znakiem &. Zasadniczą jednak różnicą jest to, że te dane są dołączane do adresu strony. Czyli wygląda to mniej więcej tak:
http://www.4programmers.net/skrypt.php?pole1=wartosc1&pole2=wartosc2
Metodą GET dane można przekazywać niewielkie ilości danych. Nie zaleca się przekazywania tą metodą np. zawartości dużych pół edycyjnych itp.
Pobieranie kodu strony WWW
Aby pobrać kod HTML wybranej strony WWW, wystarczy wywołać metodę Get
. Komponent TidHTTP
zawiera kilka przeciążonych metod Get, w tym jedną, która zwraca zawartość podanej w parametrze strony.
HTML := HTTP.Get(edtURL.Text); // pobranie kodu HTML
Funkcja Get
zwraca w powyższym przypadku kod strony WWW w formie łańcucha znaków ? String
. Program w trakcie działania przedstawiony jest na rysunku 11.10.
Rysunek 11.10. Kod strony wstawiony do komponentu TMemo
Kod procedury pobierającej ową zawartość witryny wygląda mniej więcej tak:
procedure TMainForm.btnGetClick(Sender: TObject);
var
HTML : String;
begin
{ wcześniej we właściwości Host komponentu TidHTTP konieczne jest przypisanie
wartości "4programmers.net" jako nazwy hosta }
HTTP.Connect; // próba połączenia
try
try
HTML := HTTP.Get(edtURL.Text); // pobranie kodu HTML
memHTML.Lines.Text := HTML; // przypisanie treści strony do komponentu TMemo
except
raise Exception.Create('Nie można połączyć się z serwerem!');
end;
finally
HTTP.Disconnect; // rozłączenie
end;
end;
Przed połączeniem się z danym serwerem konieczne jest wpisanie we właściwości Host nazwy serwera, z którym próbujemy się łączyć. Czyli w przypadku, gdy próbujemy odczytać kod strony http://4programmers.net/programy.php, we właściwości Host
należy wpisać 4programmers.net. Istnieje jednak na to recepta ? wystarczy sama metoda Get
, aby komponent sam, na podstawie podanego adresu, połączył się z żądanym serwerem:
procedure TMainForm.btnGetClick(Sender: TObject);
var
HTML : String;
begin
HTML := HTTP.Get(edtURL.Text); // pobranie kodu HTML
memHTML.Lines.Text := HTML; // przypisanie treści strony do komponentu TMemo
end;
Wysyłanie danych przez skrypt PHP
Mam tu na myśli wysyłanie e-maila przez skrypt PHP umieszczony na serwerze. Nasz program, korzystając z metody Post, prześle do skryptu odpowiednie informacje, a ten z kolei wyśle do mnie e-maila z wiadomością wpisaną przez użytkownika. Jest to również dobry sposób na wysyłanie poczty, lecz należy pamiętać, że wysłanie jest zależne od tego, czy serwer w danym momencie działa, czy też nie. Jesteśmy jednak w stanie w pewien sposób kontrolować przesyłanie tego e-maila ? możemy wyświetlić odpowiedni tekst lub np. policzyć, ile osób korzystało z naszego skryptu.
Nie mam zamiaru wyjaśniać tutaj podstawowych pojęć związanych z PHP. Jeżeli tę książkę czyta ktoś, kto wcześniej miał styczność z tym językiem, to zrozumienie skryptu z listingu 11.6 nie będzie stanowiło dla niego problemu.
Listing 11.6. Budowa skryptu mail.php
<?
/*
Copyright (c) 2002 by Adam Boduch <adam@4programmers.net>
*/
mail("adam@4programmers.net", "Opinia dotycząca książki",
$message, "From: $from");
echo "E-mail został wysłany prawidłowo. Dziękuje! Odpowiedź uzyskasz na adres $from";
?>
Polecenie mail w PHP powoduje wysłanie skryptu z tematem Opinia dotycząca książki na adres adam@4programmers.net. To chyba powinno wystarczyć.
Wysyłanie danych do skryptu PHP
Interfejs programu przedstawiony został na rysunku 11.11. Posiada dwie kontrolki ? jedną służącą do wpisania adresu zwrotnego, a drugą do wpisania treści wiadomości.
Rysunek 11.11. Program w trakcie działania
Po naciśnięciu przycisku dane z formularza są przesyłane do do pliku http://4programmers.net/mail.php:
procedure TMainForm.btnSendMsgClick(Sender: TObject);
var
StreamIn, StreamOut : TStringStream;
begin
{ utworzenie strumieni }
HTTP.Host := '4programmers.net';
StreamIn := TStringStream.Create('');
StreamOut := TStringStream.Create('');
try
StreamIn.WriteString(Format('message=%s&from=%s', [memMsg.Text, edtFrom.Text]));
HTTP.Post('http://4programmers.net/mail.php', StreamIn, StreamOut); // wysłanie zawartości do skryptu
{ wyświetlenie odpowiedzi, jaka została zwrócona przez skrypt }
MessageBox(Handle, PChar(StreamOut.DataString), 'Wiadomość wysłana :?)', MB_ICONINFORMATION);
finally
{ zwolnienie zasobów }
StreamIn.Free;
StreamOut.Free;
end;
end;
Jak widzisz, procedura ta korzysta ze strumieni typu TStringStream
. Zawartość pierwszego z nich ? StremIn
? będzie przesyłana do skryptu, a zawartość drugiego ? StreamOut
? będzie zawierała tekst zwrócony przez ów skrypt mail.php.
Należy pamiętać o tym, że dane przesyłane do skryptu muszą mieć specyficzną budowę, którą skrypt będzie w stanie zanalizować. Przede wszystkim wszystkie parametry (tj. zwrotny adres e-mail i treść wiadomości) muszą być oddzielone znakiem &.
StreamIn.WriteString(Format('message=%s&from=%s', [memMsg.Text, edtFrom.Text]));
Dodatkowo wartość jest oddzielona od nazwy parametru znakiem =. Tutaj należy Ci się parę słów wyjaśnienia. Nazwa parametru ? np. message ? to inaczej identyfikator. Za pomocą tej nazwy skrypt PHP będzie odwoływał się do treści przesłanej mu w formularzu.
Skrypt mail.php rzeczywiście umieściłem na serwerze http://4programmers.net. Jeżeli więc kiedykolwiek chciałbyś wysłać mi opinię dotyczącą tej książki, możesz skorzystać z tego programu.
Praktyczne przykłady wykorzystania HTTP
Poniżej przedstawiam dwa inne przykłady wykorzystania protokołu HTTP oraz komponentu TidHTTP
. Wydaje mi się, że prezentują one rozwiązanie popularnych problemów, z jakimi spotyka się początkujący programista.
Sprawdzenie nowej wersji programu
Użytkując różne programy, możesz zauważyć, że duża ich ilość oferuje sprawdzenie, czy w danej chwili nie została udostępniona nowa wersja. Przedstawię tutaj rozwiązanie tego problemu. Cały kod będzie zrealizowany z użyciem komponentów dostępnych w Delphi 7.
Jak to działa?
Zasada działania będzie następująca: na swoim serwerze musisz umieścić plik tekstowy, który zawierał będzie numer wersji programu. Po uruchomieniu programu i naciśnięciu przycisku program odczyta wersję z pliku tekstowego, a następnie porówna ją ze swoim numerem wersji. Jeżeli numer z serwera będzie większy, oznaczać to będzie, że istnieje nowa wersja i należy podać do niej URL.
Budowa programu
Główny formularz programu przedstawiony został na rysunku 11.12.
Rysunek 11.12. Główny formularz programu
Dwie etykiety, które widać na rysunku, są na początku niewidoczne (ich właściwości Visible są ustawione na False) ? zostaną pokazane w przypadku znalezienia nowej wersji programu. Etykieta lblURL
ma ustawiony kolor niebieski, a właściwość Cursor
ma wartość crHandPoint
. Etykieta będzie zawierała adres nowej wersji pliku ? po jej kliknięciu otwarta zostanie przeglądarka i rozpocznie się pobieranie pliku.
Procedura sprawdzająca istnienie nowej wersji wygląda tak:
procedure TMainForm.Button1Click(Sender: TObject);
var
NewVersion : String[5];
begin
HTTP.Host := '127.0.0.1';
NewVersion := HTTP.Get(URL); // pobranie numeru aktualnej wersji
if NewVersion > Version then
begin
{ w przypadku, gdy znaleziono nową wersje }
ShowMessage('Znaleziono nową wersję programu ? ' + NewVersion);
// istnieje nowa wersja programu
Delete(NewVersion, Pos('.', NewVersion), 1); // usunie kropki z numeru wersji
Label1.Visible := True;
lblURL.Caption := 'http://127.0.0.1/' + AppName + NewVersion + '.exe';
lblURL.Visible := True;
end;
end;
Na samym początku po pobraniu numeru wersji i porównaniu jej z dotychczasową wartością możemy stwierdzić, czy istnieje uaktualnienie programu. URL do nowej wersji pliku nie może zawierać kropek, więc przed podaniem użytkownikowi adresu należy je wszystkie usunąć. Można to zrealizować za pomocą funkcji Pos
oraz Copy
. Pełen kod źródłowy programu przedstawiony został w listingu 11.7.
Listing 11.7. Kod źródłowy modułu MainFrm.pas
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdHTTP, { dodany modul ??> } ShellAPI;
type
TMainForm = class(TForm)
HTTP: TIdHTTP;
Button1: TButton;
Label1: TLabel;
lblURL: TLabel;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure lblURLClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
const
Version = '1.0';
URL = 'http://127.0.0.1/version.txt';
AppName = 'moj_program';
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
Caption := AppName + ' ? ' + Version;
end;
procedure TMainForm.Button1Click(Sender: TObject);
var
NewVersion : String[5];
begin
HTTP.Host := '127.0.0.1';
NewVersion := HTTP.Get(URL); // pobranie numeru aktualnej wersji
if NewVersion > Version then
begin
{ w przypadku, gdy znaleziono nową wersje }
ShowMessage('Znaleziono nową wersję programu ? ' + NewVersion);
// istnieje nowa wersja programu
Delete(NewVersion, Pos('.', NewVersion), 1); // usunie kropki z numeru wersji
Label1.Visible := True;
lblURL.Caption := 'http://127.0.0.1/' + AppName + NewVersion + '.exe';
lblURL.Visible := True;
end;
end;
procedure TMainForm.lblURLClick(Sender: TObject);
begin
{ pobranie nowej wersji programu }
ShellExecute(Handle, 'open', PChar(TLabel(Sender as TObject).Caption), nil, nil, SW_SHOWNORMAL);
end;
end.
Korzystanie z zewnętrznej wyszukiwarki
Tym razem napiszemy program, który będzie korzystał z wyszukiwarki serwisu 4programmers.net. Jest to serwis poświęcony programowaniu ? m.in. w Delphi. Ćwiczenie będzie polegać na pobraniu, a następnie analizie kodu HTML strony i przedstawieniu wyników wyszukiwania w naszej aplikacji.
Jak to działa?
Skorzystanie z zewnętrznej wyszukiwarki umieszczonej na jakimś serwerze wymaga najpierw zadania określonego zapytania. Zapytanie konstruujemy w formie określonego adresu strony ? np.
To zapytanie przekazujemy do skryptu search.php metodą GET. Parametr Q, ?doklejony? do adresu, zawiera słowo kluczowe do wyszukania. Jedyna trudność to interpretacja kodu źródłowego strony WWW i wyodrębnienie z niego interesujących nas elementów.
Struktura kodu HTML
Nim zabierzemy się za pisanie aplikacji, należy poznać strukturę, czyli sposób, w jaki wyszukiwarka 4programmers.net generuje kod HTML.
Wejdź na stronę www.4programmers.net. Na głównej stronie znajduje się wyszukiwarka. Wpisz w odpowiednie pole słowo do wyszukania ? np. hook. W rezultacie tego zapytania wyszukiwarka znajdzie jedną stronę odpowiadającą zadanemu pytaniu. Kliknij prawym przyciskiem w obrębie strony i wybierz pozycję Pokaż źródło. Nas interesuje jedynie ten fragment HTML:
<!??TITLE??>Jak założyć globalnego Hooka na klawiaturę?</a></b> (2002-10-02 16:54:16)
Oto kod ukazujący, jak założyć funkcję przechwytująca na klawiaturę. W interface:
var MainHook : HHOOK;
function KeyHook(Code: Integer; wParam : WPARAM; lParam : LPARAM): Longint; stdcall;
A w Implementation:
function KeyHook(Code: Integer; w...
URL:
http://www.4programmers.net/view_faq.php?id=181
Za pomocą komentarzy HTML opisane są fragmenty strony. Np.
<code class="html4strict"><!--TITLE-->Jak założyć globalnego Hooka na klawiaturę?<!--/TITLE-->
Analiza kodu HTML
function Ereg(var Body : String; Pattern : String) : TMatch;
Ereg(Body, '<!?-TITLE-->|<!--/TITLE-->');
function TMainForm.Ereg(var Body: String; Pattern: String): TMatch;
var
Offset : Integer;
Counter : Integer;
APattern : array[0..1] of String;
BeginPos, EndPos : Integer;
begin
Offset := 1; // pozycja ostatnio znalezionej wartości
Counter := 1;
{ oddzielenie dwóch elementów na podstawie znaku | }
APattern[0] := Copy(Pattern, 0, Pos('|', Pattern) ?1);
APattern[1] := Copy(Pattern, Pos('|', Pattern)+1, Length(Pattern));
while (PosEx(APattern[0], Body, Offset) > 0) do // szukamy pierwszego członu
begin
SetLength(Result, Counter); // określamy wielkość tablicy
{ określenie początkowej pozycji szukanego określenia }
BeginPos := PosEx(APattern[0], Body, Offset) + Length(APattern[0]);
{ określenie końcowej pozycji szukanego określenia }
EndPos := PosEx(APattern[1], Body, Offset);
Offset := EndPos+1; // do zmiennej przypisywana jest pozycja ostatnio znalezionej wartości
// wyciągnięcie treści pomiędzy np. znacznikami <!--TITLE--> i <!--/TITLE-->
Result[Counter-1] := Copy(Body, BeginPos, EndPos - BeginPos);
Inc(Counter); // zwiększenie licznika
end;
end;
Na potrzeby naszej funkcji Ereg należało także zadeklarować nowy typ danych:
TMatch = array of String;
Kod źródłowy programu
Listing 11.8. Kod źródłowy modułu MainFrm.pas
{
Copyright (c) 2002 by Adam Boduch <adam@4programmers.net>
}
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ComCtrls, StrUtils, IdBaseComponent,
IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, ImgList, ShellAPI,
IdAntiFreezeBase, IdAntiFreeze;
type
TMatch = array of String;
TMainForm = class(TForm)
StatusBar: TStatusBar;
GroupBox1: TGroupBox;
Label1: TLabel;
edtQ: TEdit;
btnSearch: TBitBtn;
GroupBox2: TGroupBox;
ListView: TListView;
HTTP: TIdHTTP;
ImageList1: TImageList;
IdAntiFreeze: TIdAntiFreeze;
procedure HTTPConnected(Sender: TObject);
procedure HTTPDisconnected(Sender: TObject);
procedure btnSearchClick(Sender: TObject);
procedure ListViewInfoTip(Sender: TObject; Item: TListItem;
var InfoTip: String);
procedure edtQKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure edtQClick(Sender: TObject);
private
anTitle : TMatch;
anURL : TMatch;
anBody : TMatch;
function Ereg(var Body : String; Pattern : String) : TMatch;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
{ TMainForm }
function TMainForm.Ereg(var Body: String; Pattern: String): TMatch;
var
Offset : Integer;
Counter : Integer;
APattern : array[0..1] of String;
BeginPos, EndPos : Integer;
begin
Offset := 1; // pozycja ostatnio znalezionej wartości
Counter := 1;
{ oddzielenie dwóch elementów na podstawie znaku | }
APattern[0] := Copy(Pattern, 0, Pos('|', Pattern) ?1);
APattern[1] := Copy(Pattern, Pos('|', Pattern)+1, Length(Pattern));
while (PosEx(APattern[0], Body, Offset) > 0) do // szukamy pierwszego członu
begin
SetLength(Result, Counter); // określamy wielkość tablicy
{ określenie początkowej pozycji szukanego określenia }
BeginPos := PosEx(APattern[0], Body, Offset) + Length(APattern[0]);
{ określenie końcowej pozycji szukanego określenia }
EndPos := PosEx(APattern[1], Body, Offset);
Offset := EndPos+1; // do zmiennej przypisywana jest pozycja ostatnio znalezionej wartości
// wyciągnięcie treści pomiędzy np. znakami <!--TITLE--> i <!--/TITLE-->
Result[Counter?1] := Copy(Body, BeginPos, EndPos ? BeginPos);
Inc(Counter); // zwiększenie licznika
end;
end;
procedure TMainForm.HTTPConnected(Sender: TObject);
begin
btnSearch.Enabled := False;
StatusBar.SimpleText := 'Połączono z serwerem 4programmers.net...';
end;
procedure TMainForm.HTTPDisconnected(Sender: TObject);
begin
btnSearch.Enabled := True;
StatusBar.SimpleText := 'Rozłączono z serwerem 4programmers.net...';
end;
function Convert(Src: String): String;
begin
Src := StringReplace(Src,Chr(182),Chr(156), [rfReplaceAll]);
Src := StringReplace(Src,Chr(177),Chr(185), [rfReplaceAll]);
Src := StringReplace(Src,Chr(188),Chr(159), [rfReplaceAll]);
Src := StringReplace(Src,'<span style="background?color: #C0C0C0">', '', [rfReplaceAll]);
Src := StringReplace(Src,'</span>', '', [rfReplaceAll]);
Src := StringReplace(Src,'<','<', [rfReplaceAll]);
Src := StringReplace(Src,'>','>', [rfReplaceAll]);
Result := Src;
end;
procedure TMainForm.btnSearchClick(Sender: TObject);
var
HTML : String;
I : Integer;
TitleItem : TListItem;
begin
HTTP.Host := '4programmers.net';
HTTP.Connect; // łączenie...
try
try
{ wyczyszczenie tablic }
anTitle := nil;
anURL := nil;
anBody := nil;
ListView.Clear; // wyczyszczenie Listview'a
StatusBar.SimpleText := 'Trwa wyszukiwanie...';
{ pobieranie rezultatu poszukiwań }
HTML := HTTP.Get('http://4programmers.net/search.php?Q=' + edtQ.Text);
anTitle := Ereg(HTML, '<!--TITLE-->|<!--/TITLE-->'); // wyciągnięcie tytułów
anURL := Ereg(HTML, '<!--URL-->|<!--/URL-->');
anBody := Ereg(HTML, '<!--BODY-->|<!--/BODY-->');
if High(anTitle) = ?1 then MessageBox(Handle, 'Niestety nie znaleziono żadnej strony odpowiadającej Twojemu zapytaniu!', ':?(', MB_ICONINFORMATION);
{ pętla po wszystkich znalezionych stronach }
for I := 0 to High(anTitle) do
begin
TitleItem := ListView.Items.Add; // dodanie do komponentu
TitleItem.ImageIndex := 0; // numer ikony ozdabiającej daną pozycję
TitleItem.Caption := Convert(anTitle[i]); // dodanie tytułu strony (po konwersji)
TitleItem.SubItems.Add(Convert(anBody[i])); // opis strony do drugiej kolumny
end;
except
raise;
end;
finally
HTTP.Disconnect;
end;
end;
procedure TMainForm.ListViewInfoTip(Sender: TObject; Item: TListItem;
var InfoTip: String);
begin
// dymek podpowiedzi...
InfoTip := Convert(anBody[Item.Index]);
end;
procedure TMainForm.edtQKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
// po naciśnięciu klawisza Enter rozpocznij wyszukiwanie
if Key = vk_Return then btnSearchClick(Sender);
end;
procedure TMainForm.edtQClick(Sender: TObject);
begin
// otwórz stronę internetową
ShellExecute(Handle, 'open', PChar(anURL[ListView.Selected.Index]), nil, nil, Sw_Show);
end;
end.
Rysunek 11.13. Rezultat poszukiwań
Po połączeniu się z serwerem przychodzi czas na zadanie zapytania i uzyskanie żądanej odpowiedzi:
HTML := HTTP.Get('http://4programmers.net/search.php?Q=' + edtQ.Text);
anTitle := Ereg(HTML, '<!--TITLE-->|<!--/TITLE-->');
Protokół FTP
Przykłady wykorzystania komponentów Indy możesz znaleźć na stronie
Podsumowanie
</li>
</ul>
</td>
Więcej informacji
Format: B5, stron: 1048
oprawa twarda
Zawiera CD-ROM
</td>
</tr>
</table>
Co do pinga znalazłem w sieci ciekawą funkcję (z wykorzystaniem komponentu TIdIcmpClient z pakietu Indy ) :
function TMainForm.Ping(const AHost : string) : Boolean;
var
MyIdIcmpClient : TIdIcmpClient;
begin
Result := True;
MyIdIcmpClient := TIdIcmpClient.Create(self);
MyIdIcmpClient.ReceiveTimeout := 1000;
MyIdIcmpClient.Host := AHost;
MyIdIcmpClient.PacketSize := 32;
MyIdIcmpClient.Protocol := 1;
MyIdIcmpClient.Port:=80;
try
Application.ProcessMessages;
MyIdIcmpClient.Ping;
except
Result := False;
Exit;
end;
if MyIdIcmpClient.ReplyStatus.ReplyStatusType <> rsEcho Then result := False;
MyIdIcmpClient.Free;
end;
wywołanie :
var jest_internet : Boolean;
jest_internet:=ping('www.google.com');
Funkcja zabezpiecza przed wywołaniem wyjątku błędu w sytuacji kiedy niema połączenia z internetem.
Żeby co chwilę nie "pingować" serwera w celu sprawdzenia połączenia z internetem (np t-timerem) warto jest wykorzystać komponent TIdIPAddrMon z pakietu Indy a dokładnie jego zdarzenie "OnStatusChanged". Zdarzenie to jest wywoływanie w momencie kiedy komputerowi zostaje nadany adres ip bądź ten adres jest zwalniany - w tych sytuacjach jest wywoływane to zdarzenie. I pozostaje prosta obsługa tego zdarzenia :
procedure TForm1.IdIPAddrMon1StatusChanged(ASender: TObject; AAdapter: Integer;
AOldIP, ANewIP: string);
begin
Jest_Internet:=Ping('www.google.com');
if Jest_Internet=True then label4.Caption:='Jest połączenie z INTERNETEM !!!';
if Jest_Internet=False then label4.Caption:='Brak połączenia z INTERNETEM !!!';
end;
Prubuje wysłać dane do skryptu metodą POST i mi zwraca pusty string.
ekhm nie ma czegosc takiego jak http.host w nowym idny czym to zastapic?
Nie napisaliście jak wysłać wartośc post :/
Nie napisaliście ja wysłać wartość post :/
Nie napisaliście jak wysłac wartość post :f :/
Coldpeer : ale wtemczas to nie bedzie w tablicy. Tak jak funkcja w php preg_match. Szuka do 1 znalezienia. A pokazany jest odpowiednik funkcji preg_match_all. Ale wystarczy zamiast do stringa dac do tablicu i bedzie ok.
nie wiem dlaczego ale mi sie nie chce skompilowąć żaden z tych kodów i wyskakuje błąd "Error in module unit1: Declaration of class TForm1 is missing or incorrect" -zagubienie deklaracji TForm1 albo niewłaściwe, co powinienem zrobić aby móc skompilować te przykłady ??
Co do funkcji Ereg, to nie prościej zapisać ją tak:
i zwracać string? Wywołanie wygląda wtedy oczywiście tak:
zamiast:
eh, nie wiem czemu mi nie chce dzialać przesyłanie do plików php message=%s&from=%s :| patrzyłem kilka razy i wszystko mam tak jak jest tutaj napisane.