1. Opis ogólny
Komponenty Internet Direct obsługują programowanie gniazdowe niskiego poziomu oraz większość powszechnie znanych protokołów internetowych.
Borland, w celu zastąpienia "przestarzałych" komponentów: TCPClient czy TcpServer, zaproponował stosowanie odpowiednich komponentów Indy.
Dostarcza kolekcję internetowych otwartych komponentów, które poprzednio nazywane były WinShoes (termin pochodzi od WinSock - nazwy
biblioteki gniazdowej Windows).
W palecie Delphi wyróżnić możemy następujące zakładki zawierające odpowiednie składniki INDY:
- Indy Clients
- Indy Servers
- Indy I/O Handles
- Indy Intercepts
- Indy Misc
Komponenty Indy rozpoznawane są po przedrostku "Id". Pakiet INDY zawiera ponad 100 komponentów zawierających aplikacje klient-serwer TCP/IP
dla rozmaitych komponentów, zawiera składniki związane z bezpieczeństwem oraz kodowaniem.
Internet Direct (Indy) stworzone zostało przez grupę kierowaną przez Chada Howera (jest również dostępne w Kyliksie). Najnowsze wersje
otwartych komponentów Indy znaleźć można pod adresem internetowym www.nevrona.com/indy. Są to darmowe komponenty uzupełnione wieloma
przykładami oraz plikami pomocy. Indy w wersji 8 rozpowszechniane było wraz z Delphi 6, w wersji 9 dostępne jest od Delphi 7.
W Delphi 2005 podczas instalacji możliwy jest wybór przez użytkownika instalowanej wersji Indy (9 lub 10).
|
Połączenia nieblokujące - polegają na odczytywaniu danych z gniazd oraz zapisywaniu do nich w sposób asynchroniczny, co nie powoduje
blokowania realizacji innych fragmentów kodu aplikacji sieciowej. Alternatywnym podejściem jest użycie połączenia blokującego,
przy którym aplikacja czeka, aż odczyt lub zapis zostanie zakończony i dopiero wówczas przechodzi do wykonania następnej linii kodu.
|
Komponenty Indy stosują połączenia blokujące co daje możliwość uproszczenia logiki programu. Operacje gniazdowe wykonywane z poziomu Indy,
powinny być wykonywane za pomocą wątku albo komponentu IdAntiFreeze stanowiącego prostszą alternatywę. Serwery Indy wykorzystują architekturę
wielowątkową którą sterować można za pomącą komponentów IdThreadMgrDefault oraz IdThreadMgrPool. Komponent IdThreadMgrDefault jest domyślnym
komponentem, drugi obsługuje odpytywanie (ang. pooling) wątków.
1.1 Gniazda
Sercem Internetu jest Transmision Control Protokol/Internet Protokol (TCP/IP). Jest to zestaw dwóch oddzielnych protokołów zapewniających
połączenie przez prywatną sieć intranetową czy internet.
|
Protokół TCP odpowiada za usługi transportowe wysokiego poziomu. Jest on zorientowany połączeniowo, czyli umożliwia zestawienie połączenia
w którym efektywnie i niezawodnie przesyłane są dane. Połączenie to charakteryzuje się możliwością sterowania przepływem, potwierdzania odbioru,
zachowania kolejności danych, kontroli błędów i przeprowadzania retransmisji. TCP organizuje również dwukierunkową współpracę między warstwą IP,
a warstwami wyższymi, uwzględniając przy tym wszystkie aspekty priorytetów i bezpieczeństwa. Musi prawidłowo obsłużyć niespodziewane
zakończenie aplikacji, do której właśnie wędruje datagram, musi również bezpiecznie izolować warstwy wyższe - w szczególności aplikacje
użytkownika - od skutków awarii w warstwie protokołu IP. Protokół IP odpowiada za: definiowanie oraz wybór marszruty datagramów
(czyli porcji danych przesyłanych w sieci) oraz określanie schematu adresowania.
|
Każde połączenie odbywa się przez port. Port jest reprezentowany przez 16 bitową liczbę. Adres IP jest 32 bitową liczbą składającą się z czterech
składników zwanych oktetami oddzielonych kropkami (np.: 127.0.0.1 jest adresem localhost’u i można go wykorzystywać przy testowaniu działania
komponentów klient-serwer przy braku połączenia z internetem).
|
Adresy IP wraz z portami TCP określają połączenia internetowe lub gniazda. Różne aplikacje działające na tym samym zestawie komputerowym
nie mogą używać tego samego gniazda (czyli tego samego portu).
|
Niektóre porty TCP zostały zarezerwowane dla określonych protokołów i usług wysokiego poziomu.
| Protokół | Port | |
| HTTP (Hypertext Transfer Protokol) | 80 | |
| FTP (File Transfer Protokol) | 21 | |
| SMTP (Simple Mail Transfer Protokol) | 25 (lub 587) | |
| POP3 (Post Office Protokol) | 110 | |
| Telnet | 23 | |
Lista portów wraz z opisem dostępna jest na stronie www.iana.org/assignments/port-numbers
1.2. Protokoły
|
Protokół stanowi zbiór zasad, które przestrzegane będą przez aplikacje typu klient i serwer, ustalający przepływ danych.
Protokoły niskiego poziomu, w skład których wchodzą TCP/IP, implementowane są przez system operacyjny. W skład protokołów wysokiego
poziomu wchodzą HTTP, FTP czy SMTP i są one zdefiniowane na stronie www.ietf.org firmy Internet Engineering Task Force.
Protokoły przesyłania są na wyższym poziomie niż protokoły transmisji, ponieważ abstrahują od mechanizmu transportu zapewnionego
przez TCP/IP. Dzięki tej właściwości protokoły nie są zależne od systemu operacyjnego czy sprzętu, są również niezależne od fizycznej sieci.
|
1.3. Typy połączeń gniazdowych
- Połączenia klienta – inicjowane są przez klienta i łączą jego lokalne gniazdo ze zdalnym gniazdem serwera. Gniazda klienta określają
serwer z którym się chcą połączyć poprzez podanie jego nazwy lub adresu IP wraz z portem.
- Połączenia nasłuchujące – są biernymi gniazdami serwera oczekującymi klienta. Po zgłoszeniu klienta żądania serwer tworzy nowe
gniazdo przeznaczone do określonego połączenia, a następnie powraca do nasłuchiwania.
- Połączenia serwera – są połączeniami aktywowanymi przez serwer po zaakceptowaniu żądania ze strony klienta.
2. Komponenty INDY w praktyce.
2.1 TCP
|
Protokół TCP (Transmision Control Protokol) jest protokołem połączeniowym, umożliwia zestawienie połączenia w którym efektywnie i niezawodnie
przesyłane są dane. Zestawione połączenie charakteryzuje się możliwością sterowania przepływem, potwierdzania odbioru, zachowania kolejności
danych, kontrolowania błędów oraz przeprowadzania retransmisji. Protokół TCP organizuje dwukierunkową współpracę między warstwą IP, a warstwami
wyższymi Do jego cech zaliczyć można również prawidłową obsługę niespodziewanie zakończonych aplikacji otrzymujących datagram oraz bezpieczne
izolowanie warstwy wyższej przed skutkami awarii w warstwie prokołu IP.
Protokół IP (Internet Protokol) jest podstawowym protokołem warstwy internetu i odpowiada on za przesyłanie pakietów zwanych datagramami.
Jest to protokół bezpołączeniowy, datagramy przesyłane są przez sieć bez kontroli poprawności ich dostarczenia. Wewnątrz datagramu IP umieszczane
są segmenty TCP oraz pakiety UDP w celu dalszego ich przesłania.
|
Protokół TCP w odniesieniu do IP posiada rozszerzone możliwości, do których zaliczyć można:
- strumienie – sformatowane dane są transportowane w postaci strumieni bitów zorganizaowancyh w bajty lub obiekty ośmio bitowe,
- buffer flow control – czyli kontrola zajętości bufora zapobiegająca przepełnianiom buforów powiązanych ze strumieniem danych.
Wolniejszy proces ma zapenioną możliwość nadążania z odbieraniem oraz przetwarzaniem danych,
- detekcja i korekcja błędów transmisji – w przypadku wystąpienia błędu w transmisji następuje powiadomienie o tym fakcie obu stron,
a następnie w celu zaradzeniu na wystąpienie błędu następuje powtórna transmisja brakujących pakietów,
- połączenie full-duplex – umożliwiające jednoczesną transmisję w obu kierunkach.
Dodatkowo używane są okna pozwalające zwiększyć wydajność transmisji poprzez dopuszczenie do transmisji kilku pakietów jednocześnie.
Technologia TCP pozwala utrzymać wiele jednoczesnych połączeń pomiędzy różnymi aplikacjami. W celu rozróżnienia połączeń używane są numery portów.
Numery IP wraz z numerami portów budują tzw.: końcówki.
Protokół tworzący internet (TCP/IP) opisywany jest w dwojaki sposób:
- za pomocą siedmiowarstwowego modelu ISO/OSI,
- uproszczonego modelu czterowarstwowego.
Warstwy z modelu uproszczonego pokrywają się z odpowienimi funkcjami z modelu ISO/OSI. Najważniejszymi są warstwy: sieciowa oraz transportowa,
drugoplanowymi są natomiast warstwy: dostępu do sieci i aplikacji. Wspominając o warstwach nadmienić należy, iż najniższą warstwą w hierarchi
architektury protokołu TCP/IP jest warstwa dostępu do sieci. Na jej poziomie dodaje się nagłówki oraz zakończenie do datagramów IP.
W wyniku czego uzyskuje się tzw. „ramki”, które są następnie przesyłane w sieci.
W skład protokołu TCP/IP wchodzą:
- protokoły transferu danych:
- IP (Internet Protocol),
- TCP (Transmission Control Protocol),
- UDP (User Datagram Protocol),
- protokoły kontroli poprawności połączeń:
- ICMP (Internet Control Message Protocol),
- protokoły zarzadzani siecią:
- SNMP (Simple Network Management Protocol),
- protokoły zdalnego włączania się do sieci:
- TELNET (Network Terminal Protocol),
- protokoły przesyłania plików:
- FTP (File Transfer Protocol).
W skład połączenia uzyskanego za pomocą TCP zaliczyć można:
- wysłanie bitu SYNC/ACK od strony próbującej nawiązać połączenie,
- odpowiedź w postaci SYNC/ACK potweirdzające nawiązanie połączenia,
- zamknięcie połączenia za pomocą pakietu FIN.
Do największych zalet protokołów TCP/IP zaliczyć można:
- niezależność od specyfikacji sprzętowo-programowej systemów,
- możliwość integracji wielu różnych rodzajów sieci,
- wspólny schemat adresacji,
- istnienie standardowych protokołów warstw wyższych.
W skład najbardziej znanych protokołów warstwy palikacjikorzystających z protokołu TCP należą:
- Telnet (Network Terminal Protokol) pozwalający na rozpoczęcie sesji poprzez sieć
dla usług terminalowych,
- FTP (File Transfer Protokol) umożliwiający przesyłanie plików,
- TFTP (Trivial File Transfer Protokol), który stanowi uproszczoną wersję protokołu FTP i wykorzystywany jest przy prostych usługach transferu
plików,
- SMTP (Simple Mail Transfer Protokol) umożliwiający na wymianę poczty elektronicznej,
- HTTP (Hyper Text Transfer Protokol) udostępniający w sieci strony zapiasane na serwerach WWW (World Wide Web).
Delphi umożliwia zestawić prostą komunikację dwóch programów przez gniazdo w obszarze sieci poprzez zastosowanie komponentów IdTCPClient oraz
IdTCPServer. Pierwszym krokiem zmierzającym do uzyskania w/w komunikacji jest ustalenie wspólnego portu zarówno dla aplikacji klient jak i serwer.
//aplikacja typu klient
IdTCPClient1.Port :=3500;
//aplikacja typu serwer
IdTCPServer1.DefaultPort := 3500;
Krokiem drugim jest zdefiniowanie adresu IP serwera po stronie klienta:
//Adres IP 127.0.0.1 jak opisano powyżej umożliwia uzyskanie testowego połączenia.
IdTCPClient1.Host := ‘127.0.0.1’;
Krokiem trzecim jest włączenie aplikacji typu serwer w tryb nasłuchiwania
IdTCPServer1.Active := true;
Tak przygotowana aplikacja klienta i serwera jest już przygotowana do nawiązania połączenia. Nawiazanie połaczenia ze strony klienta następuje
w momencie wykonania polecenia:
IdTCPClient1.Connect;
|
Za pomocą kolekcji Bindings można nawiązywać połączenia z wieloma adresami IP oraz portami.
|
Komponent IdTCPServer umożliwia w odpowiednim zdarzeniu obsłużyć przychodzące połączenie ze strony klienta, co możemy wykorzystać na przykład
do utworzenia logu zdarzeń.
procedure Tform1.IdTCPServer1Connect(Athread: TidPeerThread);
begin
Memo1.lines.add(‘Połączenie od: ‘ + Athread.Connection.Socked.Binding.PeerIP);
end;
Przeważnie po ustanowieniu połączenia następuje komunikacja pomiędzy aplikacją typu klient i serwer. Oparta on może być na łańcuchach tekstowych.
Gniazda, zarówno serwera jak i klienta, dysponują metodami odczytu i zapisu przeznaczonymi do przesyłania danych.
Po stronie klienta wysłanie ciągów znaków tekstowych odbywa się poprzez zastosowanie polecenia: IdTCPClient1.Write.
Rozbudowując zdarzenie OnClick przycisku Button uzyskujemy:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Edit1.Text <> '' then
begin
//nawiązanie połączenia
if not IdTCPClient1.Connected then
begin
try
IdTCPClient1.Connect(-1);
except
on exception do
begin
Showmessage('Nie można nawiązać połaczenia z serwerem: '
+ IdTCPClient1.Host);
end;
end;
end;
//rozłączenie
if IdTCPClient1.Connected then
begin
//wysyłanie wiadomości
IdTCPClient1.Write(Edit1.Text);
IdTCPClient1.Disconnect;
end;
end;
end;
Po stronie serwera należy rozbudować zdarzenie OnConnect:
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
Memo1.Lines.Add('Połączenie od: ' + AThread.Connection.Socket.Binding.PeerIP);
Memo1.Lines.Add('Otrzymany tekst: ' + Athread.Connection.AllData);
end;
Cały kod programu:
unit Main_Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, StdCtrls;
type
TForm1=class(TForm)
Button1: TButton;
Edit1: TEdit;
Memo1: TMemo;
IdTCPClient1: TIdTCPClient;
IdTCPServer1: TIdTCPServer;
procedure IdTCPServer1Connect(AThread: TIdPeerThread);
procedure Button1Click(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
//wyczyszczenie komponentów Edit1 i Memo1
Edit1.Clear;
Memo1.Clear;
//ustawienie portów
IdTCPServer1.DefaultPort := 3500;
IdTCPClient1.Port := 3500;
//ustawienie adresu IP po stronie klienta
IdTCPClient1.Host := '127.0.0.1';
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
//aktywacja serwera
If not IdTCPServer1.Active then
begin
IdTCPServer1.Active := true;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Edit1.Text <> '' then
begin
//nawiązanie połączenia
if not IdTCPClient1.Connected then
begin
try
IdTCPClient1.Connect(-1);
except
on exception do
begin
Showmessage('Nie można nawiązać połaczenia z serwerem: '
+ IdTCPClient1.Host);
end;
end;
end;
//rozłączenie
if IdTCPClient1.Connected then
begin
IdTCPClient1.Write(Edit1.Text);
IdTCPClient1.Disconnect;
end;
end;
end;
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
Memo1.Lines.Add('Połączenie od: '
+ AThread.Connection.Socket.Binding.PeerIP);
Memo1.Lines.Add('Otrzymany tekst: '
+ Athread.Connection.AllData);
end;
end.
Inne sposoby wymiany informacji pomiędzy IdTCPServer a IdTCPClient:
- poprzez zdefiniowanie właściwości po stronie serwera (ustawienie rozkazu tekstowego, kodu numerycznego, wyniku tekstowego):
object IdTCPServer1 : TIdTCPServer
CommandHandlers=<
Item
Command=‘test’
Name=‘TidCommandHandler0’
ParseParams=False
ReplyMormal.NumericCode=100
ReplyNormal.Text.Stringd=(‘Witam – Serwer INDY’)
ReplyNormal.TextCode=‘100’
end
Poniższy kod wykonuje klient:
procedure Tform1.Button1Click(Sender: TObject);
begin
IdTCPClient1.SendCmd(‘test’);
Showmessage(IdTCPClient1.LastCmdResult.TextCode
+ ‘ : ‘ + IdTCPClient1.LastCmdTResult.Text.Text);
end;
- poprzez obsługę zdarzenia OnCommand:
//Serwer
procedure Tform1.IdTCPServer1TidCommandHandler1Command (Asender: TIdCommand);
begin
Asender.Thread.Connection.Writeln(‘Odpowiedź serwera’);
end;
//Klient
procedure Tform1.ButtonClick(Sender: TObject);
begin
IdTCPClient1.WriteLn(‘execute’);
Showmessage(IdTCPClient1.ReadLn);
end;
2.1.1. Prosty przykład odpowiedzi (PING)
Budowę naszego programu rozpoczynamy od dodania do formy komponentów: IdTCPClient oraz IdTCPServer, można dołaczyć również IdAntiFreeze.
Następnie dodajemy dwa przyciski Button, pole Edit oraz Memo. W procedurze obsługi zdarzenia OnCreate (procedura FormCreate) dodajemy
czyszczenie pola Edit oraz Memo:
procedure TForm1.FormCreate(Sender: TObject);
begin
//czyszczenie pola Edit1
Edit1.Text := '';
//czyszczenie komponentu Memo1
Memo1.Clear;
end;
Następnie w procedurze obsługi zdarzenia OnClick jednego z przycisków (w naszym przypadku Button2) dodajemy kod aktywujący serwer oraz definiujący
jego port:
procedure TForm1.Button2Click(Sender: TObject);
begin
//zdefiniowanie standardowego portu serwera
IdTCPServer1.DefaultPort := 6000;
if not IdTCPServer1.Active then
begin
//aktywacja sewera
IdTCPServer1.Active := true;
//ukrycie przycisku Button2
Button2.Visible := false;
end;
end;
Musimy również wyłaczyć serwer przy zamknięciu aplikacji. Kod dezaktywacji serwera umieszczamy w procedurze obsługi zdarzenia OnClose formy
programu:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
//sprawdzamy czy serwer pracuje
if IdTCPServer1.Active then
begin
//dezaktywacja serwera
IdTCPServer1.Active := false;
end;
end;
Całość kodu poświęconą częsci dla serwera kończymy obsługując zdarzenie OnExecute komponentu IdTCPServer:
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
LLine: string; //nowa zmienna typu string
begin
Sleep(1000); //odczekanie 1 sekundy
LLine := AThread.Connection.ReadLn; //odczytanie wysłanej informacji przez klienta
//i przypisanie jej do zmiennej typu string
//sprawdzenie wartości otrzymanej i przypisanej do zmiennej
if LLine = 'PING' then
begin
//przesłanie odpowiedzi do klienta
AThread.Connection.WriteLn('250');
end;
end;
Cześć związaną z obdsługą serwera oraz rozpoczęciem i zakończeniem aplikacji mamy za sobą. Teraz musimy zbudować cześć związaną z obsługą klienta
TCP/IP. Cały kod obsługujący klienta naszej aplikacji umieścimy w obsłudze zdarzenia OnClick drugiego przycisku (w naszym przypadku Button1):
procedure TForm1.Button1Click(Sender: TObject);
begin
//sprawdzenie wartości przetrzymywanej w polu Edit
If Edit1.Text <> '' then
begin
//podstawienie adresu IP z pola Edit
IdTCPClient1.Host := Edit1.Text;
//ustawienie numeru portu klienta
IdTCPClient1.Port := 6000;
//ustawienie czasu na odpowiedź
IdTCPClient1.ReadTimeout := 5;
end;
//sprawdzenie czy klient nawiązał już połączenie
if (not IdTCPClient1.Connected) and (IdTCPClient1.Host <> ‘’) then
begin
//ustanowienie połączenia
IdTCPClient1.Connect(-1);
end;
//wykonywanie pętli dopóki połączenie jest ustanowione
while IdTCPClient1.Connected do
begin
//wysyłanie tekstu “PING” na serwer
IdTCPClient1.WriteLn('PING');
//odebranie tekstu przesłanego przez serwer i dodanie do Memo1
Memo1.Lines.Add(IdTCPClient1.ReadLn('', 3000));
//jeżeli minął czas odpowiedzi wyświetlenie informacji i zakończenie działania pętli
if IdTCPClient1.ReadLnTimedOut then
begin
//wyświetlenie informacji
MessageDlg('Timeout!', mtInformation, [mbOk], 0);
//zakończenie działania pętli
Exit;
end;
end;
end;
Cały kod programu:
unit indy_ping_unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
IdAntiFreezeBase, IdAntiFreeze, IdTCPServer;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Label1: TLabel;
Button1: TButton;
IdTCPClient1: TIdTCPClient;
IdAntiFreeze1: TIdAntiFreeze;
Button2: TButton;
IdTCPServer1: TIdTCPServer;
procedure IdTCPServer1Execute(AThread: TIdPeerThread);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '';
Memo1.Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
If Edit1.Text <> '' then
begin
IdTCPClient1.Host := Edit1.Text;
IdTCPClient1.Port := 6000;
IdTCPClient1.ReadTimeout := 5;
end;
if (not IdTCPClient1.Connected) and (IdTCPClient1.Host <> '') then
begin
IdTCPClient1.Connect(-1);
end;
while IdTCPClient1.Connected do
begin
IdTCPClient1.WriteLn('PING');
Memo1.Lines.Add(IdTCPClient1.ReadLn('', 3000));
if IdTCPClient1.ReadLnTimedOut then
begin
MessageDlg('Timeout!', mtInformation, [mbOk], 0);
Exit;
end;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPServer1.DefaultPort := 6000;
if not IdTCPServer1.Active then
begin
IdTCPServer1.Active := true;
Button2.Visible := false;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if IdTCPServer1.Active then
begin
IdTCPServer1.Active := false;
end;
end;
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
LLine: string;
begin
Sleep(1000);
LLine := AThread.Connection.ReadLn;
if LLine = 'PING' then
begin
AThread.Connection.WriteLn('250');
end;
end;
end.
Z uwagi na połączenie części klienta i serwera w jednym programie szybko można sprawdzić działanie poprzez próbę zestawienia połączenia z adresem
127.0.0.1 (łączymy się z serwerem na naszej stacji roboczej).
2.2. UDP
User Datagram Protocol (UDP) działa w sposób bezpołączeniowy. Zapewnia więc tak samo zawodny sposób dostarczania pakietów co protokół IP.
W protokole tym nie uwzględniono jakiejkolwiek kontroli transmisji czy korekcji błędów, konsekwencją czego pakiety UDP mogą zostać zagubione,
zduplikowane albo też przybyć w nieprawidłowej kolejności. Dzięki wykorzystaniu mniejszych nagłówków niż ma to miejsce w przypadku TCP
pozwala na lepsze wykorzystanie przepustowości łączy i dzięki temu na szybsze przetwarzanie pakietów.
|
UDP jest wykorzystywane głównie w przypadkach, gdy kontrola transmisji jest zapewniona przez protokoły wyższych warstw.
|
W skład najbardziej znanych protokołów warstwy aplikacji korzystających z protokołu UDP zaliczyć można:
- DNS (Domain Name Service) służący do zamiany adresów IP na nazwy,
- RIP (Routing Information Protokol) służący do wymiany informacji związanych z aktualizacją reguł doboru tras w węzłach sieci,
- NFS (Network File System) umożliwiający współdzielenie plików przez wiele komputerów połączonych w sieci.
W celu skorzystania z User Datagram Protocol w Delphi poprzez INDY należy zastosować IdUDPClient oraz IdUDPServer. Struktura programu jest
podobna jak w przypadku pakietu TCP (komponentów IdTCPClient oraz IdTCPServer).
Kod źródłowy programu korzystającego z protokołu UDP poprzez INDY przedstawia poniższy przykład:
unit udp_Unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdUDPServer, IdBaseComponent, IdComponent, IdUDPBase, IdUDPClient,
StdCtrls, IdSocketHandle, IdTCPServer;
type
TForm1=class(TForm)
IdUDPClient1: TIdUDPClient;
IdUDPServer1: TIdUDPServer;
Edit1: TEdit;
Button1: TButton;
Memo1: TMemo;
procedure IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
procedure IdUDPServer1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Clear;
Memo1.Clear;
IdUDPClient1.Port := 36000;
IdUDPServer1.DefaultPort := 36000;
IdUDPServer1.Active := true;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
IdUDPClient1.Host := '127.0.0.1';
IdUDPClient1.Send(Edit1.Text);
end;
procedure TForm1.IdUDPServer1Status(ASender: TObject; const AStatus: TIdStatus;
const AStatusText: string);
begin
Memo1.Lines.Add(AStatusText);
end;
procedure TForm1.IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
var
sText: string;
begin
//do uses dodać: IdSocketHandle
Memo1.Lines.Add(ABinding.PeerIP);
AData.Position := 0;
SetLength(sText, AData.Size);
AData.ReadBuffer(sText[1], AData.Size);
Memo1.Lines.Add(sText)
end;
end.
Procedurę odczytu można również zbudować korzystając ze strumieni:
procedure IdUDPServerUDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
var
Text: string;
StrStream: TStringStream;
begin
StrStream := TStringStream.Create('');
try
StrStream.CopyFrom(AData, AData.Size);
Text := StrStream.DataString;
finally
StrStream.Free;
end;
end;
2.3. Obsługa poczty - wysyłanie (SMTP) i odbieranie (POP3)
W celu używania protokołów pocztowych w Delphi za pomocą pakietu INDY niezbędne jest umieszczenie komponentu komunikatów (IdMessage)
w naszej aplikacji. Wysyłanie poczty nastąpi po jego wypełnieniu i użyciu komponentu IdSMTP. Odbiór wiadomości pocztowej umożliwia
komponent IdPOP3, który zwraca obiekt IdMessage.
2.3.1 SMTP
|
SMTP - Simple Mail Transfer Protocol. Protokół ten jest głównie używany do przenoszenia poczty elektronicznej. Standardowo serwery
SMTP oczekują na przychodzącą pocztę na porcie 25. Odebraną pocztę kopiują do odpowiednich skrzynek pocztowych. Jeśli wiadomość
nie może zostać dostarczona, nadawcy zostaje zwrócony komunikat błędu zawierający początkowy fragment wiadomości.
|
Przykładowy kod źródłowy wysyłania wiadomości pocztowej:
procedure Tform1.wyslij();
begin
//czyszczenie zawartości komponentu IdMessage
IdMessage1.Clear;
//ustawienie adresu IP/nazwy serwera
IdSMTP1.Host :=Edit1.Text;
//ustawienie nazwy użytkownika
IdSMTP1.Username := Edit2.Text;
//ustawienie hasła użytkownika
IdSMTP1.Password := Edit3.Text;
//ustawienie trybu authentifikacji
IdSMTP1.AuthenticationType := atLogin;
//nawiązanie połączenia w przypadku jego braku
if not IdSMTP1.Connected then
begin
try
StatusBar1.SimpleText := 'Zestawianie połączenia z serwerem ...';
//nawiązywanie połączenia
IdSMTP1.Connect(-1);
StatusBar1.SimpleText := 'Połączony ...';
except
on exception do
begin
StatusBar1.SimpleText :='BŁĄD !!! Wysyłanie poczty !!! Połączenie z serwerem '
+ IdSMTP1.Host + ' niepowiodło się !!!';
end;
end;
end;
//jeżeli połączenie jest zestawione – wysyłanie wiadomości
if IdSMTP1.Connected then
begin
//dodanie informacji od kogo
IdMessage1.From.Text := Edit4.Text;
//dodanie tematu
IdMessage1.Subject :=Edit5.Text;
//dodanie adresu odbiorcy
IdMessage1.Recipients.Add.Text :=Edit6.Text;
//dodanie załącznika do wiadomości
TidAttachment.create(IdMessage1.MessageParts, ExtractFilePath(ParamStr(0))
+ 'zalacznik.txt');
StatusBar1.SimpleText := 'Wysyłanie wiadomości ...';
Try
//wysyłanie wiadomości
IdSMTP1.Send(IdMessage1);
StatusBar1.SimpleText := 'Wiadomość wysłana.';
//czyszczenie komponentu IdMessage
IdMessage1.Clear;
except
on exception do
begin
showmessage('Błąd przy wysyłaniu wiadomości !!!');
StatusBar1.SimpleText := 'Błąd przy wysyłaniu wiadomości !!!';
//czyszczenie komponentu IdMessage
IdMessage1.Clear;
end;
end;
end;
end;
2.3.2 POP3.
|
POP3 - (Post Office Protocol) służy do przenoszenia poczty elektronicznej z serwera pocztowego na komputer użytkownika. Protokół oparty
jest na architekturze klient-serwer, w której pocztę odbiera serwer pocztowy. Serwer przechowuje wiadomość aż do momentu, gdy użytkownik
się zaloguje i ją pobierze.
|
Przykładowy kod źródłowy odbioru wiadomości pocztowej:
procedure Tform1.obierz ();
var
il_wiad : integer; //ilość wiadomości
il_zal : integer; //ilość załączników
zal_nazwa : string; //nazwa załącznika
begin
//czyszczenie komponentu IdMessage
IdMessage1.Clear;
//ustawienie adresu IP/nazwy serwera
IdPOP31.Host := Edit1.Text;
//ustawienie nazwy użytkownika
IdPOP31.Username := Edit2.Text;
//ustawienie hasła użytkownika
IdPOP31.Password := Edit3.Text;
//nawiązanie połączenia w przypadku jego braku
If not IdPOP31.Connected then
begin
try
statusbar1.SimpleText := 'Odbieranie poczty - zestawianie połączenia!!!';
//zestawianie połączenia
IdPOP31.Connect(-1);
statusbar1.SimpleText := 'Odbieranie poczty - połączony!!!';
except
on exception do
begin
statusbar1.SimpleText := 'Odbieranie poczty - błąd połączenia!!!';
end;
end;
end;
//odebranie wiadomości w przypadku pomyślnego nawiązania połączenia
if IdPOP31.Connected then
begin
//sprawdzenie ilości wiadomości na serwerze
il_wiad := IdPOP31.CheckMessages;
statusbar1.SimpleText := il_wiad;
while il_wiad > 0 do
begin
//czyszczenie komponentu IdMessage
IdMessage1.Clear;
//odbiór wiadomości z serwera, wypełnienie komponentu IdMessage
IdPOP31.Retrieve(il_wiad, IdMessage1);
//sprawdzenie ilości załączników w wiadomości
il_zal := IdMessage1.MessageParts.Count - 1;
Showmessage('Odbieranie wiadomości: ' + inttostr(il_wiad) + ' Od: '
+ IdMessage1.From.Text + ' Temat: '
+ IdMessage1.Subject + ‘ Załączników: ‘
+ inttostr(il_zal);
while il_zal > 0 do
begin
if (IdMessage1.MessageParts.Items[il_zal] is TIdAttachment) then
begin
zal_nazwa := TIdAttachment(IdMessage1.MessageParts.Items[il_zal]).Filename;
TIdAttachment(IdMessage1.MessageParts.Items[il_zal]).SaveToFile
(ExtractFilePath(ParamStr(0)) + zal_nazwa);
end;
il_zal := il_zal – 1;
end;
//kasowanie wiadomości na serwerze
IdPOP31.Delete(il_wiad);
il_wiad := il_wiad – 1;
end;
end;
end;
2.4. HTTP
Protokół HTTP (Hyper-Text Transfer Protokol) jest kolejnym (poza protokołami obsługi wiadomości) z najbardziej popularnych. Po stronie
klienta następuje czytanie plików, serwery natomiast generują i udostępniają strony HTML. Strony internetowe generowane są w sposób
statyczny, jak i dynamiczny (np.; w odpowiedzi na wykonywane czynności użytkownika). Na podstawie wpisanego w przeglądarce adresu
następuje wyszukanie (lokalizacja) adresu IP serwera i dane przesyłane są do przeglądarki użytkownika.
W przypadku gdy protokół HTTP nie zapewnia wystarczającego poziomu bezpieczeństwa wykorzystuje się jego rozbudowaną wersję: HTTPS.
Rozbudowana wersja HTTPS umożliwia szyfrowanie danych pomiędzy klientem a serwerem i nosi ono nazwę SSL (Secure Socket Layer).
Zaimplementowanie w kodzie źródłowym Delphi protokołu HTTP jest możliwe dzięki kontrolce TIdHTTP (zakładka Indy Clients).
Połączenie.
Zachowując się intuicyjnie, możemy w łatwy sposób nawiązać połączenie z serwerem poprzez wpisanie w metodzie Connect komponentu TIdHTTP
adresu serwera wraz z portem.
IdHTTP.connect(‘www.google.pl’, 80);
Status.
W celu sprawdzenia statusu połączenia stosujemy zdarzenie OnStatus. Pozwala ono wychwycić na jakim etapie działa komponent TIdHTTP. Parametr
AStatus zwraca informacje o stanie połączenia w formie danych:
- hsResolving – szukanie hosta,
- hsConnecting – próba połączenia,
- hsConnected – połączony,
- hsDisconnecting – rozłączenie,
- hsDisconnected – rozłączony.
procedure TForm1.IdHTTPStatus(ASender: TObject; AStatus: TIdStatus; AStatusText: string );
begin
Memo1.Lines.Add(AStatusText);
case AStatus of
hsResolving: Memo1.Lines.Add(‘Wyszukiwanie hosta …’);
hsConnecting: Memo1.Lines.Add(‘Łączenie z hostem …’);
hsConnecting: Memo1.Lines.Add(‘Połączenie zestawione.’);
hsDisconnecting: Memo1.Lines.Add(‘Rozłączanie …’);
hsDisconnected: Memo1.Lines.Add(‘Rozłączono !’);
end;
end;
Metoda Post.
Istotą metody Post jest przekazywanie danych w nagłówku HTTP do przeglądarki. Dane są przekazywane w postaci:
nazwa_pola=wartosc_pol&nazwapola2=wartosc_pola2. Poszczególne elementy oddzielone są od siebie znakiem &, natomiast nazwa pola oddzielona
jest od wartości znakiem równości („=”).
Wykorzystanie metody Post komponentu TIdHTTP sprowadza się do podania dwóch parametrów typu TStringStream (strumienie). Pierwszy z parametrów
zawiera dane przesyłane do skryptu (wartość wejściowa), drugi parametr zawiera dane zwrócone przez skrypt (wartość wyjściowa).
Używając metody Post musimy podać jeszcze adres strony, do której przekazany zostanie nagłówek oraz treść IdHTTP.
Sposób użycia metody Post:
IdHTTP.Post(‘http://adres.www/index.php’, Stream_in, Stream_out);
Metoda Get.
Podobnie jak w przypadku metody Post poszczególne pola oddzielone są od siebie znakiem &. Podstawową różnicą jest natomiast sposób ich
przekazywania. Dane dołączane są do adresu strony, wygląda to następująco:
„http://www.google.pl/search?pole1=wartosc1&pole2=wartosc2”.
Metodą Get sugeruje się przekazywanie niewielkiej ilości danych.
Pobieranie kodu HTML polega na użyciu metody Get komponentu TIdHTTP. W wyniku działania metody Get kod HTML zostanie zwrócony w postaci
tekstu (String).
var
html_tekst : string;
begin
html_tekst := IdHTTP.Get(‘http://www.google.pl/’);
end;
2.4.1. Zapis strony do pliku na dysku lokalnym
Zastosowanie metody Get w korelacji ze strumieniem (String; TStream) pozwoli na zapisanie strony www do pliku.
var
ms: TMemoryStream;
begin
ms := TMemoryStream.Create;
try
Edit1.Text := 'http://4programmers.net/Delphi/Indy';
IdHTTP1.Get(Edit1.Text, ms);
ms.SaveToFile(ExtractFilePath(ParamStr(0)) + FormatDateTime('yyyy-mm-dd', date)
+ '_' + FormatDateTime('hh_nn_ss_zzz', Time) + '_test.html');
finally
ms.Free;
end;
end;
Funkcje formatowania daty (FormatDateTime) użyte zostały ze względu na możliwy do wystąpienia błąd w przypadku istnienia pliku „*test.html”
na dysku. Oczywiście można użyć poleceń języka związanych z kasowaniem plików.
Tak zapisany plik można otworzyć za pośrednictwem przeglądarki internetowej używając polecenia ShellExecute:
var
ms: TMemoryStream;
plik_nazwa : string;
begin
ms := TMemoryStream.Create;
try
Edit1.Text := 'http://4programmers.net/Delphi/Indy';
IdHTTP1.Get(Edit1.Text, ms);
plik_nazwa := ExtractFilePath(ParamStr(0)) + FormatDateTime('yyyy-mm-dd', date)
+ '_' + FormatDateTime('hh_nn_ss_zzz', Time) + '_test.html';
ms.SaveToFile(plik_nazwa);
finally
ms.Free;
end;
//OTWARCIE ZAPISANEGO PLIKU !!!
if fileexists(plik_nazwa) then
begin
ShellExecute(Handle, 'open',Pchar(plik_nazwa), nil, nil, SW_SHOWNORMAL);
end;
end;
2.4.2. Niestandardowy serwer HTTP.
Budowę niestandardowego serwera HTTP opartego na INDY należy zacząć umieszczając na formie komponent TIdHTTPServer z palety Indy Servers.
Po umieszczeniu komponentu niezbędne jest obsłużenie aktywacji i dezaktywacji komponentu. W celach testowych dobrze jest również dodać
pole edycyjne przeznaczone na numer portu.
Włączenie serwera:
IdHTTP.Server1.Active := true;
Wyłączenie serwera:
IdHTTP.Server1.Active :=false;
Ustawienie domyślnego portu na którym będzie pracował serwer (ustawienie portu należy przeprowadzać przed uruchomieniem serwera):
IdHTTPServer1.DefaultPort := 80;
Zmieniając domyślne ustawienia portu serwera możliwe jest używanie kilku serwerów w jednym czasie.
Po uruchomieniu serwera metodą Active należy obsłużyć zdarzenie OnCommandGet odpowiedzialne za zwrócenie strony wraz z informacjami dodatkowymi:
procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
html_result : string;
begin
//procedura zwraca stronę www
//logowanie
RichEdit1.Lines.Add(ARequestInfo.Document);
end;
A oto kod źródłowy najważniejszych procedur naszego niestandardowego serwera:
//WŁĄCZENIE SERWERA
procedure TForm1.Button1Click(Sender: TObject);
var
//do uses dodać: idSocketHandle
Binding : TIdSocketHandle;
begin
//włączenie serwera
if not IdHTTPServer1.Active then
begin
IdHTTPServer1.DefaultPort := 80;
IdHTTPServer1.Bindings.Clear;
Binding := IdHTTPServer1.Bindings.Add;
Binding.Port := StrToIntDef(inttostr(IdHTTPServer1.DefaultPort), 80);
Binding.IP := '127.0.0.1';
try
IdHTTPServer1.Active := true;
except
on e: exception do
begin
ShowMessage(format('Exception %s in Activate. Error is:"%s".', [e.ClassName,
e.Message]));
end;
end;
end;
if IdHTTPServer1.Active then
begin
Showmessage('HTTP Serwer właczony!.' + #13 + 'Nasłuchiwanie na porcie: '
+ inttostr(IdHTTPServer1.DefaultPort));
end;
end;
//WYŁĄCZENIE SERWERA
procedure TForm1.Button2Click(Sender: TObject);
begin
//wyłaczenie serwera
if IdHTTPServer1.Active then
begin
IdHTTPServer1.Active := false;
end;
if not IdHTTPServer1.Active then
begin
Showmessage('HTTP Serwer wyłączony!.');
end;
end;
//OBSŁUGA ZDARZENIA ONCOMMANDGET
procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
html_result : string;
begin
//procedura zwraca stronę www
//logowanie – pokazanie w RichEdit nazwy wyświetlanej strony
RichEdit1.Lines.Add(ARequestInfo.Document);
//budowa i wyświetlenie HTML’u w przeglądarce internetowej
end;
W wynik podania w adresie przeglądarki http://localhost/test?user=tete zobaczymy efekt działania serwera (można również użyć polecenia z numerem
portu: http://localhost:80/test?user=tete) jak na poniższym przykładzie:
Powyższy przykład pokazuje tworzenie statycznej strony www. Jednakże możliwe jest również tworzenie stron dynamicznych dzięki komponentom serii
Producer znajdujących się na karcie Internet palety komponentów Delphi.
Komponenty serii Producer:
- PageProducer – umożliwia modyfikację pliku HTML, w którym umieszczone zostały specjalne znaczniki. W czasie działania programu PageProducer
zamienia znaczniki na właściwy kod. Dzięki takiej metodzie istnieje łatwy sposób modyfikacji wybranych fragmentów kodu HTML. Specjalne znaczniki,
o których mowa, przetwarzane są w zdarzeniu OnTag komponentu PageProducer. Podstawowym formatem znaczniku specjalnego jest
<#nazwaznacznikaspecjalnego>.
- DataSetPageProducer – dzięki niemu możliwe jest automatyczne zastępowanie znaczników nazwami pól ze źródła bazy danych.
- DataSetTableProducer – umożliwia on wyświetlać w formie tabeli dane otrzymywane z zapytania lub zbioru danych.
- QueryTableProducer i SQLQueryTableProducer – przeznaczeniem jest budowanie zapytań z parametrami na podstawie wprowadzonych danych
w formularzu.
Odbiegając nieco od tematu artykułu zauważyć można, iż komponenty z serii Producer zostały specjalne dodane w Delphi do obsługi baz danych.
2.4.3. Prosty serwer HTTP.
Podobnie jak w przypadku niestandardowego serwera HTTP na formie umieszczamy komponent TIdHTTPServer z palety Indy Servers. Dodajemy do formy
pola TEdit (przeznaczone na adres serwera, port serwera, katalog dokumentu głównego index.html), przycisk TButton (przeznaczony do
włączenia/wyłączenia serwera) oraz TMemo (który spełanić będzie funkcję prostego logu zdarzeń).
Do słowa uses dodajemy ponadto: idSocketHandle, IdGlobal, IdThreadMgr, IdThreadMgrDefault, syncobjs, IdThreadMgrPool, ExtCtrls, IdIntercept,
IdIOHandlerSocket.
W momencie tworzenia formy obsługujemy zdarzenie OnCreate:
procedure TForm1.FormCreate(Sender: TObject);
begin
memo1.Clear;
Edit_adres.Text := '127.0.0.1';
Edit_port.Text := '8080';
Edit_kat.Text := 'C:\';
if IdHTTPServer1.Active then
begin
Button1.Caption := 'Wyłącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP włączony, nasłuchiwanie na porcie: ' + Edit_port.Text);
end;
if not IdHTTPServer1.Active then
begin
Button1.Caption := 'Włącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP wyłączony!!!');
end;
end;
Włączenie i wyłączenie serwera obsługujemy poprzez zdarzenie OnClick przycisku Button:
procedure TForm1.Button1Click(Sender: TObject);
var
//do słowa uses dodać: idSocketHandle
Binding : TIdSocketHandle;
i : integer;
begin
i := 0;
if not IdHTTPServer1.Active then
begin
IdHTTPServer1.Bindings.Clear;
Binding := IdHTTPServer1.Bindings.Add;
Binding.Port := StrToIntDef(edit_port.text, 80);
Binding.IP := Edit_adres.text;
end;
if not DirectoryExists(edit_kat.text) then
begin
ShowMessage(Format('Web root katalog (%s) nieznaleziony.',[edit_kat.text]));
end;
if (DirectoryExists(edit_kat.text)) and (not IdHTTPServer1.Active) and (i=0) then
begin
try
IdHTTPServer1.Active := true;
ShowMessage(format('Nasłuchiwanie za połączeniem HTTP na
%s:%d.',[IdHTTPServer1.Bindings[0].IP,
IdHTTPServer1.Bindings[0].Port]));
except
on e: exception do
begin
ShowMessage(format('Wyjątek %s przy aktywacji. Błąd:"%s".', [e.ClassName,
e.Message]));
end;
end;
i := 1;
end;
if (DirectoryExists(edit_kat.text)) and (IdHTTPServer1.Active) and (i=0) then
begin
IdHTTPServer1.Active := false;
ShowMessage(format('Nasłuchiwanie za połączeniem HTTP na %s:%d
zakończone.',[IdHTTPServer1.Bindings[0].IP,
IdHTTPServer1.Bindings[0].Port]));
end;
if IdHTTPServer1.Active then
begin
Button1.Caption := 'Wyłącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP włączony, nasłuchiwanie na porcie: ' + Edit_port.Text);
end;
if not IdHTTPServer1.Active then
begin
Button1.Caption := 'Włącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP wyłączony!!!');
end;
end;
Najważniejszą procedurą wysyłająca stronę jest procedura obsługująca zdarzenie OnCommandGet komponentu IdHTTPServer:
procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
LocalDoc: string;
ByteSent: Cardinal;
ResultFile: TFileStream;
begin
{
Wywołanie serwera: http://127.0.0.1:8080/
}
LocalDoc := ExpandFilename(edit_kat.text + 'index.html');
if FileExists(LocalDoc) then
begin
if AnsiSameText(Copy(LocalDoc, 1, Length(edit_kat.text)), edit_kat.Text) then
begin
if AnsiSameText(ARequestInfo.Command, 'HEAD') then
begin
ResultFile := TFileStream.create(LocalDoc, fmOpenRead or fmShareDenyWrite);
try
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := GetMIMEType(LocalDoc);
AResponseInfo.ContentLength := ResultFile.Size;
finally
ResultFile.Free;
end;
end else
begin
ByteSent := IdHTTPServer1.ServeFile(AThread, AResponseInfo, LocalDoc);
end;
end;
end;
end;
oraz funkcja:
function TForm1.GetMIMEType(sFile: TFileName): String;
begin
result := MIMEMap.GetFileMIMEType(sFile);
end;
Cały kod źródłowy serwera zamieszczony jest poniżej:
unit http_serwer_unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPServer,
IdCustomHTTPServer, IdHTTPServer,
//dodane do uses
idSocketHandle, IdGlobal, IdThreadMgr, IdThreadMgrDefault,
syncobjs, IdThreadMgrPool, ExtCtrls, IdIntercept,
IdIOHandlerSocket;
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Edit_adres: TEdit;
Edit_port: TEdit;
Edit_kat: TEdit;
Button1: TButton;
Memo1: TMemo;
Label4: TLabel;
IdHTTPServer1: TIdHTTPServer;
procedure IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
function GetMIMEType(sFile: TFileName): String;
public
{ Public declarations }
MIMEMap: TIdMIMETable;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
memo1.Clear;
Edit_adres.Text := '127.0.0.1';
Edit_port.Text := '8080';
Edit_kat.Text := 'C:\';
if IdHTTPServer1.Active then
begin
Button1.Caption := 'Wyłącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP włączony, nasłuchiwanie na porcie: ' + Edit_port.Text);
end;
if not IdHTTPServer1.Active then
begin
Button1.Caption := 'Włącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP wyłączony!!!');
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
//do słowa uses dodać: idSocketHandle
Binding : TIdSocketHandle;
i : integer;
begin
i := 0;
if not IdHTTPServer1.Active then
begin
IdHTTPServer1.Bindings.Clear;
Binding := IdHTTPServer1.Bindings.Add;
Binding.Port := StrToIntDef(edit_port.text, 80);
Binding.IP := Edit_adres.text;
end;
if not DirectoryExists(edit_kat.text) then
begin
ShowMessage(Format('Web root katalog (%s) nieznaleziony.',[edit_kat.text]));
end;
if (DirectoryExists(edit_kat.text)) and (not IdHTTPServer1.Active) and (i=0) then
begin
try
IdHTTPServer1.Active := true;
ShowMessage(format('Nasłuchiwanie za połączeniem HTTP na
%s:%d.',[IdHTTPServer1.Bindings[0].IP,
IdHTTPServer1.Bindings[0].Port]));
except
on e: exception do
begin
ShowMessage(format('Wyjątek %s przy aktywacji. Błąd:"%s".', [e.ClassName,
e.Message]));
end;
end;
i := 1;
end;
if (DirectoryExists(edit_kat.text)) and (IdHTTPServer1.Active) and (i=0) then
begin
IdHTTPServer1.Active := false;
ShowMessage(format('Nasłuchiwanie za połączeniem HTTP na %s:%d
zakończone.',[IdHTTPServer1.Bindings[0].IP,
IdHTTPServer1.Bindings[0].Port]));
end;
if IdHTTPServer1.Active then
begin
Button1.Caption := 'Wyłącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP włączony, nasłuchiwanie na porcie: ' + Edit_port.Text);
end;
if not IdHTTPServer1.Active then
begin
Button1.Caption := 'Włącz serwer HTTP';
memo1.Lines.Add('Serwer HTTP wyłączony!!!');
end;
end;
procedure TForm1.IdHTTPServer1CommandGet(AThread: TIdPeerThread;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
LocalDoc: string;
ByteSent: Cardinal;
ResultFile: TFileStream;
begin
{
Wywołanie serwera: http://127.0.0.1:8080/
}
LocalDoc := ExpandFilename(edit_kat.text + 'index.html');
if FileExists(LocalDoc) then
begin
if AnsiSameText(Copy(LocalDoc, 1, Length(edit_kat.text)), edit_kat.Text) then
begin
if AnsiSameText(ARequestInfo.Command, 'HEAD') then
begin
ResultFile := TFileStream.create(LocalDoc, fmOpenRead or fmShareDenyWrite);
try
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentType := GetMIMEType(LocalDoc);
AResponseInfo.ContentLength := ResultFile.Size;
finally
ResultFile.Free;
end;
end else
begin
ByteSent := IdHTTPServer1.ServeFile(AThread, AResponseInfo, LocalDoc);
end;
end;
end;
end;
function TForm1.GetMIMEType(sFile: TFileName): String;
begin
result := MIMEMap.GetFileMIMEType(sFile);
end;
end.
2.5. FTP.
|
FTP (File Transfer Protocol) – protokół ten działa na zasadzie klient-serwer. Zdefiniowany jest jako jedna z usług sieciowych w warstwowym
modelu TCP/IP, który opisujące funkcje sieci komputerowych. Protokół ten określa sposób przesyłania plików pomiędzy dwoma komputerami.
Połączenie z serwerem FTP następuje na zasadzie autoryzacji użytkownika, a dostęp do poszczególnych plików i katalogów uzależniony jest
od posiadanych uprawnień logującego się użytkownika.
|
W celu zbudowania aplikacji typu klient niezbędne jest umieszczenie na formie projektu komponentu IdFTP z panelu Indy Client. Przy budowie
aplikacji typu serwer na formie należy umieścić komponent IdFTPServer z panelu Indy Servers.
2.5.1. Klient FTP.
Pierwszą rzeczą jaką możemy zrobić (nie musimy) jest ustalenie portu na jakim ma pracować klient FTP (standardowo jest to port numer 21).
Zmianę portu umożliwia nam metoda Port komponentu IdFTP:
IdFTP1.Port := '21';
Następnym krokiem przed nawiązaniem połączenia jest zdefiniowanie adresu serwera, nazwy użytkownika oraz jego hasła:
IdFTP1.Username := 'anonymous';
IdFTP1.Password := 'haslo';
IdFTP1.Host := '127.0.0.1';
|
Serwery FTP przeznaczone do użytku publicznego pozwalają przeważnie na uzyskanie anonimowego dostępu do określonych zasobów osobom,
które posługują się identyfikatorem 'anonymous'.
|
Mając ustalone powyższe parametry możemy nawiązać połączenie metoda Connect.
if not IdFTP1.Connected then
begin
IdFTP1.Connect();
end;
W celu rozłączenia z serwerem FTP należy użyć metody Disconnect.
if IdFTP1.Connected then
begin
IdFTP1.Disconnect;
end;
Zbudowanie programu na powyższych zasadach tworzyć może jedynie zarys stosowanych metod w aplikacji typu klient. Każdy klient FTP umożliwia
dokonywanie pewnych czynności zarówno po stronie serwera jak i na dysku lokalnym. Należy zatem obsłużyć między innymi metody wysyłania
i odbierania plików/katalogów, tworzenia nowych katalogów na serwerze, zmiany trybu przesyłania danych (tekstowo/binarnie). Niezbędne jest
również dodanie elementów wizualnych, których zadaniem będzie prezentacja danych w formie okienkowej.
Metoda Put obsługuje wysyłanie danych na serwer.
var
localfile, remotefile : string
begin
IdFTP1.Put(LocalFile, RemoteFile);
end;
Gdzie:
- localfile – pełna ścieżka wraz z nazwą pliku na dysku lokalnym,
- remotefile – nazwa pliku wysyłanego na serwer.
Metoda Get obsługuje pobieranie pliku z serwera.
var
localfile, remotefile : string
begin
IdFTP1.get(RemoteFile, LocalFile);
end;
Gdzie:
- remotefile – nazwa pliku pobieranego z serwera,
- localfile – pełna ścieżka wraz z nazwą pliku zapisywana na dysku lokalnym.
Tworzenie nowych katalogów na serwerze obsługuje metoda MakeDir
var
nowy_kat : string;
begin
IdFTP1.MakeDir(nowy_kat);
end;
Zmianę katalogu na serwerze obsługuje metoda ChangeDir.
var
kat_zdalny : string;
begin
IdFTP1.ChangeDir(kat_zdalny);
end;
Metodę zmiany katalogów na serwerze jak również na dysku lokalnym dobrze jest zaimplementować w obsłudze zdarzenia elementów wizualnych
(takich jak np.: ListView).
Zmianę trybu przesyłania danych obsługuje metoda TransferType. W celu zautomatyzowania wyboru można dodać obsługę metody do elementu
TRadioGroup. Do słowa uses należy dodać: IdFTPCommon.
case RadioGroup1.ItemIndex of
0: IdFTP1.TransferType := ftAscii;
1: IdFTP1.TransferType := ftBinary;
end;
2.5.2. Serwer FTP.
W skład najważniejszych elementów serwera FTP wchodzić musi obsługa zdarzeń:
- OnAfterUserLogin – w obsłudze zdarzenia ustawia się parametry startowe,
- OnUserLogin – w obsłudze zdarzenia dokonujemy kontroli poprawności logującego się użytkownika,
- OnStoreFile – w obsłudze zdarzenia definiujemy zachowanie serwera w przypadku odbierania pliku od klienta,
- On RetrieveFile – w obsłudze zdarzenia definiujemy zachowanie serwera w przypadku żądania pobrania pliku przez klienta,
- OnChangeDirectory – w obsłudze zdarzenia definiujemy zachowanie serwera w przypadku otrzymania żądania zmiany katalogu przez klienta,
- OnListDirectory – w obsłudze zdarzenia definiujemy zachowanie serwera w przypadku otrzymania żądania ze strony klienta podania struktury
plików i katalogów,
- OnMakeDirectory – jak nazwa wskazuje zdarzenie obsługuje żądanie utworzenia katalogu na serwerze.
Zdarzenie OnAfterLogin:
procedure TForm1.IdFTPServer1AfterUserLogin(ASender: TIdFTPServerThread);
begin
//ustawiamy katalog domowy podczas logowania
ASender.HomeDir := '\';
ASender.CurrentDir := '\';
end;
Zdarzenie OnUserLogin:
procedure TForm1.IdFTPServer1UserLogin(ASender: TIdFTPServerThread;
const AUsername, APassword: string; var AAuthenticated: Boolean);
begin
//sprawdzenie użytkownika
AAuthenticated := ((AUsername = 'anonymous') and (APassword = 'haslo'));
if AAuthenticated = true then
begin
//funkcje po rozpoznaniu użytkownika
end;
end;
Zdarzenie OnStoreFile:
procedure TForm1.IdFTPServer1StoreFile(ASender: TIdFTPServerThread;
const AFileName: string; AAppend: Boolean; var VStream: TStream);
var
appdir : string;
begin
appdir := sc_programu:=ExtractFilePath(ParamStr(0));
//zmienną appdir można zdefiniować jako public
//oraz w obsłudze zdarzenia OnAfterLogin podstawić wartość jak powyżej
//odbieranie pliku
if not Aappend then
begin
//odbieranie pliku - nowy plik
VStream := TFileStream.Create(AppDir + AFilename,fmCreate);
end;
if Aappend then
begin
//odbieranie pliku - nadpisywanie istniejącego
VStream := TFileStream.Create(AppDir + AFilename,fmOpenWrite);
end;
end;
Zdarzenie On RetrieveFile:
procedure TForm1.IdFTPServer1RetrieveFile(ASender: TIdFTPServerThread;
const AFileName: string; var VStream: TStream);
var
appdir : string;
begin
appdir := sc_programu:=ExtractFilePath(ParamStr(0));
//zmienną appdir można zdefiniować jako public
//oraz w obsłudze zdarzenia OnAfterLogin podstawić wartość jak powyżej
//wysyłanie pliku
VStream := TFileStream.Create(AppDir + AFilename,fmOpenRead);
Application.ProcessMessages;
end;
Zdarzenie OnChangeDirectory:
procedure TForm1.IdFTPServer1ChangeDirectory(ASender: TIdFTPServerThread;
var VDirectory: string);
begin
//zmiana katalogu
Asender.CurrentDir := VDirectory;
end;
Zdarzenie OnListDirectory (zmienne appdir i change_dir typu string do zdefiniowania jako public w innych częściach kodu źródłowego programu):
procedure TForm1.IdFTPServer1ListDirectory(ASender: TIdFTPServerThread;
const APath: string; ADirectoryListing: TIdFTPListItems);
var
LFTPItem :TIdFTPListItem;
SR : TSearchRec;
SRI : Integer;
begin
//przesłanie zawartości katalogu do klienta
SRI := FindFirst(AppDir + change_dir + '*.*', faAnyFile - faHidden - faSysFile, SR);
While SRI = 0 do
begin
LFTPItem := ADirectoryListing.Add;
LFTPItem.FileName := SR.Name;
LFTPItem.Size := SR.Size;
LFTPItem.ModifiedDate := FileDateToDateTime(SR.Time);
Application.ProcessMessages;
if SR.Attr = faDirectory then
LFTPItem.ItemType := ditDirectory
else
LFTPItem.ItemType := ditFile;
SRI := FindNext(SR);
end;
FindClose(SR);
SetCurrentDir(AppDir + '..');
end;
Zdarzenie OnMakeDirectory:
procedure TForm1.IdFTPServer1MakeDirectory(ASender: TIdFTPServerThread;
var VDirectory: string);
begin
//tworzenie katalogu
if not ForceDirectories(Appdir + VDirectory) then
begin
//można dodać komunikat błędu na serwerze
Memo1.Lines.Add('Błąd tworzenia katalogu: ' + Appdir + VDirectory);
end;
end;
2.6. IdThreadComponent
Jednym z prostrzych sposobów zbudowania aplikacji nieblokującej działania podczas wykonywania poleceń INDY jest wykorzystanie komponentu
IdThreadComponent. Dzięki niemu usykamy efekt wątkowości. Ogólny sposób użycia prezentuje poniższy schemat:
procedure TForm1.btnConnect1Click(Sender: TObject);
begin
//uruchomienie wątku
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent1Run(Sender: TIdThreadComponent);
begin
//wykonywany kod w wątku
{
…np.: funkcje pobierania, wysyłania pliku IdFTP1.Get czy IdFTP1Put(),
}
//zatrzymanie wątku po wykonaniu kodu
IdThreadComponent1.Stop;
end;
2.6.1. Przykład użycia w powiązaniu z pakietem TCP
procedure T Form1.Button1Click(Sender: TObject);
begin
//wątek dla wysyłania wiadomości ...
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent1Run(Sender: TIdCustomThreadComponent);
begin
IdTCPClient1.Host :=label1.Caption;
if (richedit1.Lines.Text <> '') and (label1.Caption <> '') then
begin
if not IdTCPClient1.Connected then
begin
try
IdTCPClient1.Connect(60);
if IdTCPClient1.Connected then
begin
IdTCPClient1.Write(richedit1.Lines.Text);
IdTCPClient1.Disconnect;
end;
except
on exception do
begin
Memo1.Lines.Add('Wysłanie wiadomości do: '
+ label1.Caption + ' nie jest możliwe !!!');
end;
end;
end;
//zatrzymanie wątku
IdThreadComponent1.Stop;
end;
W przypadku wystąpienia wyjątku zastosowano zapis stosownej informacji do kontrolki Memo, nie jest zalecane stosowanie okien powiadomień
(np.: Showmessage() w połączeniu wątkiem.
2.6.2. Przykład użycia w powiązaniu z pakietem UDP
procedure T Form1.Button1Click(Sender: TObject);
begin
//wątek dla wysyłania wiadomości ...
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent1Run(Sender: TIdCustomThreadComponent);
begin
Id UDPClient1.Host :=label1.Caption;
if (richedit1.Lines.Text <> '') and (label1.Caption <> '') then
begin
try
IdUDPClient1. Send(richedit1.Lines.Text);
except
on exception do
begin
Memo1.Lines.Add('Wysłanie wiadomości do: '
+ label1.Caption + ' nie jest możliwe !!!');
end;
end;
//zatrzymanie wątku
IdThreadComponent1.Stop;
end;
Wykorzystanie wątku za pomocą komponentu ThreadComponent w odniesieniu do pakietu UDP jest analogiczne jak w przypadku TCP,
podobne rozwiązania stosuje się w odniesieniu do innych komponentów INDY.
2.6.3. Przykład użycia w powiązaniu z pakietem FTP
Wysyłanie pliku (IdFTP1.Put()):
procedure TForm1.Button1Click(Sender: TObject);
begin
//uruchomienie wątku
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent1Run(Sender: TIdCustomThreadComponent);
var
kontrolka : Integer;
File_lokalny : String;
localfile : string;
remotefile : string;
begin
label1.Visible := true;
label1.Caption := '';
Kontrolka:=ListView1.SelCount;
try
// jezeli puste pole to index = 0, jezeli pozycja wybrana to index = 1
if kontrolka <> 0 then
begin
File_lokalny := ListView1.Selected.Caption;
if directoryexists(Edit_kat_lokalny.Text + File_lokalny + '\') then
begin
Memo1.Lines.Add('Przesyłanie katalogu nie jest możliwe !!!');
end;
if fileexists(Edit_kat_lokalny.Text + File_lokalny) then
begin
if IdFTP1.Connected then
begin
IdFTP1.TransferType := ftBinary;
localfile := Edit_kat_lokalny.Text + File_lokalny;
remotefile := File_lokalny;
label1.Caption := 'Wysyłanie ...';
IdFTP1.Put(LocalFile, RemoteFile);
end;
end;
end;
except
on exception do
begin
Memo1.Lines.Add('Błąd podczas wysyłania pliku !!!'
+ #13 + 'Nie można nawiązać połączenia FTP !!!'
+ #13 + 'Adres: ' + IdFTP1.Host + ' nieodpowiada.');
end;
end;
label1.Caption := '';
label1.Visible := false;
//zatrzymanie wątku
IdThreadComponent1.Stop;
end;
Pobieranie pliku (IdFTP1.Get()):
procedure TForm1.Button1Click(Sender: TObject);
begin
//uruchomienie wątku
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent3Run(Sender: TIdCustomThreadComponent);
var
kontrolka : Integer;
File_zdalny : String;
test : string;
localfile : string;
remotefile : string;
begin
label1.Visible := true;
label1.Caption := '';
Kontrolka:=ListView1.SelCount;
try
// jezeli puste pole to index = 0, jezeli pozycja wybrana to index = 1
if kontrolka <> 0 then
begin
File_zdalny := ListView1.Selected.Caption;
test := ListView1.Selected.SubItems[0];
if test = 'Dir' then
begin
showmessage('Pobranie katalogu nie jest możliwe !!!');
end;
if test <> 'Dir' then
begin
if IdFTP1.Connected then
begin
IdFTP1.TransferType := ftBinary;
localfile := Edit_kat_lokalny.Text + File_zdalny;
remotefile := File_zdalny;
if fileexists(Edit_kat_lokalny.Text + File_zdalny) then
begin
Memo1.Lines.Add('Pobieranie przerwane !!!'
+ #13 + 'Pobierany plik istnieje już na dysku lokalnym !!!');
end;
if not fileexists(Edit_kat_lokalny.Text + File_zdalny) then
begin
label1.Caption := 'Pobieranie ...';
IdFTP1.get(RemoteFile, LocalFile);
end;
end;
end;
end;
except
on exception do
begin
Memo1.Lines.Add('Błąd podczas pobierania pliku !!!'
+ #13 + 'Nie można nawiązać połączenia FTP !!!'
+ #13 + 'Adres: ' + IdFTP1.Host + ' nieodpowiada.');
end;
end;
label1.Caption := '';
label1.Visible := true;
//zatrzymanie wątku
IdThreadComponent1.Stop;
end;
Odwołanie się do komórki ListView1 i sprawdzenie czy nie zawiera ona tekstu „Dir” oznaczającego katalog musi być poprzedzona wypełnieniem
kontrolki ListView1 w momencie łączenia z serwerem. Przykładowy sposób pobrania danych z serwera oraz wypełnienia odpowiednimi wartościami
kontrolki ListView1 przedstawia poniższy kod:
procedure TForm1.Button1Click(Sender: TObject);
begin
//uruchomienie wątku
IdThreadComponent1.Start;
end;
procedure TForm1.IdThreadComponent1Run(Sender: TIdCustomThreadComponent);
var
kontrolka : integer;
begin
inherited;
kontrolka := 0;
label1.Visible := true;
label1.Caption := '';
if (Edit_adres.Text = '') then
begin
Memo1.Lines.Add('Podaj adres IP adresata w sieci TCP/IP !!!');
end;
if Edit_adres.Text = '127.0.0.1' then
begin
Memo1.Lines.Add('Połączenie z adresem 127.0.0.1 nie jest obsługiwane !!!');
end;
try
if (Edit_adres.Text <> '') and (Edit_adres.Text <> '127.0.0.1') and (kontrolka = 0) then
begin
IdFTP1.Username := 'test'; //nazwa użytkownika
IdFTP1.Password := 'testowe'; //hasło użytkownika
IdFTP1.Host := Edit_adres.Text; //adres serwera
if not IdFTP1.Connected then
begin
label1.Caption := 'Łączenie ...';
IdFTP1.Connect();
if IdFTP1.Connected then
begin
ListView1.Clear; //wyczyszczenie kontrolki ListView1
//ustawienie głównego katalogu wystawianego przez serwer
Form1.kat_zdalny := IdFTP1.RetrieveCurrentDir;
Form1.kat_zdalny_bezwzgledny := IdFTP1.RetrieveCurrentDir;
edit_kat_zdalny.Text :=Form1.kat_zdalny;
//wywołanie procedury wypełeniącej LsitView1
ListView1();
kontrolka := 1;
end;
end;
end;
except
on exception do
begin
//Wypisanie na kontrolce ListView1 informacji o niepowodzeniu połączenia
ListView1.Canvas.Font.Size := 10;
ListView1.Canvas.Font.Color := clRed;
ListView1.Canvas.TextOut(15, 30, 'Nie można nawiązać połączenia');
ListView1.Canvas.TextOut(15, 50, 'z adresem: ' + IdFTP1.Host);
end;
end;
//zatrzymanie wątka
IdThreadComponent1.Stop;
Label1.Visible := false;
end;
procedure TForm1.ListView1 ();
Var
LS: TStringList;
ind : integer;
List_zdalny : TListItem;
test_kat : string;
plik_nazwa : string;
plik_wielk : string;
plik_data : string;
begin
LS := TStringList.Create;
try
IdFTP1.ChangeDir(Form1.kat_zdalny);
IdFTP1.TransferType := ftASCII;
ListBox1.Items.Clear;
IdFTP1.List(LS);
LS.Sort;
LS.Capacity;
ListBox1.Items.Assign(LS);
ind := 0;
ListBox1.Selected[ind] := true;
ListView_zdalny.Clear;
while ind + 1 <= LS.Count do // rob dopoki liczba zbalezionych plikow nie wyjdzie
//poza zakres indeksu
begin
test_kat := copy(ListBox1.Items.Strings[ind], 24, 5);
plik_nazwa := copy(ListBox1.Items.Strings[ind], 40,
length(ListBox1.Items.ValueFromIndex[ind]));
plik_wielk := copy(ListBox1.Items.Strings[ind], 28, 10);
while pos(' ', plik_wielk) > 0 do
begin
delete(plik_wielk, 1, pos(' ', plik_wielk));
end;
plik_data := copy(ListBox1.Items.Strings[ind], 1, 23);
//jeżeli główna ścieżka
if Form1.kat_zdalny = '\' then
begin
//jeżeli katalog
if (test_kat = '<DIR>') and (plik_nazwa <> '.') and (plik_nazwa <> '..') then
begin
List_zdalny := ListView1.Items.Add; // stworzenie nowej pozycji
List_zdalny.Caption := '[' + plik_nazwa + ']';
List_zdalny.SubItems.Add('Dir');
end;
//jeżeli plik
if test_kat <> '<DIR>' then
begin
List_zdalny := ListView1.Items.Add; // stworzenie nowej pozycji
List_zdalny.Caption := plik_nazwa;
List_zdalny.SubItems.Add(plik_wielk);
List_zdalny.SubItems.Add(plik_data);
end;
end;
//jeżeli ścieżka podrzędna
if Form1.kat_zdalny <> '\' then
begin
//jeżeli katalog
if (test_kat = '<DIR>') and (plik_nazwa <> '.') then
begin
List_zdalny := ListView1.Items.Add; // stworzenie nowej pozycji
List_zdalny.Caption := '[' + plik_nazwa + ']';
List_zdalny.SubItems.Add('Dir');
end;
//jeżeli plik
if test_kat <> '<DIR>' then
begin
List_zdalny := ListView1.Items.Add; // stworzenie nowej pozycji
List_zdalny.Caption := plik_nazwa;
List_zdalny.SubItems.Add(plik_wielk);
List_zdalny.SubItems.Add(plik_data);
end;
end;
ind := ind +1;
if ind + 1 > LS.Count then
begin
break;
end;
ListBox1.Selected[ind] := true;
end;
finally
LS.Free;
end;
end;
2.7. PING - komponent IdIcmpClient (Internet Control Message Protocol)
|
ICMP (Internet Control Message Protokol) jest drugim protokołem (podstawowym jest IP) warstwy Internetu i jest on ścicśle związany
z protkołwem IP.Jego zadaniem jest przesyłanie komunikatów o nieprawidłowościach w pracy sieci. Pozwala on przesyłać wiadomości
sterujące, które dotyczyć mogą: przepływu, testowania połączeń, wskazywania alternatywnych połączeń oraz wykrywania niedostępnych użytkowników.
|
Zastosowanie komponentu IdIcmpClient umożliwia pozyskać informacje zwiazane z funkcjonowaniem urządzeń w sieci np.: router’ów, stacji
roboczych. Przesłanie polecenia ping i odebranie informacji umożliwia dokonać w/w kontroli. Za pomocą kontrolki IdIcmpClient pozyskać
można również dodatkowe informacje związane z ilością przesyłanych bajtów, czasem życia, czasem oczekiwania na odpowiedź.
Sposób użycia komponentu jest stosunkowo prosty. W aplikacji pisanej w Delphi należy umieścić na formie pole Edit (posłuży do
wprowadzania adresu IP), przycisku Button (uruchomi wysyłanie pinga), komponentu Memo (zapisywane będą do komponentu informacje
zwrotne). Ostatnim komponentem jaki umieszczamy na formie jest IdIcmpClient.
W obsłudze zdarzenia OnCreate formy dodajemy:
procedure TForm1.FormCreate(Sender: TObject);
begin
//wyczyszczenie pola Edit
Edit1.Text := '';
//wyczyszczenie komponentu Memo
Memo1.Clear;
end;
W obsłudze zdarzenia OnClick przycisku Button dodajemy:
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer;
begin
//ustawiamy czas oczekiwania
IdIcmpClient1.ReceiveTimeout := 1000;
//wyłączamy dostępność przycisku button
button1.Enabled := False;
try
//ustawiamy adres IP
IdIcmpClient1.Host := Edit1.Text;
//wykonujemy pętlę 20 razy
for i := 1 to 20 do begin
//wysyłamy polecenie Ping
IdIcmpClient1.Ping;
//pozwalamy odetchnąć systemowi – obsłużyć komunikaty Windows
Application.ProcessMessages;
//dodatkowo można wykonać opóźnienie – w tym przypadku 1 sekundy
sleep(1000);
end;
finally
//finalizując włączamy dostępność przycisku button
button1.Enabled := True;
end;
end;
W obsłudze zdarzenia OnReply komponentu IdIcmpClient dodajemy:
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
var
sTime: string; //nowa zmienna typu string
begin
//sprawdzanie błedów odpowiedzi ping'a (AReplyStatus.MsgType?)
if (AReplyStatus.MsRoundTripTime = 0) then
sTime := '<1' //ustawienie wartości zmiennej dla wartości mniejszych niż 1
else
sTime := '='; //ustawienie wartości zmiennej
//wypełnienie komponentu Memo odpowiednio: ilością przesłanych bajtów,
//kolejnym numerem ID, czasem życia, zmienną sTime, czasem oczekiwania na odpowiedź
Memo1.Lines.Add(Format('%d bytes from %s: Sequence ID=%d TTL=%d Time%s%d ms',
[AReplyStatus.BytesReceived,
AReplyStatus.FromIpAddress,
AReplyStatus.SequenceId,
AReplyStatus.TimeToLive,
sTime,
AReplyStatus.MsRoundTripTime]));
end;
Cały kod programu:
unit icmp_clent_unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdBaseComponent, IdComponent, IdRawBase, IdRawClient, IdIcmpClient,
StdCtrls;
type
TForm1 = class(TForm)
IdIcmpClient1: TIdIcmpClient;
Memo1: TMemo;
Button1: TButton;
Edit1: TEdit;
procedure IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Edit1.Text := '';
Memo1.Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer;
begin
IdIcmpClient1.ReceiveTimeout := 1000;
button1.Enabled := False;
try
IdIcmpClient1.Host := Edit1.Text;
for i := 1 to 20 do begin
IdIcmpClient1.Ping;
Application.ProcessMessages;
sleep(1000);
end;
finally
button1.Enabled := True;
end;
end;
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
var
sTime: string;
begin
//sprawdzanie błedów odpowiedzi ping'a (AReplyStatus.MsgType?)
if (AReplyStatus.MsRoundTripTime = 0) then
sTime := '<1'
else
sTime := '=';
Memo1.Lines.Add(Format('%d bytes from %s: Sequence ID=%d TTL=%d Time%s%d ms',
[AReplyStatus.BytesReceived,
AReplyStatus.FromIpAddress,
AReplyStatus.SequenceId,
AReplyStatus.TimeToLive,
sTime,
AReplyStatus.MsRoundTripTime]));
end;
end.
2.7.1. IdIcmpClient - przykład sprawdzający dostępność adresów IP w całym segmencie
unit ping_unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdRawBase, IdRawClient, IdIcmpClient, IdBaseComponent, IdComponent,
IdIPWatch, StdCtrls, ComCtrls, Menus;
type
TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
RichEdit1: TRichEdit;
IdIPWatch1: TIdIPWatch;
IdIcmpClient1: TIdIcmpClient;
lbl_local_ip: TLabel;
lbl_numer_ip: TLabel;
Edit2: TEdit;
Edit3: TEdit;
Button2: TButton;
Edit4: TEdit;
PopupMenu1: TPopupMenu;
zapiszlog1: TMenuItem;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Button3: TButton;
Edit5: TEdit;
Label5: TLabel;
wyczyzawarto1: TMenuItem;
CheckBox1: TCheckBox;
procedure wyczyzawarto1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure zapiszlog1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
sc_programu : string;
host, segment1, segment2, segment3, segment4 : string;
operacja : integer;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
//pobranie ścieżki dostępu do pliku exe naszej aplikacji
sc_programu:=ExtractFilePath(ParamStr(0));
//wyłaczenie historii komponentu IdIPWatch
IdIPWatch1.HistoryEnabled := false;
//ustawienie ścieżki dostępu do pliku z historią wraz z jego nazwą
IdIPWatch1.HistoryFilename := sc_programu + 'iphist.dat';
//pobranie adresu IP stacji roboczej
lbl_local_ip.Caption := IdIPWatch1.LocalIP;
lbl_numer_ip.Caption := '';
Edit1.Text := IdIPWatch1.LocalIP;
Edit2.Text := '1';
Edit3.Text := '254';
Edit4.Text := '2';
Edit5.Text := '1000';
//zwolnienie komponentu IdIPWatch
IdIPWatch1.Free;
RichEdit1.Clear;
RichEdit1.PlainText := true;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i, j : integer;
begin
if Edit1.Text <> '' then
begin
host := Edit1.Text;
operacja := 1;
segment1 := copy(host, 1, (Pos('.', host)) - 1);
delete(host, 1, (Pos('.', host)));
segment2 := copy(host, 1, (Pos('.', host)) - 1);
delete(host, 1, (Pos('.', host)));
segment3 := copy(host, 1, (Pos('.', host)) - 1);
delete(host, 1, (Pos('.', host)));
segment4 := host;
//Showmessage('Segment: ' + segment1 + '-' + segment2 + '-' + segment3);
Richedit1.SelAttributes.Color := clRed;
Richedit1.SelAttributes.style := [fsBold];
RichEdit1.Lines.Add('Start ... segment: ' + segment1 + '-' + segment2 + '-'
+ segment3);
Button1.Enabled := false;
IdIcmpClient1.ReceiveTimeout := strtoint(Edit5.Text);
for j:=strtoint(Edit2.Text) to strtoint(Edit3.Text) do
begin
if operacja = 0 then
begin
break;
end;
for i:= 1 to strtoint(edit4.Text) do
begin
if operacja = 0 then
begin
break;
end;
IdIcmpClient1.Host := segment1 + '.' + segment2 + '.' + segment3 + '.'
+ inttostr(j);
lbl_numer_ip.Caption := 'Sprawdzam: ' + IdIcmpClient1.Host;
Application.ProcessMessages;
try
IdIcmpClient1.Ping;
Except
//
end;
Application.ProcessMessages;
sleep(100);
end;
end;
Button1.Enabled := true;
Richedit1.SelAttributes.Color := clRed;
Richedit1.SelAttributes.style := [fsBold];
RichEdit1.Lines.Add('... stop !');
end;
end;
procedure TForm1.IdIcmpClient1Reply(ASender: TComponent;
const AReplyStatus: TReplyStatus);
var
sTime : string;
begin
if (AReplyStatus.MsRoundTripTime = 0) then
sTime := '<1' //ustawienie wartości zmiennej dla wartości mniejszych niż 1
else
sTime := '='; //ustawienie wartości zmiennej
if (AReplyStatus.MsRoundTripTime >= 1000) and (not CheckBox1.Checked) then
begin
Richedit1.SelAttributes.Color := clBlack;
Richedit1.SelAttributes.style := [];
RichEdit1.Lines.Add(IdIcmpClient1.Host + ' - time out (' +
inttostr(AReplyStatus.MsRoundTripTime) + ')');
end;
if (AReplyStatus.MsRoundTripTime < 1000) then
begin
Richedit1.SelAttributes.Color := clNavy;
Richedit1.SelAttributes.style := [fsBold];
RichEdit1.Lines.Add(Format('%d bytes from %s: Sequence ID=%d TTL=%d Time%s%d ms',
[AReplyStatus.BytesReceived,
AReplyStatus.FromIpAddress,
AReplyStatus.SequenceId,
AReplyStatus.TimeToLive,
sTime,
AReplyStatus.MsRoundTripTime]));
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
operacja := 0;
end;
procedure TForm1.zapiszlog1Click(Sender: TObject);
begin
RichEdit1.Lines.SaveToFile(sc_programu + segment1 + '-' + segment2 + '-' + segment3
+ '.txt');
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
operacja := 0;
Application.ProcessMessages;
lbl_numer_ip.Caption := 'Kończenie działania programu ...';
Application.ProcessMessages;
close;
end;
procedure TForm1.wyczyzawarto1Click(Sender: TObject);
begin
RichEdit1.Clear;
end;
end.
2.8. Komponent IdIPWatch
Komponent IdIPWatch pozwala w prosty sposób pobrać adres IP zestawu na którym uruchamiamy naszą aplikację. Po umieszczeniu na formie komponentu
IdIPWatch należy obsłużyć np.: zdarzenie OnCreate formy dodając w niej funkcje obsługi koponentu IdIPWatch jak przedstawia poniższy przykład:
var
sc_programu : string; //nowa zmienna typu string
local_ip : string; //nowa zmienna typu string
begin
//pobranie ścieżki dostępu do pliku exe naszej aplikacji
sc_programu:=ExtractFilePath(ParamStr(0));
//wyłaczenie historii komponentu IdIPWatch
IdIPWatch1.HistoryEnabled := false;
//ustawienie ścieżki dostępu do pliku z historią wraz z jego nazwą
IdIPWatch1.HistoryFilename := sc_programu + 'iphist.dat';
//pobranie adresu IP stacji roboczej
local_ip := IdIPWatch1.LocalIP;
//zwolnienie komponentu IdIPWatch
IdIPWatch1.Free;
end;
2.9. Komponent IdConnectionIntercept
W/w komponent pozwala nam uzyskać wiecej informacji na temat zestawionego połączenia a zwłaszcza podczas transmisji (pakiety przychodzące oraz
wychodzące). Na poniższym przykładzie widzimy prostą metodę jego zastosowania:
Przychodzące:
procedure Tklient_ftp_form.IdConnectionIntercept1Receive(
ASender: TIdConnectionIntercept; AStream: TStream);
var
Text: string;
StrStream: TStringStream;
begin
StrStream := TStringStream.Create('');
try
StrStream.CopyFrom(AStream, AStream.Size);
Text := Trim(StrStream.DataString);
log_intercept := Text;
RichEdit1.Lines.Add('<<- ' + Text);
finally
StrStream.Free;
end;
end;
Wychodzące:
procedure Tklient_ftp_form.IdConnectionIntercept1Send(
ASender: TIdConnectionIntercept; AStream: TStream);
var
Text: string;
StrStream: TStringStream;
begin
StrStream := TStringStream.Create('');
try
StrStream.CopyFrom(AStream, AStream.Size);
Text := Trim(StrStream.DataString);
if pos('PASS', Text) > 0 then
begin
RichEdit1.Lines.Add('->> Password ...');
end;
if pos('PASS', Text) = 0 then
begin
RichEdit1.Lines.Add('->> ' + Text);
end;
finally
StrStream.Free;
end;
end;