A Delphi programozási nyelv

Hálózati programozás

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:

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: 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:

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:

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: 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.