A Delphi alapvetően két fajta socket-komponenst biztosít az alkalmazás-fejlesztők számára, amik rendkívül egyszerűvé teszik kliens-szerver modellen alapuló programok létrehozását. Ezen komponensek lehetővé teszik két folyamat ill. gép egymáshoz kapcsolódását, valamint a felépült kapcsolaton keresztül adatok küldését ill. fogadását. Bár a készen kapott komponensek kizárólag TCP socketek létrehozására képesek, a Delphi-hez mellékelt VCL forráskód birtokában nem túl bonyolult feladat ezek átalakítása úgy, hogy igény esetén más típusú (pl. UDP) vagy akár teljesen más címcsaládhoz tartozó protokollok (pl. IPX, SPX) használatát is lehetővé tegyék.

A komponensek mindegyike a Windows Sockets interfészre épül, és sikeresen veszik le a fejlesztők válláról a socket API amúgy - főleg nem-blokkoló üzemmód esetén - meglehetősen nehézkes kezelésének feladatát. Bár a komponensek bizonyos nagyon speciális esetekre nincsenek felkészítve (pl. nem nyújtanak lehetőséget a socket opcióinak állítására a setsockopt()-on keresztül) az általuk biztosított megfelelő property-k felhasználásával gond nélkül meghívhatjuk magunk is bármelyik szükséges WinSock függvényt. Így a már említett kényelem mellett rugalmasságot is biztosítnak e komponensek, ami potenciális felhasználási körüket jelentősen kibővíti.

Mint arról már szó volt: a Delphi két fajta socket-komponenst bocsát rendelkezésünkre. Ezek egyike a TClientSocket komponens, ami - nevének megfelelően - a kommunikáció kliens oldali részének kezelésére hivatott - nevezetesen kapcsolat felépítésének kezdeményezésére egy meghatározott géppel ill. a megnyitott kapcsolaton keresztül adatok fogadására és küldésére.

Azt, hogy melyik géppel kívánunk kapcsolatot létesíteni két módon is megadhatjuk: amennyiben a gépet host-neve (pl. www.borland.com) alapján azonosítjuk úgy azt a TClientSocket Host property-jében helyezzük el, míg ha "csak" IP-címmel rendelkezünk, akkor ezt az adatot az Address proprety-be írjuk. Amennyiben a címet pl. felhasználótól kérjük be és így alaposabb vizsgálat hiányában nem tudjuk eldönteni, hogy az host-név vagy IP-cím -e, abban az esetben is a Host property-t használjuk.

A célgépen elérni kívánt szolgáltatást ill. annak port-számát pedig a Port ill. Service property-k segítségével határozhatjuk meg. Amennyiben a port-számot (pl. FTP esetén 21) szeretnénk megadni úgy az előbbi, míg ha a szolgáltatást nevével szeretnénk azonosítani (pl. "FTP") úgy az utóbbi property-t használjuk. Ez esetben a service névŽport szám konverzió a WinSock getservbyname() függvényének felhasználásával a WINDOWS könyvtárban található SERVICES fájl alapján történik.

A fenti property-párosok egyébként egymással összefüggésben vannak, azaz egyikük megváltoztatása a másikuk módosulását is maga után vonja, hiszen mindketten ugyanazt az adatot határozzák meg csak éppen különböző módon.

A ClientType property pedig a socket működési módjának meghatározására használható: nevezetesen annak megadására, hogy a komponens az adatátviteli (SendXXX ill. ReceiveXXX) műveleteket blokkoló vagy nem-blokkoló módon hajtsa -e végre. Amennyiben valamilyen speciális körülmény miatt blokkoló üzemmódban szeretnénk használni a komponenst, úgy a direkt átviteli metódusok (pl. ReceiveText, SendBuf, stb.) helyett erősen ajánlott a TWinSocketStream valamint a kapcsoló metódusok használata melyekkel könnyen elkerülhetővé válik, hogy egy, az átvitel során esetlegesen keletkezett hiba a teljes alkalmazás blokkolásához vezessen.

A kapcsolat felépítésének megkezdéséhez állítsuk a TClientSocket Active property-jét true-ra. A kapcsolat megkezdésének felépítése (a connect() WinSock függvény hívása) előtt - amennyiben megadtuk - úgy a komponens OnLookup() eseménye kerül végrehajtásra. Ebben az eseménykezelőben nyílik lehetőségünk pl. a socket bizonyos, a komponens által nem támogatott tulajdonságainak beállítására, a setsockopt() felhasználásával. Ekkor már nem módosíthatjuk a cél-állomást meghatározó paramétereket, de semmi akadálya sincs annak, hogy pl. a bind() függvény felhasználásával az automatikusan kiosztott helyett egy általunk meghatározott helyi port-címmel kérjük a kapcsolat felépítését.

Ezek után kerül sor az esetlegesen sztringgel meghatározott paraméterek, nevezetesen a host cím ill. a service név IP-címmé ill. port-számmá konvertálására, melynek sikeres végrehajtása után a komponens OnConnecting() eseménye kerül végrehajtásra. Ez az esemény tipikusan a felhasználó felé történő visszajelzésre ad lehetőséget, hiszen az esemény végrehajtását követő connect() WinSock művelet végrehajtása több 10 másodpercig is eltarthat.

Amennyiben a kapcsolat sikeresen felépült úgy a komponens az OnConnect() eseménykezelőt hívja meg, amelyikben a folyamat során először nyílik lehetőségünk a kapcsolat összes paraméterének (többek között pl. a végül is felhasznált helyi port-cím) lekérdezésére, valamint adatok fogadására ill. küldésére.

Közvetlenül a kapcsolat felépülése után egy OnWrite() esemény is generálódik, ami beindítja az átviteli ciklust. Szintén ugyanez történik, ha OnWrite()-ból vagy a program bármely más részéből a socketen keresztül küldésre került adat kiürül az átviteli pufferből. Fontos azonban megjegyezni, hogy az OnWrite() _nem feltétlenül_ periodikusan végrehajtásra kerülő esemény - amennyiben egy OnWrite() eseménykezelőben elmulasztottunk újabb adatokat küldeni, úgy ez az esemény legközelebb csak egy független, a program más részéből kezdeményezett adatküldés után fog újra végrehajtódni. Maguk az adatok küldésére - az adatátvitel jellegétől függően - számos metódus áll rendelkezésre: a SendText() sztringek, míg a SendBuf bináris jellegű adatok küldésére használható. Ez utóbbinál figyelni kell arra, hogy az átadni kívánt adatokat nem feltétlenül veszi is át elküldésre - ez az átviteli puffer telítettségétől függ és erről a függvény visszatérési értéke szolgáltat információt. Külön figyelmet érdemel a SendStream() metódus, mellyel az átviteli puffer méreténél nagyobb adathalmaz átvitele is egyetlen hívással a komponensre bízható, ami a továbbiakban automatikusan küldi az adatokat - amennyire az átviteli körülmények azt megengedik. Az utóbbi metódusnak átadott stream "felügyeleti jogát" a hívás után a komponens fogja ellátni, így arra a hívó alkalmazásban többet nem szabad hivatkozni - sőt diszpozálni sem szabad. Fontos megjegyezni, hogy a bizonyos WinSock implementációk normál körülmények között (MSG_OOB flag megadásának hiányában) kizárólag az átviteli puffer telítődésekor (ált. 8 kb) ill. CRLF karaktersorozat észlelésekor üríti azt (küldi el az adatokat), így amennyiben az utóbbi feltétel nem teljesül magunknak kell gondoskodnunk arról, hogy az OnWrite() eseménykezelőben telítsük az átviteli puffert. Ez egyébként már csak az átviteli teljesítmény maximalizálása miatt is előnyős eljárás.

Amennyiben a kapcsolaton keresztül adat érkezik úgy az OnRead() esemény-kezelő kerül végrehajtásra. Szintén ugyanez történik, ha ugyan újabb adat nem fut be, de az utolsónak végrehajtott OnRead() esemény valamely okból kifolyólag nem ürítette ki teljesen a bemeneti puffert. Így ez az esemény - szemben az OnWrite()-tal - mindaddig periodikusan végrehajtásra kerül, amíg a bemeneti puffer adatokat tartalmaz. (Ez alól csak egyetlen kivétel van - nevezetesen amikor a kapcsolatot a túloldal az előtt bontja, hogy a kliensnek alkalma lett volna fogadnia az összes átküldött adatot. Ekkor ugyanis - bár az átvitt adat a bementi pufferben van - nem generálódik újabb OnRead() esemény. Ez okból kifolyólag - amennyiben szükséges - az OnDisconnect() eseményben is meg kell vizsgálni, hogy nincs-e adat a bemeneti pufferben és ha van akkor azt teljes mértékben kiüríteni.)

A kapcsolat a kliens vagy a szerver oldalról kezdeményezett bontása esetén az OnDisconnect() esemény hajtódik végre, amelynek lefutása után a socket bezárásra (megsemmisítésre) kerül. Fontos megjegyezni, hogy a felhasznált adatterületek és egyéb erőforrások felszabadítása csakis a komponens megsemmisítése (Free v. Destroy metódus hívása) után történik meg, így dinamikus létrehozás esetén erre külön ügyeljünk.

Amennyiben a felépítési kísérlet vagy az adatátvitel során bármilyen hiba lép fel úgy az OnError() esemény-kezelő kerül végrehajtásra. Amennyiben nincs ilyen kezelő megadva, valamint ha a kezelő a paraméterként átadott és a WinSock hibakódot tartalmazó ErrorCode váltózót nem nullázza ki, úgy a komponens egy kivételt generál, amelyet nem-blokkoló mód esetén semmilyen kivétel-kezelő (pl. try…except blokk) sem tud "elkapni". Adatfolyam (stream) jellegű socketek esetén az alkalmazási szintre jutó hibák kizárólag fatális jellegűek lehetnek, amik a socket azonnali bezárását vonják maguk után - így OnError() eseménykezelőben nincs szükség a "kézi" bezárásra (sőt pl. egy Close metódus meghívása kivételt okoz).

A kapcsolat másik, szerver oldali végpontjának implementálását a TServerSocket komponens látja el, melynek feladata egy meghatározott porton érkező kapcsolat-felvételi kérések fogadása, azokhoz igény szerint újabb socketek nyitása, valamint az aktív kapcsolatok nyilvántartása.

Funkciójából adódóan a TServerSocket a TClientSocket-nél megismert tulajdonságok közül kizárólag a Port és Service valamint az Active nevű property-vel rendelkezik. Az előbbi két tulajdonság segítségével - a már fent megismert összefüggések szerint - annak megadására nyílik lehetőségünk, hogy a komponens mely porton figyelje és fogadja a bejövő kapcsolat-felvételi kéréseket (listen), míg utóbbi tulajdonsággal tudjuk a komponenst aktívvá tenni (azaz csakis akkor "figyel és fogad" a komponens, ha az Active property értéke true).

A kliens oldali komponenshez hasonlóan a TServerSocket is két féle működési móddal rendelkezik. Az alapértelmezett mód az aszinkron, tehát nem blokkoló üzemmód, mely a már fent megismert módon működik. Ezzel szemben szerver esetén nem lehet a klasszikus blokkoló üzemmódot alkalmazni, hiszen ez nem csak az aktív, hanem az összes kliens kiszolgálását is akadályozná, ami pl. nagy valószínűséggel folyamatosan lehetetlenné tenné újabb kliensek csatlakozását. Ennek áthidalására a TServerSocket komponens bevezeti az ún. szál-blokkoló (thread-blocking) üzemmód fogalmát, mely egy olyan technikát takar melynek során minden egyes beérkező kapcsolat egy-egy újabb szálon (thread) kezd el futni, amiben aztán tetszőleges blokkoló folyamatok hajthatók végre, hiszen azok mindegyike csakis a szóban forgó szálat fogja "feltartani", míg az összes többi szálnak (köztük az újabb kapcsolatokat fogadó szerverének is) szabad utat enged.

Közvetlenül a figyelő (listen) szerver socket megnyitása előtt (tehát az Active property true-ra állítása után) hajtódik végre a komponens OnListen() eseménye, melyben a kliens komponens esetében az OnLookup() eseményben végrehajtható műveletek elvégzésére nyílik lehetőségünk. Miután a komponens sikeres megnyitotta a figyelő socketet, aktívvá válik, és automatikusan fogadja a meghatározott portra érkező kapcsolat felvételi kéréseket. Egy újonnan fogadott kapcsolatról legelőször az OnAccept() esemény-kezelőben szerezhetünk tudomást, amikor is azonban még nem dolgozhatunk azzal, csak kizárólag az OnLookup() ill. OnListen() esemény-kezelőkben is elvégezhető "igazító" műveletek hajthatunk végre az újonnan létrehozott socketen. A kapcsolat tényleges megnyitásáról az OnClientConnect() eseménykezelő tájékoztat amelyben már "teljes jogú" sockettel van dolgunk.

Itt kicsit elágazik a végrehajtás módja a két műveleti mód esetén. Míg nem-blokkoló üzemmód esetén a már megszokott módon folynak tovább a műveletek, addig szál-blokkoló üzemmódban OnGetThread() esemény kerül meghívásra, amelyben az alkalmazásnak lehetősége nyílik egy saját, a TServerClientThread-ből származatott objektum-példány létrehozására, ami a továbbiakban a kliens végrehajtási szálát fogja alkotni. Amennyiben a szóban forgó eseménykezelő nincs megadva, vagy amennyiben az nem ad vissza egy ilyen szál-objektumot úgy a TServerSocket önmaga hoz létre egyet, és azon kezdi el futtatni a végrehajtást. Az új szál indításáról az OnThreadStart() míg később, a kapcsolat megszakadásakori leállásáról az OnThreadEnd() esemény-kezelőkben szerezhetünk tudomást.

Az OnClientRead(), OnClientWrite() és OnClientDisconnect() események pedig értelemszerűen a kliens komponensnél már megismert helyzetekben generálódnak.

A TServerSocket aktuális kapcsolatait TServerWinSocket típusú Socket property-jének Connections property-jén, pontosabban annak metódusain és property-n keresztül tudjuk kezelni ill. azokról információkat lekérdezni.

A CD-n megtaláljátok egy egyszerű CHAT program forráskódját is. A program futtatásához telepített TCP/IP protokoll, míg fordításához legalább 3-as Delphi-re szükséges, méghozzá a Client/Server változatban.