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.