IV. TCP/IP REXX
Автор: Kádár Zsolt
Дата: 24.05.1999
Источник: https://xenia.sote.hu
Язык: Венгерский
A TCP/IP REXX kiterjesztés
Az OS/2 TCP/IP implementációja két olyan bôvítôcsomaggal is rendelkezik, amelyekkel a TCP/IP API-k jelentôs része REXX-bôl is használható. Ha valaki telepítette gépén a TCP/IP-t, akkor meg kell hogy találja az RXFTP.DLL és az RXSOCK.DLL fájlokat, amelyek a REXX FTP és socket bôvítôfüggvényeket tartalmazzák. A csomagokhoz tartoznak INF fájlok is, amelyekben megtalálhatjuk a függvények leírását. Sajnos az eredeti INF fájlok meglehetôsen szûkszavúak. Az FTP függvények esetében ez még nem is olyan nagy probléma, mivel az FTP parancsok ismeretében elég jól meg lehet saccolni, hogy mit csinálnak a REXX függvények. A socket API-kat viszont gyakorlatilag csak az tudja az INF fájl alapján használni, aki már foglalkozott socket programozással. Valószínûleg sokan tiltakoztak is az IBM-nél, mivel tavaly kiadtak egy frissített INF fájlt, ami alapján már az is elkezdheti a socketek programozását, aki nem rendelkezik semmilyen elôképzettséggel. Az ObjectREXX Windows-os verziójában ugyanezek a bôvítôfüggvények megtalálhatóak, ráadásul használatuk is teljesen megegyezik az OS/2-es kiadáséval.
REXX ftp API-k
Amennyiben az RXFTP.DLL fájl megtalálható a LIBPATH-ban, akkor az FTP bôvítôfüggvényeket a következô kódrészlettel regisztrálhatjuk REXX programunkban:
Call RxFuncAdd "FtpLoadFuncs", "RxFtp", "FtpLoadFuncs" Call FtpLoadFuncs
A függvények használat után az FtpDropFuncs
függvénnyel törölhetôk a memóriából. Az eddig említett két függvényen kívül a következô függvények találhatók még a csomagban:
Funkció és szintakszis: | Paraméterek: | Visszatérési érték: |
A bôvítôcsomag verziószámának lekérdezése: Rc = FTPVersion(változó) | változó = az RxFtp csomag verziója | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
A felhasználó adatainak definiálása: Rc = FtpUser(szerver, felhasználó, jelszó [, account]) | szerver = az ftp szerver neve, felhasználó = a felhasználó neve, jelszó = a felhasználó jelszava, account = szerverfüggô kiegészítô információ | 0 = a megadott karakterláncok valótlanok, 1 = a megadott karakterláncok valósak |
A transzfer mód beállítása: Rc = FtpSetBinary(mód) | mód = 'Binary' (bináris mód), 'Ascii' (szöveges mód) | 0 = a megadott paraméter valótlan, 1 = a megadott paraméter valós |
Az FTP szekció lezárása: Rc = FtpLogoff() | - | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl feltöltése a szerverre és csatolása egy a szerveren található másik fájlhoz: Rc = FtpAppend(lokális_fájl, távoli_fájl [, mód]) | lokális_fájl = a feltöltendô fájl neve, távoli_fájl = a szerveren lévô fájl, amelyhez hozzá akarjuk toldani a lokális fájlt, mód = 'Binary' vagy 'Ascii' | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl törlése a szerveren: Rc = FtpDelete(távoli_fájl) | távoli_fájl = a törlendô szerverfájl neve | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl átnevezése a szerveren: Rc = FtpRename(távoli_fájl) | távoli_fájl = az átnevezendô szerverfájl neve | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl letöltése: Rc = FtpGet(lokális_fájl, távoli_fájl [, mód]) | lokális_fájl = a letöltött fájl neve a letöltés után, távoli_fájl = a letöltendô fájl neve, mód = 'Binary' vagy 'Ascii' | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl feltöltése: Rc = FtpPut(lokális_fájl, távoli_fájl [, mód]) | lokális_fájl = a feltöltendô fájl neve, távoli_fájl = a feltöltött fájl neve a feltöltés után, mód = 'Binary' vagy 'Ascii' | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl feltöltése, amennyiben a megadott távoli fájl még nem létezne: Rc = FtpPutUnique(lokális_fájl, távoli_fájl [, mód]) | lokális_fájl = a feltöltendô fájl neve, távoli_fájl = a feltöltött fájl neve a feltöltés után, mód = 'Binary' vagy 'Ascii' | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Könyvtárinformáció lekérdezése rövid formátumban: Rc = FtpLs(minta, összetett_változó) | minta = azoknak a fájloknak mintája, amelyeket listázni akarunk (pl. *.txt), összetett_változó = ebben kerül eltárolásra a megszerzett könyvtárinformáció | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Könyvtárinformáció lekérdezése hosszú formátumban: Rc = FtpDir(minta, összetett_változó) | minta = azoknak a fájloknak mintája, amelyeket listázni akarunk (pl. *.txt), összetett_változó = ebben kerül eltárolásra a megszerzett könyvtárinformáció | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Könyvtár váltása a kiszolgálón: Rc = FtpChDir(könyvtár) | könyvtár = az a könyvtár a szerveren, amelyikre át akarunk váltani | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Könyvtár készítése a kiszolgálón: Rc = FtpMkDir(könyvtár) | könyvtár = a szerveren létrehozni kívánt könyvtár neve | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Könyvtár törlése a kiszolgálón: Rc = FtpRmDir(könyvtár) | könyvtár = a szerveren törölni kívánt könyvtár neve | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Az aktuális könyvtár lekérdezése a kiszolgálón: Rc = FtpPwd(könyvtár) | könyvtár = a visszaadott könyvtár nevét tartalmazza idézôjelek között | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Karakterlánc (tetszôleges parancs) küldése a kiszolgálónak: Rc = FtpQuote(karakterlánc) | karakterlánc = a küldendô parancs | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Információ küldése a kiszolgálónak: Rc = FtpSite(infó) | infó = a küldendô információ | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
A kiszolgáló operációs rendszerének lekérdezése: Rc = FtpSys(oprendszer) | oprendszer = itt tárolódik el a lekérdezés után a szerver operációs rendszerének neve | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Fájl másolása két ftp kiszolgáló között: Rc = FtpProxy(szerver1, felhasználó1, jelszó1, account1, szerver2, felhasználó2, jelszó2, account2, fájl1, fájl2 [,'mód]) | szerver1 = a cél szerver neve (a szerver, amelyikre másolni akarunk), felhasználó1 = a felhasználó neve a cél szerveren, jelszó1 = a felhasználó jelszava a cél szerveren, account1 = a felhasználó accountja a cél szerveren (ha nincs, akkor NULL-t kell megadni), szerver2 = a forrás szerver neve (a szerver amelyrôl másolni akarunk), felhasználó2 = a felhasználó neve a forrás szerveren, jelszó2 = a felhasználó jelszava a forrás szerveren, account2 = a felhasználó accountja a forrás szerveren (ha nincs, akkor NULL-t kell megadni), fájl1 = a cél fájl neve, fájl2 = a forrás fájl neve, mód = transzfer mód ('Binary' vagy 'Ascii'). | 0 = sikeres végrehajtás, -1 = sikertelen végrehajtás |
Az ftp kiszolgáló pingetése (jelenlétének ellenôrzése): Rc = FtpPing(szerver, hossz) | szerver = a pingetni kívánt szerver, hossz = a pingetéshez használt adat hossza | Sikeres végrehajtás esetén a függvény visszaadja az adat oda-vissza küldése alatt eltelt idôt ms-ban kifejezve. Hiba esetén a következô értékek valamelyikét adja vissza: PINGREPLY = a szerver nem válaszol, PINGSOCKET = nem lehet sockethez jutni, PINGPROTO = ismeretlen ICMP protokoll, PINGSEND = a küldés nem sikerült, PINGRECV = a fogadás nem sikerült, PINGHOST = ismeretlen szerver |
Azoknál a függvényeknél, amelyeknél a -1-es visszatérési érték jelent hibát, további információt lehet megtudni a hiba okáról, ha megvizsgáljuk az elôre definiált FTPERRNO változó tartalmát. Az alábbi táblázatban megadtuk a lehetséges értékeket és azok okát.
FTPERRNO hibakód: | Hiba oka: |
FTPSERVICE | Ismeretlen szolgáltatás. |
FTPHOST | Ismeretlen kiszolgáló. |
FTPSOCKET | Nem lehet sockethez jutni. |
FTPCONNECT | Nem lehet összekapcsolódni a kiszolgálóval. |
FTPLOGIN | Sikertelen belépési kísérlet. |
FTPABORT | A le- vagy feltöltés megszakadt. |
FTPLOCALFILE | A lokális fájl megnyitása nem sikerült. |
FTPDATACONN | Nem sikerült az adatforgalmat beindítani. |
FTPCOMMAND | A megadott parancs végrehajtása nem sikerült. |
FTPPROXYTHIRD | A proxy szerver nem támogatja a harmadik fél által végzett adatforgalmat. |
FTPNOPRIMARY | A proxy adatforgalomhoz szükséges elsôdleges kapcsolat nem jött létre. |
Mint láthatjuk, a függvények többsége nagyon hasonlít a parancssorban kiadott ftp parancsokra. Aki egy kis gyakorlattal rendelkezik ezen a területen, annak az RxFtp függvények használata sem okozhat különösebb problémát. Érdemes viszont programírás közben ügyelni arra, hogy a függvények által visszaadott hibakódokat megfelelôen feldolgozzuk. Parancssorban végzett ftp-zés közben ugyanis mindig látjuk, ha egy mûvelet végrehajtása nem sikerül. A REXX FTP API-kat használó program végrehajtása közben azonban "teljesen vakok" vagyunk, ha nem gondoskodunk megfelelôen a visszaadott hibakódok feldolgozásáról.
A továbbiakban tekintsük meg példaként azt a programot, amely a hobbes.nmsu.edu /pub/incoming könyvtárában lévô fájlokat figyeli, és figyelmeztet, amennyiben ott új fájlok tûnnek fel!
/* A hobbes.nmsu.edu /pub/incoming könyvtárának figyelése. */ /* Az RxFtp segédfüggvények regisztrálása */ Call RxFuncAdd 'FtpLoadFuncs', 'RxFtp', 'FtpLoadFuncs' Call FtpLoadFuncs 'QUIET' /* A kiszolgáló és a felhasználó adatainak beállítása */ server = 'hobbes.nmsu.edu' user = 'anonymous' passwd = 'kadzsol@freemail.c3.hu' dirtry = '/pub/incoming' '@cls' Say Say 'Kapcsolódás a(z) 'server' kiszolgálóhoz!' Say 'Felhasználó: 'user Say 'Jelszó : 'passwd Say 'Könyvtár : 'dirtry Call FtpSetUser server, user, passwd if result <> 1 then Call perror 'A megadott adat(ok) érvénytelen(ek)!' /* Kapcsolódás a kiszolgálóhoz és a belépési könyvtár lekérdezése */ Call FtpPwd remotedir if result <> 0 then Call perror 'A kapcsolódás nem sikerült!' else Say 'A kapcsolódás sikerült.' /* Könyvtárváltás */ Say Say 'Váltás a 'dirtry' könyvtárra.' Call FtpChDir dirtry if result <> 0 then Call perror 'A könyvtárváltás nem sikerült!' else Say 'A könyvtárváltás sikerült.' /* A fájllista lekérdezése */ Say Say 'A fájlok listájának letöltése folyik.' Call FtpDir "*", "file." if result <> 0 then Call perror 'A fájllista letöltése nem sikerült!' else Say 'A fájllista letöltése sikerült.' /* Kilépés */ Say Say 'Kilépés a kiszolgálóról.' Call FtpLogoff if result <> 0 then Call perror 'A kilépés nem sikerült!' else Say 'A kilépés sikerült.' /* A korábban letöltött lista beolvasása */ i = 1 locallist = 'files.lst' if stream(locallist, 'c', 'query exists') <> '' then do while lines(locallist) line.i = linein(locallist) i = i + 1 end line.0 = i - 1 rc = lineout(locallist) /* Az új fájlok kiválasztása és kiírása a képernyôre */ if files.0 > 0 then do newfiles = 'ujfajlok.txt' '@copy 'newfiles' ujfajlok.bak >NUL 2>>&1' '@del 'newfiles' >NUL 2>>&1' '@copy 'locallist' files.bak >NUL 2>>&1' '@del 'locallist' >NUL 2>>&1' Say Say 'A következô új fájlok találhatók a kiszolgálón:' do i = 1 to file.0 ujfile = 0 if line.0 > 0 then /* ha van lokális fájllista */ do j = 1 to line.0 if file.i = line.j then ujfile = 1 end if ujfile = 0 then /* ismeretlen fájl */ do say file.i Rc = LineOut(newfiles, file.i) end /* Készítjük az új lokális listát is */ Rc = LineOut(locallist, file.i) end Rc = LineOut(newfiles) Rc = LineOut(locallist) end else Say 'Nincsenek fájlok a kiszolgálón!' exit /* Hibaüzenet megjelenítése */ perror: PROCEDURE EXPOSE ftperrno parse arg message Say Say message Say 'Hibakód: 'ftperrno Call FtpLogoff exit return
A példaprogram az RxFtp segédfüggvények regisztrálásával indul, amelyet az ftp-zéshez szükséges paraméterek megadása követ. Az ftp kiszolgálóval akkor épül fel a kapcsolat, amikor lekérdezzük a munkakönyvtárat. Amennyiben sikeresen létrejött a kapcsolat, akkor átváltunk a figyelni kívánt alkönyvtárra és letöltjük az ott található fájlok részletes listáját. Ezek után nincs más hátra, mint megszakítani a kapcsolatot és eldönteni, hogy vannak-e a listában új fájlok. Ez úgy történik meg, hogy a program beolvassa a korábbi futás során lokálisan eltárolt fájllistát, amelyet összehasonlít a frissen letöltött fájllistával. Az új fájlok megjelenítése és fájlba írása mellett elkészül az új lokális fájllista is. A biztonság kedvéért a program másolatot készít az elôzô futás során generált fájlokról is. A program végén található perror eljárás a megfelelô hibaüzenet és FTPERRNO hibakód megjelenítésérôl gondoskodik.
REXX socket API-k
Amennyiben helyesen telepítettük a TCP/IP-t, akkor az RXSOCK.DLL fájl megtalálható a /TCPIP/DLL könyvtárban, amely benne kell hogy legyen a LIBPATH-ban. Ha nem így lenne, akkor másoljuk be az RXSOCK.DLL-t egy olyan könyvtárba, amely benne van a LIBPATH-ban és regisztráljuk a REXX program elején a bôvítôfüggvényeket az alábbi kódrészlettel:
Call RxFuncAdd 'SockLoadFuncs', 'RxSock', 'SockLoadFuncs' Call SockLoadFuncs
A program végén a bôvítôfüggvényeket az SockDropFuncs() függvénnyel takaríthatjuk ki a gép memóriájából.
A socket (magyarul talán aljazatnak lehetne nevezni) tulajdonképpen egy speciális fájlazonosító, amelyet arra lehet használni, hogy adatokat továbbítsunk TCP/IP protokollt beszélô gépek között. Az adat, amelyet a socketbe írunk, a hálózat és a TCP/IP protokoll segítségével a másik gép számára is rendelkezésre áll, így a socketeket használó alkalmazásoknak csak a megfelelô socket írását vagy olvasását kell elvégezniük. A socketeket úgy is lehet tekinteni, mint egy, a hálózaton keresztül megosztott speciális fájl vagy várakozási sor. Az alkalmazások általában a következô lépéseket végzik el:
Kliens oldali socketkezelés:
- A socket létrehozása a socket() függvényhívással
- Rákapcsolódás a socketre a connet() függvényhívással
- Adatcsere a send() és recv() függvényekkel
- A kapcsolat megszakítása és a socket lezárása a shutdown() és close() függvényekkel
Szerver oldali socketkezelés:
- A socket létrehozása a socket() függvényhívással
- A socket összekapcsolása a megfelelô porttal (bind() függvényhívás)
- Várakozási sor létrehozása a kapcsolódó kliensek számára (listen() függvényhívás)
- A kapcsolódó kliensek kiszolgálása (accept() függvényhívás)
Az RxSock bôvítôcsomagban természetesen megtalálható a fenti függvények megfelelôje. Nevüket megkaphatjuk, ha a TCP/IP socket API-k neve elé odatesszük a Sock szót. A socket() TCP/IP API REXX megfelelôje pl. a SockSocket() függvényhívás. Az alábbi táblázatban összefoglaltuk a socket függvények REXX megfelelôit:
Funkció és szintakszis: | Paraméterek: | Visszatérési érték: |
A bôvítôcsomag verziószámának lekérdezése: verzió = SockVersion() | - | A bôvítôcsomag verziószáma (Az 1.2-es verzió elôtt ez a függvény még nem létezett!) |
Kapcsolat elfogadása: csocket = SockAccept(socket [, cím]) | socket = a használt socket azonosítója, cím = opcionálisan megadható összetett változó, amely a kapcsolódó kliens paramétereit tartalmazza | Sikeres végrehajtás esetén a csocket tartalmazza azt a socket azonosítót, amely segítségével adatcsere folytatható a klienssel. A csocket -1 értéke hibát jelent. |
Port hozzárendelése az újonnan létrehozott sockethez: Rc = SockBind(socket, cím) | socket = a SockSocket által visszaadott socket azonosító, cím = a portinformációt és a lokális IP-t tartalmazó összetett változó | 0 sikeres, -1 sikertelen végrehajtás |
Socket lezárása és a használt port felszabadítása: Rc = SockClose(socket) | socket = a lezárandó socket azonosítója | 0 sikeres, -1 sikertelen végrehajtás |
Kapcsolat kezdeményezése: Rc = SockConnect(socket, cím) | socket = a kapcsolatot kezdeményezô socket azonosítója, cím = a szerver oldali socket címét tartalmazó összetett változó | 0 sikeres, -1 sikertelen végrehajtás |
A kiszolgáló adatainak lekérdezése IP cím alapján: Rc = SockGetHostByAddr(ipcím, kiszolgáló [, tartomány]) | ipcím = a kiszolgáló IP száma, kiszolgáló = ebben az összetett változóban kerül eltárolásra a visszakapott információ, tartománynév = a tartomány neve, amely mindig 'AF_INET' | 1 sikeres, 0 sikertelen végrehajtás |
A kiszolgáló IP címének lekérdezése neve alapján: Rc = SockGetHostByName(név, kiszolgáló) | név = a kiszolgáló neve (pl. www.ibm.com), kiszolgáló = ebben az összetett változóban kerül eltárolásra a visszakapott információ | 1 sikeres, 0 sikertelen végrehajtás |
A saját IP cím lekérdezése: ipcím = SockGetHostId() | - | A saját (lokális) gép IP címe. |
A sockethez kapcsolódó peer címének lekérdezése: Rc = SockGetPeerName(socket, cím) | socket = a socket azonosítója, cím = a visszaadott információt tartalmazó összetett változó | 0 sikeres, -1 sikertelen végrehajtás |
Lokális socket címének lekérdezése: Rc = SockGetSockName(socket, cím) | socket = a socket azonosítója, cím = a visszaadott információt tartalmazó összetett változó | 0 sikeres, -1 sikertelen végrehajtás |
A socket opciók lekérdezése: Rc = SockGetSockOpt(socket, szint, opciónév, opció) | socket = a lekérdezendô socket azonosítója, szint = a lekérdezés szintje, az 'SOL_SOCKET' az egyetlen megadható érték, opciónév = a lekérdezendô opció neve (SO_BROADCAST = van-e broadcast lehetôsége a socketnek, SO_DEBUG = lehet-e debuggolni, SO_DONTROUTE = ki lehet-e kerülni a routert, SO_ERROR = hibaüzenetek letöltése, SO_KEEPALIVE = tud-e a socket keep_alive csomagokat küldeni, SO_LINGER = vár-e becsukáskor a socket, amíg az összes adat továbbításra kerül, SO_OOBINLINE = képes-e a socket out-of-band adat fogadására, SO_RCVBUF = a fogadási puffer mérete, SO_RCVLOWAT = a vízjel-információ fogadása be van-e állítva, SO_RCVTIMEO = a fogadás türelmi ideje (timeout), SO_REUSEADDR = képes-e a socket a lokális cím újrafelhasználására, SO_SNDBUF = a küldési puffer mérete, SO_SNDLOWAT = vízjel-információ küldése be van-e állítva, SO_SNDTIMEO = a küldés türelmi ideje, SO_TYPE = a socket típusa (STREAM, DGRAM, RAW, UNKNOWN), SO_USELOOPBACK = kikerüli-e a hardvert, ha lehetséges, opció = ebben tárolódik el a visszaadott opció aktuális értéke) | 0 sikeres, -1 sikertelen végrehajtás |
A socket adatstruktúra inicializálása és a TCP/IP hálózat ellenôrzése: Rc = SockInit() | - | 0 sikeres, 1 sikertelen végrehajtás |
A socket mûködési karakterisztikájának beállítása/kiolvasása: Rc = SockIoctl(socket, ioctl_parancs, ioctl_adat) | socket = a socket azonosítója, ioctl_parancs = a végrehajtandó ioctl parancs (FIONBIO = az ioclt_adat értékétôl függôen beállítja, hogy a socket blokkolja-e a függvényhívások visszatérését, amíg a függvényhívás által kért mûvelet végre nem hajtódik; az ioctl_adat nem nulla értéke beállítja a nem blokkoló üzemmódot, a nulla pedig feloldja, FIONREAD = eltárolja az ioclt_adatban az azonnal olvasható bájtok számát vagy beállítja az azonnal olvasható karakterek számát az ioctl_adat értéke alapján), ioctl_adat = az ioctl_parancs által beállított vagy kiolvasott adat | 0 sikeres, -1 sikertelen végrehajtás |
Várakozás a beérkezô hívásokra: Rc = SockListen(socket, sorméret) | socket = a socket azonosítója, sorméret = beállítja a függvényhívás által létrehozott várakozási sor maximális méretét | 0 sikeres, -1 sikertelen végrehajtás |
A legutóbbi socket hiba és az azt magyarázó üzenet kiíratása a standard error eszközre: SockPSock_Errno([hibaüzenet]) | hibaüzenet = opcionális magyarázó szöveg | - |
Adat fogadása kapcsolt socketrôl: Rc = SockRecv(socket, adat, hossz [, opciók]) | socket = az olvasott socket azonosítója, adat = a fogadott adat, hossz = a fogadott adat maximális hossza, opciók = szóközzel elválasztott opciók listája (MSG_OOB = out-of-band adat olvasása, MSG_PEEK = az adatot csak kiolvassuk, így az ott marad a socketen) | Sikeres végrehajtás esetén a visszatérési érték a fogadott adat hosszát tartalmazza, 0 visszatérési érték a kapcsolat lezárását jelenti. A -1-es érték hibát jelez. |
Adat fogadása tetszôleges socketrôl: Rc = SockRecvFrom(socket, adat, hossz [, opciók], cím) | socket = az olvasott socket azonosítója, adat = a fogadott adat, hossz = a fogadott adat maximális hossza, opciók = szóközzel elválasztott opciók listája (MSG_OOB = out-of-band adat olvasása, MSG_PEEK = az adatot csak kiolvassuk, így az ott marad a socketen), cím = az adat küldôjének címét tartalmazó összetett változó | Sikeres végrehajtás esetén a visszatérési érték a fogadott adat hosszát tartalmazza, a -1-es érték hibát jelez. |
Socketek figyelése: Rc = SockSelect(olvas, ír, hiba [, timeout]) | olvas = azokat a socketeket tartalmazó összetett változó, amelyeknek az olvashatóságát akarjuk figyelni (olvas.0 = figyelt socketek száma, olvas.1 = az elsô socket azonosítója, stb.), ír = azokat a socketeket tartalmazó összetett változó, amelyeknek az írhatóságát akarjuk figyelni, hiba = azokat a socketeket tartalmazó összetett változó, amelyeknek a hibaállapotát akarjuk figyelni, timeout = opcionálisan megadható várakozási idô másodpercben kifejezve | Sikeres lefutás esetén a visszatérési érték tartalmazni fogja a használható socketek számát, amelyek az input paraméterként megadott összetett változókban kerülnek eltárolásra. -1-es visszatérési érték hibát jelez. |
Adat küldése kapcsolt socketre: Rc = SockSend(socket, adat, [, opciók]) | socket = az írt socket azonosítója, adat = a küldött adat, opciók = szóközzel elválasztott opciók listája (MSG_OOB = out-of-band adat küldése, MSG_DONTROUTE = bekapcsolja az SO_DONTROUTE opciót a küldés idejére) | Sikeres végrehajtás esetén a visszatérési érték a küldött adat hosszát tartalmazza. A -1-es érték hibát jelez. |
Adat küldése tetszôleges socketre: Rc = SockSendTo(socket, adat, [, opciók], cím) | socket = az írt socket azonosítója, adat = a küldött adat, opciók = szóközzel elválasztott opciók listája (MSG_OOB = out-of-band adat küldése, MSG_DONTROUTE = bekapcsolja az SO_DONTROUTE opciót a küldés idejére), cím = az írt socket címét tartalmazó összetett változó | Sikeres végrehajtás esetén a visszatérési érték a küldött adat hosszát tartalmazza. A -1-es érték hibát jelez. |
A socket opciók beállítása: Rc = SockSetSockOpt(socket, szint, opciónév, opció) | socket = a beállítandó socket azonosítója, szint = a beállítás szintje, az 'SOL_SOCKET' az egyetlen megadható érték, opciónév = a beállítandó opció neve (SO_BROADCAST = engedélyezi a broadcast tulajdonságot, SO_DEBUG = engedélyezi a debuggolást, SO_DONTROUTE = engedélyezi a routerkikerülést, SO_KEEPALIVE = engedélyezi a keep_alive csomagok küldését, SO_LINGER = engedélyezi a várakozást, amíg az összes adat továbbításra nem kerül, SO_OOBINLINE = engedélyezi az out-of-band adat fogadását, SO_RCVBUF = beállítja a fogadási puffer mérete, SO_RCVLOWAT = beállítja a vízjelinformáció fogadását, SO_RCVTIMEO = beállítja a fogadás türelmi idejét (timeout), SO_REUSEADDR = beállítja, hogy a socket képes legyen a lokális cím újrafelhasználására, SO_SNDBUF = beállítja a küldési puffer méretét, SO_SNDLOWAT = beállítja a vízjelinformáció küldését, SO_SNDTIMEO = beállítja a küldés türelmi idejét, SO_USELOOPBACK = beállítja a hardverkikerülést, ha lehetséges, opció = a beállítandó opció értéke) | 0 sikeres, -1 sikertelen végrehajtás |
Az adatforgalom leállítása: Rc = SockShutDown(socket, mód) | socket = a socket azonosítója, mód = az adatforgalom leállításának módja (0 = a socket nem fogadhat többet adatot, 1 = a socket nem küldhet többet adatot, 2 = a socket nem küldhet vagy fogadhat adatot) | 0 sikeres, -1 sikertelen végrehajtás |
Az utolsó hibakód lekérdezése: errno = SockSock_Errno() | - | Az errno változóban megtalálható a függvényhívás után a legutolsó rosszul végzôdô socket mûvelet hibakódja. |
Socket létrehozása: socket = SockSocket(tartomány, típus, protokoll) | tartomány = kommunikációs tartomány, értéke mindig "AF_INET", típus = a socket típusa (SOCK_STREAM = kétirányú megbízható adatforgalom, SOCK_DGRAM = kevésbé megbízható, datagrammokon alapuló kapcsolat nélküli adatforgalom, SOCK_RAW = felület elôre definiált protokollokhoz, pl. IP, ICMP), protokoll = a használni kívánt protokoll (IPPROTO_UDP, IPPROTO_TCP, 0 = default) | A socket változó tartalmazza a létrehozott socket azonosítóját sikeres végrehajtás esetén. -1-es visszatérési érték hibát jelez. |
Socket felszámolása: Rc = SockSoClose(socket) | socket = a megszüntetni kívánt socket azonosítója. | 0 sikeres, -1 sikertelen végrehajtás |
A függvények egy része gyakran használ összetett változókat, amelyek szerkezete a C socket függvényekkel való kompatibilitás miatt kötött. Az alábbiakban megadtuk ezeknek az összetett változók részeinek nevét és funkcióját:
Socket címe (változó address):
- address.family = minden esetben "AF_INET"
- address.port = a használt port száma
- address.addr = az IP szám (vagy INADDR_ANY)
Partner gép (változó host)
- host.name = a partner gép neve
- host.alias.0 = az álnevek száma
- host.alias.i = a partner gép i. álneve
- host.addrtype = minden esetben "AF_INET"
- host.addr = a partner default IP száma
- host.addr.0 = a partner IP címeinek száma
- host.addr.j = a partner j. IP száma
Amennyiben egy függvényhívás nem sikerül, akkor további információt szerezhetünk a hiba okáról, ha megvizsgáljuk az elôre definiált errno és h_errno változók tartalmát. A lehetséges értékeket és azok jelentését összefoglaltuk az alábbi két táblázatban:
Az errno lehetséges értékei:
EWOULDBLOCK | Nem áll adat rendelkezésre vagy lejárt a várakozási idô (nem blokkoló socket). |
EINPROGRESS | A kapcsolódás folyamatban (nem blokkoló socket). |
EALREADY | Az elôzô kapcsolódás nem sikerült (nem blokkoló socket). |
ENOTSOCK | A socket paraméter értéke valótlan. |
EDESTADDRREQ | Hiányzik a rendeltetési cím. |
EMSGSIZE | Az üzenet túl nagy ahhoz, hogy egyetlen datagrammban el lehessen küldeni. |
EPROTOTYPE | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ENOPROTOOPT | Az opció vagy a szint paraméter érvénytelen. |
EPROTONOSUPPORT | Nem támogatott protokoll. |
ESOCKTNOSUPPORT | Nem támogatott socket típus. |
EOPNOTSUPP | A mûvelet nem lehetséges a megadott sockettel. |
EPFNOSUPPORT | Nem támogatott protokoll család. |
EAFNOSUPPORT | Nem támogatott protokoll család. |
EADDRINUSE | A cím már használatban van. |
EADDRNOTAVAIL | A megadott cím érvénytelen, vagy nem elérhetô. |
ENETDOWN | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ENETUNREACH | A hálózat nem elérhetô. |
ENETRESET | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ECONNABORTED | A szoftver megszakította a kapcsolatot. |
ECONNRESET | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ENOBUFS | Nincs elég pufferhely. |
EISCONN | A socket már össze be van kapcsolva. |
ENOTCONN | A socket még nincs bekapcsolva. |
ESHUTDOWN | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ETOOMANYREFS | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ETIMEDOUT | A kapcsolódás várakozási ideje lejárt (timeout). |
ECONNREFUSED | A partner megtagadta az összekapcsolódást. |
ELOOP | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ENAMETOOLONG | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
EHOSTDOWN | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
EHOSTUNREACH | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
ENOTEMPTY | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
EINTR | Megszakított rendszerhívás. |
EINVAL | A SockListen függvényt nem hívták meg; a socket már kapcsolva van egy címhez; a parancs nem értelmes, a megadott adat nem érvényes. |
EMFILE | A használatban lévô socketek száma elérte a maximumot. |
EFAULT | Érvénytelen cím. |
A h_errno lehetséges értékei:
HOST_NOT_FOUND | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
TRY_AGAIN | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
NO_RECOVERY | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
NO_ADDRESS | Nincs adat az eredeti dokumentációban errôl a paraméterrôl. |
A sok elmélet után most nézzünk meg egy példaprogramot, amely megmutatja a fontosabb API-k használatát. A példaprogram két részbôl, egy kliens és egy szerverkomponensbôl áll. A példa lényege az, hogy a szerver tükrözve küldi vissza a kliens által küldött karakterláncot. Elôször tekintsük meg a kliens megvalósítását:
/* Kliens program */ /* A szerver nevének regisztrálása */ Parse Arg Server /* Betöltjük a socket könyvtárat */ Call RxFuncAdd 'SockLoadFuncs', 'RXSOCK', 'SockLoadFuncs' Call SockLoadFuncs 'QUIET' /* Bekérjük a tükrözendô karakterláncot */ Say 'Adja meg a tükrözendô szöveget!' Parse Pull InpString /* Létrehozzuk a socketet */ Socket = SockSocket('AF_INET', 'SOCK_STREAM', '0') /* Lekérdezzük a szerver nevéhez tartozó IP számot */ Call SockGetHostByName Server, 'Host.!' /* A socketet összekapcsoljuk a szerverrel */ Host.!family = 'AF_INET' Host.!port = 1996 Call SockConnect Socket, 'Host.!' /* Elküldjük a feldolgozandó karakterláncot */ Call SockSend Socket, InpString /* Fogadjuk a szerver válaszát és zárjuk a socketet */ Call SockRecv Socket, 'OutString', 256 Call SockShutDown Socket, 2 Call SockClose Socket Say 'Bemenet: 'InpString' Kimenet: 'OutString'.' exit
A kliens program elôször eltárolja a szerver paraméterként megadott nevét, majd betölti a segédfüggvényeket, és bekéri a tükrözendô szöveget. Ezután következik a kommunikációhoz szükséges socket létrehozása. A socket típusaként STREAM-et adtunk meg, amely full duplex (kétirányú) kommunikációt tesz lehetôvé. A szerverrel való összekapcsolódáshoz szükség van annak IP számára, amelyet a következô lépésben kérdezünk le. Az IP szám és a port birtokában összekapcsoljuk a létrehozott socketet a kiszolgálóval és elküldjük a megadott karakterláncot. A továbbiakban fogadjuk a szerver válaszát, s amikor az megérkezik, akkor lezárjuk a socketet és felszabadítjuk a lefoglalt erôforrásokat. Az utolsó lépés a bemeneti és kimeneti (tükrözött) karakterláncok megjelenítése.
A szerver programban szükség van néhány további sorra, mivel létre kell hozni egy várakozási sort is:
/* Szerver program */ /* Betöltjük a socket könyvtárat */ Call RxFuncAdd 'SockLoadFuncs', 'RXSOCK', 'SockLoadFuncs' Call SockLoadFuncs 'QUIET' /* Létrehozzuk a socketet */ Socket = SockSocket('AF_INET', 'SOCK_STREAM', '0') /* Lekérdezzük az IP számunkat */ Host.!addr = SockGetHostId() /* �sszekapcsoljuk a socketet a porttal */ Host.!family = 'AF_INET' Host.!port = 1996 Call SockBind Socket, 'Host.!' /* Létrehozzuk a kapcsolódáshoz szükséges várakozási sort */ Call SockListen Socket, 1 /* Várjuk a kliens jelentkezését */ Say 'Kapcsolatra várunk...' ClientSocket = SockAccept(Socket) Say 'A kapcsolat létrejött.' /* Nem akarunk több klienst fogadni, ezért zárjuk a socketet */ Call SockShutDown Socket, 2 Call SockClose Socket /* Beolvassuk a küldött adatot és tükrözve küldjük vissza */ Call SockRecv ClientSocket, 'InpString', 256 Say 'Bemeneti adat: 'InpString OutString = Reverse(InpString) Call SockSend ClientSocket, OutString /* Zárjuk a kliens socketet */ Call SockShutDown ClientSocket, 2 Call SockClose ClientSocket exit
A szerver program a klienssel megegyezô módon indul. A segédfüggvények betöltése után létrehozunk egy socketet, amelyet a saját IP számunk birtokában kapcsolunk a kommunikációhoz kiválasztott 1996-os porthoz. Az 1024 alatti portokat lehetôleg ne használjuk saját célra, mivel azokat a hivatalos TCP/IP alkalmazásoknak elôre definiálták. Mielôtt fogadni tudná a szerver a klienseket, létre kell hozni egy ún. várakozási sort, amelynek méretét a SockListen függvény paraméterével lehet beállítani. A várakozási sor a kapcsolódó kliensek "sorbaállítására" szolgál. Amikor a szerver egy, a várakozási sorban veszteglô klienssel párbeszédet akar kezdeményezni, akkor kiadja a SockAccept utasítást, amelynek eredményeként visszakap egy socketazonosítót, amelyen keresztül lebonyolíthatja az adatcserét a sorban elsô helyen várakozó klienssel. A várakozási sorhoz tartozó socket természetesen szintén életben marad, s a szerver egy újabb SockAccept hívással párbeszédet kezdeményezhet a sorban következô klienssel is. Mivel a példaprogramban csak egyetlen klienssel folytatunk adatcserét, ezért a kapcsolat létrejötte után zárjuk az elsô socketet, majd elolvassuk, tükrözzük és visszaküldjük a megadott karakterláncot. A program végén zárjuk az adatcseréhez használt socketet is és kilépünk.
A példaprogramokban nem figyelünk az esetleges hibákra. Egy valódi alkalmazásban ez természetesen nem lenne megengedhetô. A példaprogramok között (lecke04d-04f) megtaláljuk a fenti kliens-szerver alkalmazás kiforrottabb megvalósítását. Terjedelmi okokból nem közöljük le ebben a leckében a kódot, csak teszünk néhány megjegyzést.
Figyeljük meg, hogy a szerverprogram továbbfejlesztett változata több kliens egyidejû kezelésére képes, ugyanis a kliensek kapcsolódásának fogadását (daemon program) és a további kommunikációt (a kliensek parancsainak feldolgozását, handler program) két különálló programra bontottuk (lecke04e és lecke04f). A kliens program (lecke04d) két darab parancs (DIR, TYPE) valamelyikét küldi a szervernek, amelyre az értelemszerûen válaszol. Ha a kliens a QUIT parancsot küldi, akkor a handler és a kliens program közötti adatcsere lezárul. A daemon program csak akkor áll le, ha ablakában kiadjuk a CTRL-C billentyûkombinációt.
A további példák között találunk még egy primitív WWW klienst (lecke04g), amely lekérdezi a megadott URL-hez tartozó dokumentum módosításának idejét. A példaprogram az RFC1945-ben leírt HTTP parancsok alapján mûködik. A következô példa (lecke04h) az elôzô alapjaira épített URL ellenôrzô alkalmazás, ugyanis megvizsgálja a bemeneti paraméterként megadott fájlban felsorolt URL-eket, s ha ezek valamelyikének dátuma változott, akkor azt jelzi egy kimeneti html fájlban. Nagyon hasznos alkalmazás, ha pl. figyeltetni akarjuk a bookmarkjaink között felsorolt dokumentumokat. A html protokollon kívül természetesen szinte minden egyéb internetes protokoll programozható REXX-bôl. A leckéhez tartozó legutolsó példa (lecke04i) a Time Server Protocol használatát demonstrálja. Az igen rövid alkalmazás nagyon jól jöhet azoknak, akik gépének órája rendszeresen elállítódik. Ha valaki további példákra kíváncsi, akkor érdemes az interneten keresni, mivel nagyon sok REXX programot írtak már, amelyekkel meg lehet pl. a levelezést (POP, SMTP), vagy akár a hírcsoport olvasást (news) is oldani.
REXX GYÍK:
K1. Szeretném leveleimet REXX programmal letölteni, azonban nem ismerem jól a POP protokoll parancsait és az azt leíró RFC-kbôl sem tudom a számomra szükséges információt kihámozni. Lehet valahogy próbálgatni a POP protokoll parancsait?
V1. Igen. A telnet program segítségével gyakorlatilag mindenféle protokoll kipróbálható. POP protokoll esetében telnetelni kell a kiszolgáló POP portjára, amely alapesetben 110:
telnet -p 110
Az összekapcsolódás után begépelhetjük a kipróbálásra szánt POP parancsokat, és rögtön látni fogjuk a kiszolgáló válaszát is.
Gyakorlatok:
1. Írjon REXX programot, amellyel leveleket lehet letölteni POP szerverrôl!
2. Írjon REXX programot, amellyel leveleket lehet küldeni SMTP szerveren keresztül!
Kádár Zsolt 1999. 05. 24. |