A Windows Sockets specifikáció - mint arról már korábban is szó volt - egy, a BSD socket koncepciójára alapuló általános hálózati programozási interfész. A specifikáció magában foglalja mind a BSD standard socket függvényeit, mind néhány egyéb, a Windows futtatási környezet specialitásait (pl. üzenet-vezérelt futás, többszálúság, stb.) kezelő ill. azokat kihasználhatóvá tevő kiegészítést.

A specifikáció célja egy olyan egységes API definiálása, amihez a különböző hálózati szoftver-gyártók igazodva lehetővé teszik az alkalmazás-írók számára programjaik a legkülönbözőbb hálózati környezetekben történő működésének lehetővé tételét. A standardizált procedurális csatolón túl a specifikáció egy bináris interfészt (ABI - Application Binary Interface) is definiál, amely lehetővé teszi a specifikációhoz illeszkedő alkalmazások számára, hogy bármely gyártótól származó, de a specifikációhoz illeszkedő hálózati protokollt azonos bináris kóddal érhessék el. (Ez gyakorlatilag azt jelenti, hogy a Windows Socket alkalmazások nem csak forrás-, hanem tárgy-kód szinten is kompatibilisek egymással, azaz maguk a futtatható állományok ugyanúgy működőképesek bármely a specifikációhoz illeszkedő hálózati környezetben.)

A WinSock 1.1 specifikáció architektúrájában az interfész implementációs részét teljes egészében a hálózati szoftver-gyártó, pontosabban az általa biztosított WINSOCK.DLL látja el, ami így lehetetlenné teszi a több, különböző gyártó által nyújtott socket-interfész elérését az alkalmazások számára. (Hiszen ehhez azoknak több különböző WINSOCK.DLL-t is el kellene egyszerre érniük, de a rendszerben nyilván csak egyetlen ilyen nevű .DLL lehet. Bár a probléma dinamikus importtal áthidalható, de ezzel éppen az alkalmazások környezet-függetlensége veszik el, hiszen ekkor valamilyen módon mindig tudatni kellene velük, hogy hány winsock.dll-t szeretnénk betölteni és hogy ezek hol találhatók, valamint az alkalmazás belső architektúráját is nagyban bonyolítaná ez a plusz, a különböző interfészek kezelését ellátó réteg.).

Ezzel szemben a WinSock 2 specifikációban az alkalmazásokhoz hasonlóan maguk a hálózati gyártók által készített protokoll-meghajtók is egy speciális, direkt e célra kialakított interfészen (SPI - Service Provider Interface) keresztül kapcsolódnak az immár az operációs rendszer által menedzselt központi WinSock managerhez (WSOCK32.DLL), ezáltal lehetővé téve akár több interfész párhuzamos használatát is. Az alkalmazások pedig ez utóbbi, a WinSock 1.1-gyel felülről teljesen kompatibilis interfésszel kommunikálva, számukra teljesen átlátszó módon érik el - akár párhuzamosan is - a különböző hálózati protokollokat.

A WinSock a standard socket-kezelő függvényeken kívül még néhány egyéb, a hálózati kommunikáció során nélkülözhetetlen függvényt is rendelkezésünkre bocsájt, melyek egy része a BSD-socketsben is megtalálható, míg mások kizárólag a WinSock sajátosságainak tekinthetők. Előbbi csoportba az ún. adatbázis-kezelő függvények tartoznak, míg utóbbiak ezek aszinkron párjai ill. a Windows és WinSock architektúrájához kötődő ill. abból adódó speciális funkciók. Az utóbbi csoport függvényei 'WSA'-val kezdődő nevük alapján könnyen megkülönböztethetők.

A WINSOCK programozása

Az interfész használatához az alkalmazásnak először is meg kell győződnie annak jelenlétéről. Fordítási idejű kapcsolás esetén a megfelelő WINSOCK.DLL automatikusan betöltődik, míg futás-idejű kapcsolás esetén az alkalmazás feladat az interfész-könyvtár betöltése a LoadLibrary() Windows függvény segítségével. Az interfész használatának megkezdése előtt azt inicializálni kell, amit az alkalmazás a WSAStartup() függvény meghívásával tehet meg. A függvény első paraméternek az általa minimálisan igényelt WinSock implementáció verziószámát, másodiknak pedig egy TWSAData típusú változót kell megadnia. Amennyiben az interfész inicializálása sikeres volt, úgy a függvény visszatérési értéke 0, míg egyéb esetben a problémára okára utaló hibakód. (A WinSock függvények által visszaadott hibakódok konstansai (WSAExxxx) megtalálhatók a winsock.h ill. winsock.pas interfész fájlokban, de az egyes funkciók esetén a kiváltó ok(ok)nak érdemes a win32.hlp-ben található WinSock referenciában utánanézni, mert ugyanaz a hibakód sok esetben funkciónként teljesen más jellegű problémára utal.). A nem hibakódot visszaadó függvények esetén az utolsó művelet eredményét a WSAGetLastError() segítségével lehet lekérdezni. A TWSAData rekord mezői és jelentésük a következők:
 

Mező neve
Típus
Jelentés
wVersion
word
Az interfész által az alkalmazás felé nyújtott WinSock interfész verziószáma. Ez az interfész által biztosított és az alkalmazás által megkövetelt verzió minimuma.
wHighVersion
word
Az interfész által támogatott legmagasabb WinSock interfész verziószáma.
szDescription
PChar
A WinSock implementációra utaló szöveges információ
szSystemStatus
PChar
Az interfész állapotára, beállításaira utaló szöveges információ
iMaxSockets
word
Az egy folyamat által egyszerre maximálisan nyitva tartható socketek száma
iMaxUdpDg
word
A interfész által támogatott legnagyobb UDP csomag-méret (bájtokban)
lpVendorInfo
PChar
Gyártó specifikus adatterületre mutató mező. (Értelmezése nem egységes.)

Az interfész sikeres inicializálása után az alkalmazás elkezdheti használni a WinSock függvényeket. Az inicializálás előtt esetlegesen meghívott függvények automatikusan a WSANOTINITALIZED hibakódot adják vissza.

Minden egyes WSAStartup() függvény-híváshoz kapcsolódnia kell egy WSACleanup() hívásnak is, amit az alkalmazásnak akkor kell meghívnia ha már nem kívánja többé igénybe venni a WinSock interfész szolgáltatásait. Az utolsó (az első WSAStartup() párját alkotó) WSACleanup() hívás deinicializálja az interfészt és felszabadítja a folyamat számára lefoglalt erőforrásokat.

A konkrét függvények tárgyalása előtt érdemes megemlítenünk, hogy a WinSock a socketek alapvetően két fajta kezelését teszi lehetővé. Az alapértelmezett kezelési mód az ún. blokkoló mód, amikor is a WinSock funkció végrehajtása alatt az alkalmazás végrehajtása felfüggesztődik. Ez azt jelenti, hogy a funkcióhívások a hagyományos, jól megszokott módon hajtódnak végre, azaz a funkció sikeres/sikertelen befejezéséig az alkalmazás mintegy "megfagy", így képtelen lesz reagálni bármilyen felhasználói beavatkozásra ill. egyéb közben esetlegesen bekövetkező eseményre.

A második, külön funkcióhívással aktiválható mód az ún. nem-blokkoló (non-blocking) üzemmód, amikor is a funkcióhívás a megfelelő adminisztráció elvégzése után azonnal visszatér, így az alkalmazás folytathatja futását míg a funkció végrehajtása a háttérben befejeződik. Ez esetben a végrehajtás állapotáról ill. az esetlegesen bekövetkező socket-eseményekről (pl. új adat érkezése) a WinSock az alkalmazást a standard Windows üzenetsoron keresztül egy speciális esemény segítségével értesíti.

Új socket nyitásához a már megismert socket() függvény használható. A függvény első paramétere az alkalmazni kívánt cím-családra jellemző AF_xxxx konstans. Bár a fent említett interfész fájlok több cím-családot is definiálnak, valójában a legtöbb WinSock implementáció csak a TCP/IP-hez kapcsolódó AF_INET, ill. ritkább esetben esetlegesen az IPX/SPX protokollok által alkalmazott AF_IPX cím-típusú socketek létrehozását támogatják. Az eljárás második paramétere SOCK_DATAGRAM ill. SOCK_STREAM lehet attól függően, hogy datagram avagy stream jellegű socketet kívánunk nyitni. A harmadik argumentum a családon belül a protokollt azonosítja: értéke AF_INET esetén tipikusan IPPROTO_UDP ill. IPROTO_TCP szokott lenni.

A socket() függvény sikeres végrehajtás esetén egy új socket-handle-t, míg hiba esetén INVALID_SOCKET-et ad vissza. Ez utóbbi esetben a hibakódot a már megismert WSAGetLastError() függvénnyel kaphatjuk meg.

Amennyiben azt szeretnénk, hogy a socket nem-blokkoló üzemmódban működjön úgy a WSAAsyncSelect() függvényt kell meghívnunk. Paraméterként a socket azonosító után az eseményeket fogadó ablak handle-jét, a socket-esemény jelzésére alkalmazni kívánt üzenet-kódot, valamint azt kell megadnunk, hogy mely események esetén kérjük a szóban forgó üzenetek generálását. Esemény generálása kérhető új kapcsolat megnyitásakor, kapcsolat-kezdeményezési kérések beérkezésekor, adat érkezésekor ill. a küldési puffer ürülésekor valamint a kapcsolat megszakadásakor - a kért események halmazát az FD_xxxx konstansok között logikai vagy művelet segítségével létrehozott érték határozza meg.

Amennyiben kértük úgy a WinSock egy FD_ACCEPT kódot tartalmazó üzenetet fog küldeni minden egyes alkalommal, amikor egy listen()-nel megnyitott sockethez egy kapcsolat-felvételi kérelem érkezik. Ugyancsak hasonló módon FD_WRITE üzenetet kapunk egy új kapcsolat accept() v. connect() révén történő felépülésekor, valamint akkor, amikor a send() ill. sendto() függvényekkel küldött adatok kiürülnek az átviteli pufferből. FD_READ üzenet abban az esetben generálódik, ha az aszinkron üzemmódú sockethez újabb adat érkezik, valamint ha az előző FD_READ határására az alkalmazás a fogadási puffert nem ürítette ki teljesen. FD_OOB eseményt csakis a speciális, az ún. out-of-band data külön fogadására konfigurált socketek esetén generál a WinSock a szóban forgó adatok érkezésekor. Az FD_CLOSE esemény pedig nyilvánvalóan a kapcsolat távoli oldalról történő befejezésekor/megszakításakor ( shutdown() ill. closesocket() ) keletkezik.

A socket sikeres létrehozása után azt vagy kapcsolat kezdeményezésére, vagy kívülről jövő kezdeményezések fogadására használhatjuk. Utóbbi esetben mindképpen - de esetleg előbbiben is - szükségünk lesz a bind() függvény alkalmazására, mellyel egy meghatározott címet tudunk társítani a sockethez. A függvény első paramétereként egy nyitott socket-handle-t, míg a továbbiakban a társítani kívánt címre mutató pointert, valamint a cím-struktúra méretét kell átadnunk. Bár a WinSock nem zárja ki (sőt, maga az specifikáció támogatja is) más címcsaládok használatát, de az interfész unit mégis - az előbb már említett okokból (ti. hogy a létező implementációk elsődlegesen az Internet protokoll-csomag használatát teszi lehetővé) - kizárólag az IP-címekre definiál struktúrát. Az TSockAddrIn rekord felépítése és a mezők jelentése a következő:
 

Mező neve
Típus
Jelentés
sin_family
word
A cím-családot azonosító konstans. (Internet protokollok esetén AF_INET)
sin_port
word
2 bájtos IP port-szám
sin_addr
dword
A 4 bájtos IP-címet különféle elérését lehetővé tevő TinAddr struktúra
sin_zero
8 byte
A struktúrát a teljes, 16 bájtos méretre kiegészítő helyfoglaló-bájtok. Értékük AF_INET esetén érdektelen.

Míg a socket(), bind(), listen(), getsockopt(), setsockopt(), getsockname(), shutdown() függvények mindig blokkoló módon futnak le (valójában ezek végrehatása mindig azonnal megtörténik így esetkükben a blokkoló helyett talán jobb a szinkron szó, hiszen valójában "nem tartják fel" az alkalmazást), addig a connect(), accept(), send(), sendto(), recv(), recvfrom(), closesocket() függvények - a socket működési módjától függően - akár aszinkron, nem-blokkoló módon is végrehajtódhatnak.

A blokkoló műveletek végrehajtása során a WinSock() implementáció egy olyan ciklust hajt végre, amelyben a szóban forgó művelet elvégzése mellett folyamatosan fogadja és az alkalmazás felé feldolgozás céljából továbbítja a standard Windows üzenetsoron érkező eseményeket. Ezáltal az alkalmazás továbbra is képes lesz a felhasználói műveletekre, valamint a belső rendszer-eseményekre válasz adására.

Amíg egy blokkoló feladat végrehajtása folyamatban van, addig a WinSock() nem tud újabb műveleteket végrehajtani az adott process számára - amennyiben az alkalmazás mégis megpróbálna meghívni egy ilyen függvényt úgy az interfész a WSAEINPROGRESS hibakóddal megtagadja annak végrehajtását. Azt, hogy blokkoló művelet végrehajtása van -e folyamatban az alkalmazások a WSAIsBlocking() függvény segítségével kérdezhetik le, míg szükség esetén a művelet végrehajtásának esetleges megszakítását a WSACancelBlockingCall() meghívásával kérhetik.

Amennyiben a nem-blokkoló socketen a kért művelet azonnal végrehajtható úgy a hívott függvény a blokkoló socketek esetén megszokott hibakóddal, míg egyéb esetben az aszinkron műveletvégzés elkezdését jelentő WSAEWOULDBLOCK kóddal tér vissza. Ez utóbbi esetben a feladat végrehajtásának sikerességéről vagy éppenséggel sikertelenségéről az alkalmazás a már említett FD_xxxx eseményeken keresztül, ill. pl. kapcsolat-felépítési műveletek esetén a select() függvény segítségével szerezhet tudomást.

A gethostbyname() ill. gethostbyaddr() nevű, a DNS ill. IP címek közti oda-vissza konverziót elvégző függvénypáros a leggyakrabban használt adatbázis-kezelő függvények közé tartoznak. Végrehajtásuk - lévén standard BSD-socket függvények - tipikusan blokkoló, míg WinSock-beli párjaik, a WSAAsyncGetHostByName() ill. a WSAAsyncGetHostByAddr() aszinkron módon működnek.

Ugyanez igaz a portok számainak és a szóban forgó portszámokat standardként használó szolgáltatások (pl. FTP - 21, HTTP- 80, stb.) közti konverziót elvégző getservbyname(), getservbyport() valamint a protokollok-számok és -nevek közti "átváltást" elvégző getprotobyname(), getprotobynumber(), ill. a nekik megfelelő funkciókat ellátó, de aszinkron módon végrehajtásra kerülő, WSA-val prefixált WinSock funkciópárosokra is (WSAGetServByName(), WSAGetServByPort(), stb.).

Az aszinkron WinSock adatbázis-kezelő függvények a végrehajtásról a már megismert módon, a Windows üzenetsoron keresztüli értesítésekkel informálják az alkalmazást. Az aszinkron funkciók által visszaadott handle a szóban forgó művelet megszakítására használható a WSACancelAsyncRequest() hívásban.

A cikk könyvtárában megtalálható a Windows Sockets specifikáció, vagy klikkelj ide.