Bevezetés
Manapság nagyon sok alkalmazás használ hálózati kapcsolatot, ami lehet internetes is. Ebben a részben a hálózati programozás alapjairól esik szó. Sok gyakorlati alkalmazásban pont erre van szükség. Csak a teljesség kedvéért kerül említésre, hogy ezen kívül, természetesen, a hálózati kapcsolatot nem csak elemi szinten lehet használni. A Delphi támogatást nyújt különböző típusú alkalmazások készítésére, amelyek magas szintről támaszkodnak az internetes programozásra, elrejtve a programozók elől az elemi műveleteket. Ilyen pl. a többrétegű adatbázisos alkalmazások (Multi-tired DataSnap) vagy a web alkalmazások készítésének támogatása. De ezek leírása nem része ennek a fejezetnek.
A komponens családok
Az alacsonyabb szintű internetes programozás megvalósításához a Delphi két komponens családot kínál: Internet és Indy. Az előbbi a Delphi fejlesztőjének saját komponenscsaládja, az utóbbi egy nyílt forráskódú komponenscsalád. Már hosszú ideje (több mint tíz éve) a szoftverfejlesztők részére hivatalosan az Indy komponens család a javasolt. Ennek ellenére az Internet család is megtalálható a legújabb változatban is (Delphi XE4), ráadásul FireMonkey alól is elérhető. De mivel az Indy család fejlettebb, több komponenst tartalmaz, na meg a hivatalos javaslatot követve, ebben a részben az Indy komponenscsaláddal foglalkozunk. Fokozott figyelmet kell fordítani viszont a család verziószámára, mert az újabb verziók messze nem mindig kompatibilisek visszafele.
A legalacsonyabb szint: a foglalatok (socket)
Két alkalmazás közötti adatcserét a legalacsonyabb szinten az úgynevezett foglalatok segítségével lehet megoldani. A foglalatok arra adnak lehetőséget, hogy két (vagy több) alkalmazás tetszőeleges adatcsomagokat (bájtsorozatot) küldjön egymásnak. Az Internet kifejezetten adatátvitelre két, az IP protokollra épülő, alapprotokollt használ, melyek a TCP és az UDP. Eme protokollok leírása természetesen nem része ennek a fejezetnek. Csak annyit említenénk meg, hogy az előbbi kapcsolat alapú, az utóbbi nem. Ez annyit jelent, hogy ha két végpont között a TCP kapcsolat fennáll, akkor a küldött adatcsomagok garantáltan célba érnek, ráadásul több független csomag esetén, a megfelelő sorrendben (ami ha belegondolunk, nem evidens). Ezzel szemben az UDP hasonló garanciákat nem nyújt, viszont gyorsabb és csak rajta keresztül van lehetőség egyszerre több címzettnek szóló, úgynevezett broadcast üzenetek küldésére.
UDP
Az UDP-n keresztüli adatküldés az Indy komponenscsaládban két fő komponens által kerül megvalósításra: TIdUDPServer és TIdUDPClient. Az adatok fogadása a TIdUDPServer komponens segítségével történik. Ennek a komponensnek a főbb jellemzői:
- Active
- Bindings
- BroadcastEnabled
- BufferSize
- IPVersion
A
Bindings jellemző szerkesztőjével megadhatjuk azokat az IP címeket és portokat, amelyre címzett üzeneteket vételezni szeretnénk (valójában, amennyi ilyet beállítunk, annyi
TIdUDPListenerThread objektum lesz csatolva a Server komponenshez, de ez rejtve marad a programozó elől). A
BroadcastEnabled jellemző Igaz vagy Hamis értékre való állítással engedélyezzük vagy tiltjuk a broadcast üzenetek fogadását. A
BufferSize-zal meghatározzuk a vételi puffer méretét (ennél hosszabb csomagot nem fogadhatunk), az
IPVersion jellemzővel pedig megadjuk, hogy IPv4 vagy IPv6 címzést használunk. Ha mindez megtörtént, akkor az
Active jellemző Igaz állapotba történő helyezését követően, a komponens elkezdi "figyelni" az érdekelt címre és portra érkező csomagokat. Ha egy csomag sikeres vétele megtörténik, akkor meghívódik az
OnUDPRead eseményhez rendelt rutin, amelyen paraméterként megkapjuk az adatot vételező
TIdUDPListenerThread (lásd feljebb) objektumot, hogy beazonosíthassuk, kitől jött a csomag, valamint egy bájttömb formájában magát az üzenet adathalmazát.
Az adatok küldése a TIdUDPClient komponens segítségével történik. Ennek a komponensnek a főbb jellemzői:
- Active
- BroadcastEnabled
- BufferSize
- Host
- IPVersion
- Port
A
Host és
Port jellemzők beállításával meghatározzuk a távoli végpont címét és portját, amelyre az üzenetet küldeni szeretnénk. A
BroadcastEnabled jellemző Igaz vagy Hamis értékre való állítással engedélyezzük vagy tiltjuk a broadcast üzenetek küldését. A
BufferSize-zal meghatározzuk az adás puffer méretét (ennél hosszabb csomagot nem küldhetünk), az
IPVersion jellemzővel pedig megadjuk, hogy IPv4 vagy IPv6 címzést használunk. Ha mindez megtörtént, akkor az
Active jellemző Igaz állapotba történő helyezését követően, a komponens készen áll adatok küldésére. Adatküldésre több metódus is rendelkezésre áll. Ilyen pl. a
SendBuffer, amely paraméterként egy
TIdData típust vár, ami lényegében egy bájttömb. A bájttömbben lévő adatok kerülnek továbbításra a komponens
Host és
Port jellemezőiben feltüntetett végpontra.
Csak a teljesség kedvéért megemlítjük, hogy ezen kívül, a
TIdUDPClient őse több UDP protokollon működő szolgáltatást magasabb szinten támogató komponensnek. Ilyenek pl. a
TIdDNSResolver (DNS = Domain Name Server), amely a névfeloldások kezelésére hivatott, a
TIdSNTP (SNTP = Simple Network Time Protocol), amely az Interneten keresztüli idő szinkront teszi lehetővé (1 – 50 ms pontossággal) és a
TIdTrivialFTP (TrivialFTP = Trivial File Transfer Protocol), amely egy egyszerűsített fájlcserélőt valósít meg.
TCP/IP
A TCP/IP-n keresztüli adatküldés az Indy komponenscsaládban két fő komponens által kerül megvalósításra: TIdTCPServer és TIdTCPClient. Az adatküldési mechanizmus a következő. Először a szerver komponensnek kell aktiválódnia és elkezdeni a figyelést (listening). Ezt követően aktiválódhat a kliens, mely első lépésben csatlakozik a szerverre. Ezzel létrejön a kapcsolat. Ezt követően minkét fél teljesen függetlenül tud egymásnak üzeneteket küldeni. Végül, a tevékenység befejeztével, bontani kell a kapcsolatot. A kapcsolat bontását bármelyik fél kezdeményezheti, de általában ez a kliens feladata. A kapcsolat magától is megszakadhat egy előre nem látott esemény miatt (pl. adatátviteli hiba lép fel). A komponenseket használó programnak korrektül kell kezelnie ezt az esetet.
A TIdTCPServer komponens főbb jellemzői:
- Active
- Bindings
- DefaultPort
Alapból a szerver a
DefaultPort jellemző által megadott porton "figyel", de a
Bindings jellemző segítségével megadható egyszerre több IP cím és port amelyre a szerver várja a csatlakozást. Ha ezeket beállítottuk és az
Active jellemzőt Igaz állapotba helyeztük, akkor teljesítettük is az első lépést. Elkezdődött a figyelés.
A
TIdTCPClient komponens főbb jellemzői a
Host és a
Port. Itt állítjuk be a távoli végponton lévő szerver címét (persze lehet gépnév is, ha működik a névfeloldás) és portját, majd a
Connect metódus meghívásával kezdeményezzük a kapcsolat létrehozását. Kicsit "előre szaladva" itt említenénk meg, hogy a kapcsolat bontását a
Disconnect metódus meghívásával kezdeményezhetjük. A kapcsolat létrejöttéről / megszűnéséről az
OnConnected /
OnDisonnected eseményekből értesülhetünk. Adatok írása / olvasása a
TIdTCPClient.IOHandler metódusainak meghívásával lehetséges. Az
IOHandler itt egy
TIdIOHandler típusú osztály, amely számos adatküldő és fogadó metódussal rendelkezik. Így pl. bájtsorozatot a
Write metódussal küldhetünk, szöveges üzenetet a
WriteLn metódussal, stb.
A fent leírt események sorozata a már aktív szerveren a következőképen játszódik le. Ha egy klienssel létrejött a kapcsolat, akkor lefut az
OnConnect eseményhez rendelt rutin. Ebben paraméterként kapunk egy
TIdContext típusú objektumot. Ez az objektum egy kapcsolatot reprezentál. Annyi ilyen objektum van, ahány kliens csatlakozott az adott szerverre. Ez az osztály rendelkezik egy
Connection jellemzővel, ami egy
TIdTCPConnection típusú objektum. Ez az objektum pedig rendelkezik egy
TIdIOHandler típusú
IOHandler jellemzővel, amelyet nyilván ugyanúgy kell kezelni, mint a kliens esetében. Egy kapcsolat megszűnéséről az
OnDisconnect eseményből értesülhetünk.
A szerverhez pillanatnyilag kapcsolodó kliensek listája bármikor lekérhető a
TIdTCPServer.Contexts jellemzőből. Ha pl. valamennyi csatlakoztatott kliens számára szeretnénk elküldeni a "Hello World" üzenetet, akkor azt valahogy így tehetnénk meg:
...
var
cList: TList;
i: Integer;
...
begin
cList := Server.Contexts.LockList;
try
for i := 0 to cList.Count – 1 do begin
with TIdContext(cList[i]) do begin
Connection.IOHandler.WriteLn('Hello World!');
end;
end;
finally
Server.Contexts.UnlockList;
end;
end;
...
Levelezés
Az elektronikus levelezés az internetes tevékenység egyik legközismertebb formája. Nem valószínű, hogy sokan állnának neki saját levelező programot írni (bár ha belegondolunk, a meglévő programokat is egyszer meg kellet írni), de ennek ellenére több gyakorlati alkalmazásban is szükség lehet a levelek küldésére / fogadására. Ilyen pl. ha elektronikus levelet automatikusan generáló és küldő programot szeretnénk írni. Ennek egyik gyakorlati esete az lehet, ha azt szeretnénk, hogy a programunk, egy esetleges előre nem látott kivétel bekövetkezéséről, e-mail-ben tájékoztassa a szoftver fejlesztőjét. Ezt elsősorban belső, vagy speciális alkalmazások esetén szokták használni, de könnyen elképzelhető sok felhasználó által használt programok esetén is (az már egy másik kérdés, hogy ha egy felhasználó hozzájárul ahhoz, hogy egy program automatikusan küldjön levelet, akkor abban a levélben milyen információk lesznek valójában).
A levelezéssel kapcsolatban az Indy komponenscsaládban három főbb komponenst kell megemlíteni. A TIdMessage komponens reprezentál egy üzenetet, a TIdSMTP egy SMTP szerverhez való csatlakozást és az üzenet elküldését biztosítja, a TIdPOP3 pedig egy POP3 kiszolgálóhoz való csatlakozást biztosít, lehetővé téve a levelek letöltését vagy törlését az adott postaládából. Nézzük át ezeket a komponenseket részletesebben.
A TIdMessage komponens, ahogyan azt említettük, egy levelet reprezentál. Főbb jellemzői:
- From
- Recipients
- CCList
- BCList
- Subject
- Body
A
From, a levél eredeti szerzőjét tartalmazza. Ez a jellemző egy
TIdEMailAddressItem típusú objektum, melynek főbb jellemzői:
Address,
Name,
Text,
Domain,
User. A
Recipients a címzettek listáját tartalmazza, a
CCList a másolatot kapók listáját, a
BCList pedig a titkos másolatot kapók listáját. Mindhárom jellemző
TIdEMailAddressList típusú, ami nem más mint egy
TIdEMailAddressItem elemeket tartalmazó lista. A
Subject a levél tárgyát leíró sima string, míg a
Body a levél tartalmát tartalmazó string lista (
TStrings).
A levél csatolmányokat is tartalmazhat. Egy csatolmányt a
TIdAttachment osztály reprezentál. Ez egy absztrakt osztály, amely helyett egyik leszármazottját kell használnunk:
TIdAttachmentFile, ha a csatolni való adatállomány egy külső fájl vagy
TIdAttachmentMemory ha a csatolni kívánt adatállomány egy stream-ben vagy egy memória pufferben (bájtsorozat) található. A csatolmány hozzáadása a levélhez talán kissé szokatlan módon történik. A csatolmány létrehozásakor, a
Create metódusnak kell paraméterként megadni azt az üzenetet, amelyhez csatolni szeretnénk. Valahogy így:
IdAttachment := TIdAttachmentFile.Create(IdMessage.MessageParts, FileName);
Az
TIdSMTP komponens főbb jellemzői:
- Host
- Port
- UserName
- Password
A
Host az SMTP szerver IP címét vagy nevét tartalmazza. A
Port a kívánt portot (általában ez a 25-s port). Ha az SMTP szerver megkívánja az autentikációt, akkor azt a
UserName és
Password jellemzőkkel lehet megadni. Csatlakozni egy SMTP szerverre a
Connect metódus meghívásával lehet. Bontani a kapcsolatot a
Disconnect, üzenetet küldeni pedig a
Send metódussal lehet. Valahogy így:
...
IdSMTP.Connect;
try
IdSMTP.Send(IdMessage);
finally
IdSMTP.Disconnect;
end;
...
A
TIdPOP3 komponens főbb jellemzői, akárcsak a
TIdSMTP esetében, a
Host,
Port,
UserName és
Password. Jelentésük is megegyezik az előző esetben lévővel, ezért itt most külön nem részletezzük. Értelemszerűen, itt is csatlakozni a POP3 kiszolgálóra a
Connect, bontani a kapcsolatot pedig a
Disconnect metódussal kell. A postafiókkal kapcsolatos műveletekhez sok metódus áll rendelkezésre. Íme közülük néhány rövid magyarázattal:
- CheckMessage
- Visszaadja a postafiókban lévő levelek számát
- Retrive
- Letölti a megadott számú üzenetet a postaládából (ahogyan azt sejteni is lehet, egy TIdMessage típusú objektumot kapunk eredményképen)
- Delete
- Kijelöli a megadott számú levelet törlésre (a levél fizikai törlése a Disconnect meghívásakor fog megtörténni)
- Reset
- Megszünteti a kijelölést a törlésre jelölt leveleknél
Csak röviden említjük, hogy az
Indy család természetesen támogatja az IMAP4-et
(Internet Message Access Protocol version 4.1) is. Ennek is főbb jellemzői a
Host,
Port,
UserName és
Password. Értelemszerűen, csatlakozni a
Connect, bontani a kapcsolatot pedig a
Disconnect metódussal kell. Ezen kívül számos metódust tartalmaz postafiókok létrehozására, megszüntetésére, átnevezésére, levelek eltávolítására, új levelek észlelésére, stb.
A HTTP protokoll használata
A levelező protokollokhoz hasonlóan nagy, vagy talán még nagyobb, népszerűségnek örvend a webkiszolgálók és böngészők által használt HTTP protokoll. Ezek alacsony szintű kezeléséhez az Indy család a TIdHTTP és a TIdHTTPServer komponenseket kínálja. Az előbbi egy HTTP szerverhez való csatlakozást és annak adatainak letöltését teszi lehetővé, az utóbbi pedig egy saját HTTP szerver megírására add lehetőséget. A HTTP protokollon keresztül manapság nagyon sokféle adat kerül továbbításra. Természetesen a legtöbb esetben nem szükséges az adathalmaz bináris elemzése. Található számos komponens, amely jóval magasabb szinten kezel egy bizonyos típusú adathalmazt. Pl. külön komponensek állnak rendelkezésre azon esetek feldolgozására, ha a HTTP-n keresztülküldött adat egy HTML fájl (saját böngésző, vagy egy spéci HTML megjelenítő). Az anyag terjedelme miatt, ezekre itt most nem térünk ki. Csak röviden említjük az HTTP alacsonyabb szintű kezelését.
Ahogyan az fent említésre került, egy HTTP szerverhez csatlakozó kliensként a TIdHTTP komponenst használjuk. A HTTP szerver felé menő lekérdezést a Request jellemző beállításával határozzuk meg. Ez a jellemző egy TIdHTTPRequest típusú objektum, amely jellemzőjeként olyan paramétereket adhatunk meg, mint proxy, jelszó, Cookie-k értékének szöveges reprezentációja és még sok más. A lekérdezés elküldése pedig a Get metódus meghívásával történik. Mindezek tudatában, egy egyszerű HTTP kliens megírása már nem is tűnik olyan bonyolult feladatnak. A kód valahogy így néz ki:
...
var
IdHTTP: TIdHTTP;
StrRead: String;
StrUrl: String;
...
begin
...
IdHTTP := TIdHTTP.Create(nil);
try
IdHTTP.Request.UserAgent := 'User-Agent: NULL';
strRead := IdHTTP.Get(strUrl); // strUrl az adott URL
finally
IdHTTP.Free;
end;
...
// A HTTP szervertől visszajövő válasz az strRead változóban lesz.
Ha saját HTTP szervert szeretnénk készíteni, ahhoz a
TIdHTTPServer komponenst használhatjuk. A
DefaultPort jellemzővel adjuk meg, hogy a komponens melyik kapun figyeljen (a web általában a 80-s porton üzemel), majd az
Active jellemző Igaz állapotba tételével aktiváljuk a komponenst. Ezt követően az már képes kliensek csatlakozását fogadni. Ha pedig az utóbbi küld egy lekérdezést, akkor lefut az
OnCommandGet eseményhez rendelt rutin, ahol paraméterként kapjuk meg a szükséges adatokat. További magyarázat helyett itt egy egyszerű példa:
procedure TForm1.IdHTTPServer1CommandGet(
AThread: TIdPeerThread;
RequestInfo: TIdHTTPRequestInfo;
ResponseInfo: TIdHTTPResponseInfo
);
var
HtmlResult: String;
begin
HtmlResult := '<h1>HttpServ Demo</h1>' +
'<p>This is the only page you''ll get from this example.</p><hr>' +
'<p>Request: ' + RequestInfo.Document + '</p>' +
'<p>Host: ' + RequestInfo.Host + '</p>' +
'<p>Params: ' + RequestInfo.UnparsedParams + '</p>' +
'<p>The headers of the request follow: <br>' +
RequestInfo.RawHeaders.Text + '</p>';
ResponseInfo.ContentText := HtmlResult;
end;
Az
Indy komponens család lehetőséget ad viszonylag könnyű szerrel elkészíteni egy egyszerű saját HTTP Proxy-t is. Ehhez a
TIdHTTPProxy komponenst kell használni, de ennek részletesebb leírására itt már nem térünk ki.
Végszó
A fent leírtakon kívül, az Indy komponens család még számos egyéb komponenst tartalmaz. Mindezekről részletes leírás és példaprogramok az Indy honlapjáról tölthetők le. A fent leírtak még valószínűleg nem adnak elegendő információt egy teljes értékű program megírására. A helyett inkább egy bevezetést adnak az Indy világába és egy irányvonalat határoznak meg annak érdekében, hogy egy egyszerűbb alkalmazás megírása könnyebb feladat legyen.
Ezen kívül egy működőképes hálózati alkalmazás megírásához mindenképpen szükség van a programozás egyéb területeiről szóló információhoz. Így például a terjedelem korlátja miatt sehol nem kerül említésre, hogy az Indy komponensek minden esetben úgynevezett blokkoló adatátvitelt alkalmaznak. Tehát az alkalmazásunk alapból többszálas lesz (bár a foglalatok leírásakor említésre került, hogy mindegyik kapcsolat egy külön szálat fog képviselni). Tehát szükség van a többszálas alkalmazások fejlesztéséhez tartozó ismeretekre. A buktatók elkerülése érdekében, alkalmazás fejlesztésekor először még a triviálisnak tűnő esetekben is érdemes elolvasni és megérteni az adott komponenshez tartozó rövid leírást, amit a Delphi súgója is tartalmaz.