(J\V]HU&NOLHQVV]HUYHUUHQGV]HUHN ALAPELVEK
$]HORV]WRWWDONDOPD]iVRNHJ\LNFVRSRUWMiWDNpWUpWHJ&DONDOPD]iVRNDWNpSYLVHOLNDNOLHQV
V]HUYHUUHQGV]HUHN,O\HQDONDOPD]iVRNNDOPiUIHOWpWHOH]KHWHQPLQGHQNLWDOiONR]RWWD]RQEDQ QHPIHOWpWOHQOUHDOL]iOMDKRJ\DKDV]QiOWV]RIWYHULO\HQDODSRQP&N|GLN$V]HUYHUNOLHQV
UHQGV]HUHNUHiOWDOiQRVViJEDQMHOOHP]KRJ\D]HJ\LNDONDOPD]iVDV]HUYHUEL]RQ\RV
V]ROJiOWDWiVRNDWYpJH]DNOLHQVNpUpVHLUHpVDNOLHQVIIHODGDWDH]HQNpUpVHNPHJIRJDOPD]iVD (összeállítása) és a kérdésre adott válaszok megjelenítése. Gondoljunk például az Internetes
ZHEROGDODNUDDKRODNOLHQVV]iPtWyJpSH]HQIXWDE|QJpV]QN HJ\HJ\IiMOWNpUOHDQQDND] egész Internetes hálózaton egyedi azonosítója, az URL, vagy újabb szóhasználattal URI DODSMiQ(]WDIiMOWSRQWRVDEEDQDQQDN+70/Q\HOYHQNyGROWIRUUiViWDPHJIHOHO
ZHEV]HUYHUW]HPHOWHWV]iPtWyJpSIRJMDHONOGHQL%iUHJ\V]HU&V]|YHJHVIiMOUyOYDQV]yH]
D]RQEDQV]iPXQNUDV]LQWHpUWKHWHWOHQ$NOLHQVJpSQN|QIXWyE|QJpV]SURJUDPYLV]RQW
pUWHOPH]QLWXGMDDNyGRNDWpVDNpV]tWMHV]iQGpNDV]HULQWPHJMHOHQtWLD]ROGDOW9DQQDNRO\DQ
HVHWHNLVDKROQLQFVNyGROiVLO\HQNRUDE|QJpV]DNOLHQVLQWHOOLJHQFLiMiQP~OLNKRJ\
PHJKtYMDDPHJIHOHOROYDVySURJUDPRWSpOGiXO:RUGYDJ\PiVKDVRQOy
GRNXPHQWXPV]HUNHV]W pVPHJMHOHQtWVHDNpUWGRNXPHQWXPRW0LQGHQPiVHVHWEHQKDH]D]
LQWHOOLJHQFLDQHPNHUOWEHpStWpVUHDE|QJpV]DXWRPDWLNXVDQOHW|OWpVUHNDSFVROpVD]85/ alapján azonosított fájl letöltését kínálja fel a kliensgépre. Innét már a felhasználó egyéni G|QWpVHKRJ\PLW|UWpQLNDN|YHWNH]NEHQDOHW|OW|WWiOORPiQQ\DOPLO\HQDONDOPD]iVW
szoftvert használ az állomány megjelenítésére, megtekintésére.
0.1. ábra. Webszerver ábrája A fentebb vázolt felállás egy speciális kliens-szerver rendszer, ahol a rendszer két
|VV]HWHYMpQHNWHYpNHQ\VpJLN|UHEiUNOLHQVEODNiUKiQ\OHKHWH]HNPLQGXJ\DQD]WNpSHVHN
elvégezni - szigorúan meghatározott. Felmerül a logikus kérdés, vajon mire jó még ez az elosztott alkalmazás? Nyilvánvaló, hogy sok egyéb célra is használható egy ilyen elven
IHOpStWHWWHORV]WRWWUHQGV]HUQp]]QNPHJHJ\NpWDYDOyViJEDQLVOpWH]DONDOPD]iVW 3RQWRVLGV]HUYHU
$],QWHUQHWHW]HPHOWHWV]iPtWyJpSHNHVHWpEHQUHQGNtYOIRQWRVKRJ\DJpSHNEHOVyUiMD szinkronizálva legyen és persze pontos is legyen. Mindenki tudja, hogy a karórákhoz képest a
V]iPtWyJpSHNEHOVyUiLQDJ\RQSRQWDWODQRNWXGQDNOHQQLWHKiWDV]LQNURQL]iOiVFVDN~J\
YDOyVtWKDWyPHJKDHJ\JpSHWNLQHYH]QNLGV]HUYHUQHNpVUHQGV]HUyUiMiWIRO\DPDWRVDQHJ\
NOVIRUUiVDODSMiQNRUULJiOMXNDW|EELJpSSHGLJHJ\HJ\NOLHQVSURJUDPRWIXWWDWDPHO\
DGRWWYDJ\EHiOOtWRWWLGQNpQWOHNpUGH]LDSRQWRVLGWDV]HUYHUWO(J\LO\HQDONDOPD]iV
QDJ\RQHJ\V]HU&OHKHWKLV]HQVHPPLPiVWQHPNHOOFVLQiOQLDNOLHQVQHNPLQWLGN|]|QNpQWD
szerverhez fordulni - ennek azonosítója persze beállítható, de akár önállóan meg is keresheti -
pVHONpUQLDSRQWRVLGWDPHO\DODSMiQDUHQGV]HUEHQOHIXWWDWHJ\WLPHV]HU&SDUDQFVRWD
NDSRWWPHJIHOHOIRUPiUDKR]RWWLGDGDWWDO$SpOGiEyOOiWKDWyKRJ\DV]HUYHUpVDNOLHQV IHODGDWDOpQ\HJHVHQHOYiOLNHJ\PiVWyODV]HUYHUPHJV]HU]LpVPHJIHOHOIRUPiEDKR]]DD kívánt adatot, míg a kliens ezt megkapva és paraméterként felhasználva egy operációs UHQGV]HUV]LQW&SDUDQFVRWDONDOPD]iVW IXWWDWOH .OLHQV
6]HUYHU 3RQWRVL GR"
L GRNRQYHU]L R6WUL QJ 7 L PH
L GREHDO O L WDV7 L PH YRL G
L GRO HNHUGH]HV 6WUL QJ
,GR6WUL QJ
0.2iEUD3RQWRVLGV]HUYHUiEUiMD /HYHOH]V]HUYHU Feladatát mindenki ismeri a gyakorlatból, ha lefuttatom a kliens alkalmazást, akkor a
EHiOOtWRWWPDLOV]HUYHUD]RQRVtWySpOGiXOPDLOV]DPDONKXDODSMiQEL]RQ\RVHOOHQU]pVHNHW HOYpJH]YHD]RQRVtWyMHOV]y OHNpULDPHJIHOHOOHYHOH]OiGiEyODEHpUNH]HWWOHYHOHNHWpV
D]RNDWEHiOOtWiVWyOIJJHQNLLVW|U|OWHWLDV]HUYHUUO
Közös elemek A fenti két példa alapján már meg is fogalmazhatjuk, hogy melyek a közös elemei egy kliensV]HUYHUUHQGV]HU&HORV]WRWWDONDOPD]iVQDN
í azonosítani kell a szervert a hálózaton - erre a célra szolgál az URL - és ezt az azonosítót a kliens gépen futó alkalmazásnak tudnia kell, nélküle nem képes megtalálni a szervert; í kell egy közös kódrendszer, üzenet szerkezet, amely alapján a két számítógép megérti HJ\PiVWDOHJHJ\V]HU&EEHVHWEHQH]HJ\V]DEiO\RVV]HUNH]HW&NDUDNWHUVRUR]DWOHKHW í mind a szerver, mind a kliens oldali alkalmazásban olyan elemek (objektumok) legyenek, amelyek a kapcsolódást és az üzenetváltást el tudják látni - pl. Socket osztály a Java nyelvben. í PLYHO D V]HUYHU IRO\DPDWRVDQ ILJ\HO D EHpUNH] ]HQHWHNUH OHJHJ\V]HU&EE PHJYDOyVtWiVD HJ\ YpJWHOHQ FLNOXV DPHO\EO NLOpSpV FVDN D V]HUYHUW OHiOOtWy VSHFLiOLV utasításra van;
Azonosítás, üzenetfogadás Az URL - Uniform Resource Locator1 - vagy az újabban használt URI - Uniform Resource Identifier2 - tökéletesen egyedi mutatót biztosít a teljes Interneten egy meghatározott
HUIRUUiVKR]UHVRXUFH (]D]HUIRUUiVOHKHWHJ\V]HU&PLQWSpOGiXOHJ\IiMOYDJ\HJ\PDSSD
de hivatkozhat egy bonyolultabb objektumra is, mint például egy adatbázis lekérés vagy egy
NHUHVPRWRU(QQHNHOpUpVpUHDV]iPtWyJpS,3FtPHV]ROJiODPLDQpYV]HUYHUHNEHQWiUROW
LQIRUPiFLyNDODSMiQHJ\pUWHOP&HQPHJIHOHOWHWKHWD]HJ\HVGRPpQQHYHNQHNWRYiEEiD
doménon belül - ez általában egy könyvtárnak felel meg az azonosított számítógépen - az
HJ\HVDOPDSSiNpVYpJODIiMORNHJ\HGLQHYpEONpSH]]ND]85,W
A névszerver vagy domain névszerver (DNS) feladata, hogy az adott hálózati szegmensen összekapcsolja a nevekkel jellemzett doméneket és a számítógépet azonosító IP címet. Kérésre - ismét egy szerver! - megmondja, hogy a NHUHVHWW QHY& GRPpQ PHO\LN JpSHQ DQQDN PHO\LN N|Q\YWiUiWyO NLLQGXOYD WDOiOKDWy PHJ 2UV]iJRQNpQW KLHUDUFKLNXV felépítésben helyezkednek el a névszerverek, ugyanakkor bárki telepíthet névszervert a saját gépére. Természetesen D] ,QWHUQHWHV IRUJDORP FtPIHOROGiViW YpJ] QpYV]HUYHUHN LVPHULN HJ\PiVW pV D KiOy]DWRQ HJ\PiVVDO NRPPXQLNiOYD képesek végül a fizikai címet megadni.
Egy számítógép több szervert is futtathat, bizonyos határok között akárhány szervert, amelyeket egymástól a port (azaz kapu) különböztet meg. A port azonosító biztosítja, hogy az
]HQHWDPHJIHOHOV]HUYHUKH]MXVVRQHODV]iPtWyJpSHQ$V]iPtWyJpSHVV]DNQ\HOYEHQ
démonnak nevezik azokat az alkalmazásokat, amelyek egy-egy porton figyelve fogadják a
EHpUNH]NpUpVHNHW]HQHWHNHW(]pUWD]HJ\HVV]HUYHUROGDOLDONDOPD]iVRNNDSFVRODWWDUWy részét is démonnak nevezzük. Ha beletekintünk valamely alkalmazás bin vagy lib
N|Q\YWiUiEDpVRWWGUHYpJ]GSURJUDPIiMOWWDOiOXQNDNNRUQDJ\YDOyV]tQ&VpJJHO
PHJWDOiOWXNDNDSFVRODWWDUWiVpUWIHOHOVHOHPHWSpOGiXOP\VTOGD0\64/DGDWEi]LVNH]HO kapcsolattartó démonja és httpd a http kéréseket fogadó webszerver (Apache) démonja. HQ PiU WDSDV]WDODWEyO WXGMD D] ,3 FtP WHWV]pV V]HULQW EHiOOtWKDWy $ V]iPtWyJpSHN (hardware address vagy rövidítve HW Address) biztosítja. Így például a 00:0C:76:94:A9:0E hexadecimális számmal D]RQRVtWRWW KiOy]DWL NiUW\D D] ,QWHUQHWHQ D ,3 FtPPHO UHQGHONH] V]iPtWyJpSEHQ WDOiOKDWy D MHJ\]HW tUiViQDNLGSRQWMiEDQ(EEOD]iOOtWiVEyOD]LVN|YHWNH]LNSHUV]HKRJ\DKiOy]DWLNiUW\DJ\iUWyNVHKROH]HQD)|OG|Q QHP J\iUWKDWQDN NpW D]RQRV +: $GGUHVV pUWpNNHO UHQGHONH] DONDWUpV]W pV SHUV]H IRO\DPDWRVDQ NRPPXQLNiOQLXN NHOO PHJHJ\H]YH HJ\PiV N|]|WW D IHOKDV]QiODQGy D]RQRVtWyV]iPRN WDUWRPiQ\DLUyO %YHQ YDQ WDUWDOpN PHUW D hexadecimális elrendezés miatt 2566 darab, azaz kereken 280000 milliárd egyedi hálózati kártya gyártható le. Vessük össze ezt a Föld jelenlegi lakosságával, ami ma 6 milliárd ember! $PLQW H]W IHOWpWHOH]KHW
PHJNO|QE|]WHWKHW VpJpW D IL]LNDL V]LQWHQ D KiOy]DWL NiUW\iQDN D J\iUWyN iOWDO JDUDQWiOW HJ\HGL D]RQRVtWyV]iPD
$] ,3 FtP SHUV]H FVDN DNNRU W|OWL EH IHODGDWiW KD YDOyEDQ HJ\HGL pV NL WXGMXN V]
&UQL D] HPEHUL WpYHGpVW WHKiW
MHiOOtWJDVVD$FpOD]KRJ\HJ\HJ\V]ROJiOWDWyLDOKiOy]DWRQEHOOJpSLPHJROGiVVDO biztosítsuk az egyediséget. Ezt a szerepet töltik be a DHCP alapú rendszerek, ahol a kiosztást a DHCP szerver végzi és tartja nyilván, ez a szoftvereszköz tartja nyilván a már kiosztott címeket és ezek fizikai azonosításához feljegyzi a kérdéses gép hardvercímét is a saját adatbázisában.
OHKHW OHJQHDV]iPtWyJpSNH]HO
1
http://archive.ncsa.uiuc.edu/SDG/Software/Mosaic/Demo/url-primer.html
2
http://www.ietf.org/rfc/rfc2396.txt
GYAKORLATI MEGVALÓSÍTÁS A Java nyelvet és annak fejlesztési elveit, alkalmazási területeit ismerve biztosak lehetünk
DEEDQKRJ\NOLHQVV]HUYHUDONDOPD]iVRNDWPiUVRNDQNpV]tWHWWHNpVH]pUWIHOWpWHOH]KHWHQ
vannak már kész eszközök egy ilyen típusú alkalmazás megvalósításához. Vizsgáljuk meg
WHKiWHOV]|ULVKRJ\D-DYDQ\HOYPLO\HQHV]N|]|NHWNtQiOIHOV]iPXQNUDPLO\HQNpV] osztályokból tudjuk összeépíteni a kívánt rendszert.
A Java dokumentáció tartalmazza az alábbi ábrát is, és ennek alapján már elindíthatjuk a keresésünket.
0.3. ábra. Java 2 platform (J2SE v. 1.4) csomagszerkezete A Java SDK úgynevezett Core API csomagjai között találjuk a Networkig csomagot, azon belül a java.net csomagot és ennek osztályai biztosítják számunkra az összes eszközt alkalmazásunk felépítésére. Vizsgáljuk meg a számunkra legfontosabb három osztály
IHOpStWpVpWP&N|GpVpW
SERVERSOCKET OSZTÁLY Csomag: java.net Deklaráció: public class ServerSocket
.|]YHWOHQVjava.lang.Object
A ServerSocket osztály példánya szerver oldali kapcsolóelemet, kapcsolódási pontot3 (socket) KR]OpWUH$NDSFVROyGiVLSRQWDKiOy]DWRQiWpUNH]NpUpVHNUHYiUpVDNpUpVDODSMiQEL]RQ\RV WHYpNHQ\VpJHNHWYpJH]PDMGOHKHWVpJHWEL]WRVtWDUUDKRJ\DYiODV]YLVV]DNHUOM|QDNpUGpVW IHOWHYK|])RQWRVPHJMHJ\H]QLKRJ\DV]HUYHUROGDOLNDSFVROyGiVLSRQWPXQNiMiW
ténylegesen a SocketImpl osztály egy példánya végzi. Egy alkalmazásban megváltoztathatjuk a kapcsolóelemet létrehozó programrészletet (factory) - amely a példányosítást végzi - oly PyGRQKRJ\DKHO\LW&]IDOQDNPHJIHOHONRQILJXUiFLyM~NDSFVROyHOHPHNHWKR]]RQOpWUH 0H]N
y y y y
private private private private
boolean created boolean bound boolean closed SocketImpl impl
$]HOVKiURPPH]DNDSFVROyHOHPiOODSRWiWWiUROMDD]D]OpWUHKR]iVWSRUWKR]N|WGpVW pVOH]iUiVWPtJ DQHJ\HGLNPH]DWpQ\OHJHVWHYpNHQ\VpJHWYpJ] SocketImpl típusú objektum referenciája.
Konstruktorok
y y y y
ServerSocket() ServerSocket(int port) ServerSocket(int port, int backlog) ServerSocket(int port, int backlog, InetAddress bindAddr)
$ QpJ\ NRQVWUXNWRU D QpJ\ NO|QE|] OpWUHKR]KDWy REMHNWXPpUW IHOHO $] HOV HVHWEHQ HJ\N|WGpVpVPLQGHQIpOHHJ\pEPHJN|WpVQpONOLREMHNWXPRWKR]XQNOpWUHDPiVRGLN esetben már megadjuk, hogy melyik port-hoz kapcsolódik az objektum, a backlog változó beállítja a felsorakoztatható üzenetek, kérések számát (az objektum addig fogad új kérést, míg ezt a beállított számot el nem éri, ez alapértelmezetten 50 kérés), és a bindAddrYiOWR]yYDOQHYpQHNpVWtSXViQDNPHJIHOHOHQD]WD] ,3FtPHW DGKDWMXN PHJ DPHO\FtPHQV]iPtWyJpSHQ DV]HUYHUNDSFVROyHOHPHOpUKHW7HUPpV]HWHVHQD],3FtP megadható a doménnévvel is, a címfeloldást a területileg illetékes DNS szerver végzi majd.
Fontosabb metódusok
$KRJ\HUUOPiUNRUiEEDQV]yYROWDWpQ\OHJHVPXQNiWDSocketImpl osztály példánya végzi.
ËJ\DPHWyGXVRNHJ\UpV]HHQQHNOpWUHKR]iViWDNO|QE|]KHO\]HWHNEHQV]NVpJHV
módosításokat hivatott intézni. A ServerSocket objektum használata során az esetek
3
A socket szó valójában - nagyon jó képi megközelítéssel - egy foglalatot, aljzatot jelent, mint amelyet az elektromos kapcsolatok létrehozásánál használunk. Mintegy "összedugjuk" a klienst és a szervert az aljzaton keresztül.
többségében az accept() metódus meghívásával egy Socket objektumot hozunk létre és a további tevékenységet már ez végzi.
y y
y y
y
y y
void createImpl() throws SocketException
Hibakezeléssel kiegészítve, a setImpl() metóduson keresztül létrehozza a Socket objektumot. private void setImpl()
Amennyiben az alkalmazás speciális kapcsolóelemek létrehozását igényli, a factory objektum segítségével ezen a metóduson keresztül beállíthatjuk a kapcsolóelem tulajdonságait. SocketImpl getImpl() throws SocketException
A SocketImpl osztály kódját hozzákapcsolja ehhez a kapcsolóelemhez, amennyiben ez még nem létezik, létre is hozza a SocketImpl objektumot. public Socket accept() throws IOException
$PHWyGXVD]DGRWWNDSFVROyHOHPKH]W|UWpQNDSFVROyGiVWILJ\HOLpVDPHQQ\LEHQLO\HQ történik, azt elfogadja. A kapcsolódásig a metódus blokkol. Létrehoz egy új általános kapcsolóelem objektumot (Socket socket) és amennyiben a rendszerben van biztonsági menedzser (SecurityManager), annak checkAccept() metódusát hívja meg a frissiben létrehozott objektum socket.getInetAddress().getHostAddress() kódjával és a socket.getPort() NyGMiYDO PLQW SDUDPpWHUHNNHO KRJ\ HOOHQUL]]H YDMRQ D P&YHOHW HQJHGpO\H]HWWH $PHQQ\LEHQ QHP EL]WRQViJL NLYpWHO NHOHWNH]LN pV D P&YHOHWHWQHPKDMWMDYpJUH 0iV NLYpWHOHN LV NHOHWNH]KHWQHN D] HOOHQU]pV VRUiQ DPHO\HN IHOVRUROiViWyO LWW PRVW eltekintünk.4 public InetAddress getInetAddress()
Visszaadja az aktuális szerver kapcsolóelem lokális címét, azaz azt a címet, amelyhez a NDSFVROyHOHP N|WGLN $PHQQ\LEHQ D NDSFVROyHOHP QHP N|WGLN VHKRYD VHP D visszaadott érték null. public String toString()
Visszaadja a kapcsolóelem létrehozásához megadott címet és portszámot string formájában. public void close() throws IOException
Lezárja a kapcsolatot. Amennyiben a kapcsolathoz kommunikációs csatorna is hozzá van rendelve, azt is lezárja.
$-DYDGRNXPHQWiFLyMDYDGRFVYDJ\HJ\V]HU &HQGRFVOHW|OWKHWD http://java.sun.com/j2se/1.4.2/download.html#docsZHEKHO\UO/HW|OWpVpVNLFVRPDJROiVXWiQDNpUGpVHV információ (feltéve, hogy a teljes anyagot egy javadocs mappába tömörítettük ki közvetlenül a C meghajtón) a C:\javadocs\api\java\net\ServerSocket.html fájlban lesznek megtalálhatók. A java.net csomag a teljes GRNXPHQWiFLyQHWZRUNLQJEORNNMiEDQOHOKHWIHO
4
SOCKET OSZTÁLY Csomag: java.net Deklaráció: public class Socket
.|]YHWOHQVjava.lang.Object
A SocketRV]WiO\NOLHQVNDSFVROyHOHPHNHWKR]OpWUHH]HNHWHJ\V]HU&HQFVDN kapcsolóelemeknek is hívhatjuk. Mind a kimenet, mind a bemenet szabályozására képes, azaz HJ\V]HU&NpWLUiQ\~IRUJDOPDWYDOyVtWPHJ$NDSFVROyHOHPOpQ\HJpEHQHJ\HJ\
kommunikációs végpont két számítógép között, de amennyiben a kliens-szerver rendszer
XJ\DQD]RQV]iPtWyJpSHQNHUOPHJYDOyVtWiVUDDP&N|GpVNWRYiEEUDLVKiOy]DWRVDODSRQ megy, mintha a két gép közötti link kábel nulla hosszúságú lenne. A kapcsolóelem munkáját valójában a SocketImpl osztály példánya látja el, a ServerSocket esetében már megismert módon. Ezért itt is igaz, hogy egy alkalmazásban megváltoztathatjuk a kapcsolóelemet létrehozó programrészletet (factory) - amely a példányosítást végzi - oly módon, hogy a KHO\LW&]IDOQDNPHJIHOHONRQILJXUiFLyM~NDSFVROyHOHPHNHWKR]]RQOpWUH 0H]N
y y y y y y y
private boolean private boolean private boolean private boolean private boolean private boolean SocketImpl impl
created bound connected closed shutIn shutOut
$]HOVKDWPH]DNDSFVROyHOHPiOODSRWiWWiUROMDD]D]OpWUHKR]iVWSRUWKR]N|WGpVWpV NDSFVRODW IHQQiOOiViW OH]iUiVW PtJ D] XWROVy PH] D WpQ\OHJHV WHYpNHQ\VpJHW YpJ] SocketImpl típusú objektum referenciája. A shutIn és shutOut logikai változók az input és az output állapotának jelzésére szolgálnak, amennyiben értékük true ezek már OH]iUWQDNWHNLQWKHWN
Konstruktorok A konstruktorok nagymértékben hasonlítanak a ServerSocket osztálynál már megismertekre,
KLV]HQDIXQNFLyNLVMHOHQWVPpUWpNEHQKDVRQOyN,WWPRVWFVDNDIRQWRVDEEDNDWVRUROMXNIHO
y
y
public Socket()
$UHQGV]HUDODSpUWHOPH]pVpQHNPHJIHOHOSocketImpl típus alapján kapcsolódás nélküli kapcsolóelemet hoz létre. public Socket(String UnknownHostException, IOException
host,
int
port)
throws
Létrehoz egy folyam (stream) kapcsolóelemet és a megadott számmal azonosított porthoz kapcsolja a megnevezett gazdagépen. Amennyiben az alkalmazás
y
y
y
meghatározott egy server socket factory-t, akkor annak createSocketImpl() metódusát hívja meg az aktuális kapcsolóelem példány létrehozásához, egyébként HJ\V]HU&NDSFVROyHOHPHWKR]OpWUH$EL]WRQViJLHOHPHNHWXJ\DQ~J\YHV]LILJ\HOHPEH mint a ServerSocket osztály esetében. public IOException
Socket(InetAddress
address,
int
port)
throws
Létrehoz egy folyam (stream) kapcsolóelemet és a megadott számmal azonosított porthoz kapcsolja a megadott IP címen. public Socket(String host, int port, InetAddress localAddr, int localPort) throws IOException
Létrehoz egy kapcsolóelemet és a megadott távoli gazdagéphez kapcsolja a megadott távoli porton keresztül. A kapcsolóelem ugyanakkor kapcsolatot hoz létre a megadott helyi címhez és porthoz. public Socket(String host, int port, boolean stream) throws IOException
Létrehoz egy folyam (stream) kapcsolóelemet és a megadott számmal azonosított porthoz rendeli a megnevezett gazdagépen. Amennyiben a stream paraméter true értéket kap, a létrehozott kapcsolóelem folyam típusú, ha azonban ennek értéke false, egy datagram típusú kapcsolóelem jön létre. Felmerülhet a kérdés, mi a különbség a két kommunikációs mód között, melyik a jobb. Mint annyi más esetben, természetesen itt sincs abszolút igazság, attól függ, hogy mit akarunk csinálni. Míg a datagram egyszeri adatcsomag, D IRO\DP VWUHDP NDSFVRODWKR] IHQQ NHOO WDUWDQL D NDSFVRODWRW KX]DPRVDEE LGHLJ (EEO N|YHWNH]LN KRJ\ U|YLG ]HQHWHNHVHWpQDIRO\DPWtSXV~NDSFVRODWIHOpStWpVHpVOHERQWiVDVRNNDOW|EE LGW YHKHW HO PLQW D] ]HQHWWRYiEEtWiV PDJDtJ\QHPJD]GDViJRV$PiVLNV]iPtWiVEDYHHQGWpQ\YLV]RQWDIRO\DPWtSXV~NDSFVRODWPHJEt]KDWyViJD
Metódusok - kapcsolatot létrehozó metódusok A Socket létrehozásának menete lényegében megegyezik a ServerSocketnél leírtakkal, így V]iPRVEHiOOtWypVOHNpUGH]PHWyGXVLVPHJIHOHOD]RWWPHJLVPHUWQHN$]DOiEELDNEDQFVDN QpKiQ\IRQWRVDEEHOWpUPHWyGXVWLVPHUWHWQN
y
y
public void connect(SocketAddress endpoint, int timeout)
$ OpWUHM|WW NDSFVROyHOHPHW D V]HUYHUKH] NDSFVROMD D PHJDGRWW LGNV]|E pUWpNNHO $PHQQ\LEHQLWW]pUypUWpNHWDGXQNPHJH]PHJKDWiUR]DWODQLGNV]|E|WMHOHQWD]D]D kapcsolat blokkol, míg létre nem jön vagy hiba keletkezik. public void bind(SocketAddress bindpoint)
A kapcsolóelemet helyi címhez köti. Metódusok - információt szolgáltató és beállító metódusok Ezek segítségével kapunk információt a helyi és a kapcsolódáshoz felhasznált távoli IP
FtPUODSRUWD]RQRVtWyV]iPUyOpVDEHiOOtWyPHWyGXVRNiOOtWMiNDSocketREMHNWXPPH]LWLV
0&N|GpVNUpV]OHWHLWD6RFNHWMDYDRV]WiO\IRUUiVNyGMiEDQWDQXOPiQ\R]KDWMXNH]DOHW|OW|WW JDK kibontása után, annak könyvtárában, az src.zip állományban található meg. Az
RV]WiO\KLHUDUFKLiQDNPHJIHOHOHQH]HQEHOOLVDMDYDPDSSDQHWN|Q\YWiUiEDQWDOiOMXND csomag forrásfájljait.
y y
public InputStream getInputStream() public OutputStream getOutputStream()
(]DNpWPHWyGXVDGHNODUiFLyMXNQDNPHJIHOHOHQDV]iPXQNUDOHJIRQWRVDEEDGDWRNDWD NLpVEHPHQNDUDNWHUVRUR]DWRWDGMDYLVV]D,WWWDOiOMXNPHJYDOyMiEDQD]]HQHWHWpVD] arra küldött választ is.
0HWyGXVRNDSXIIHUWiOOtWypVOHNpUGH]PHWyGXVRN
y y y y
public public public public
synchronized synchronized synchronized synchronized
void setSendBufferSize(int size) int getSendBufferSize() void setReceiveBufferSize(int size) int getReceiveBufferSize()
Mindkét beállító metódus esetében a SocketOptions interfész konstansait állítjuk be a SocketImpl osztályon keresztül. A fogadó puffer esetében a SO_RCVBUF D NLPHQ puffer esetében a SO_SNDBUF értékét állítjuk. )HODGDW +R]]XQN OpWUH HJ\ QDJ\RQ HJ\V]HU SDUDPpWHUHLW (KKH] D]RQEDQ PHJ NHOO tUQL D]
& 6HUYHU6RFNHW SpOGiQ\W pV tUDVVXN NL D] DNWXiOLV W PHJV]yOtWy NOLHQV ROGDOL 6RFNHW SpOGiQ\ NyGMiW LV
mert a szerver csak a kapcsolat létrejöttekor, annak elfogadásakor hozza létre a kommunikációt biztosító saját Socket példányát. Fordítsuk le mindkét forráskódot és a két programot egymástól IJJHWOHQONO|QYLUWXiOLVJpSSHOIXWWDVVXN(OV
QHNDV]HUYHUWNHOOHOLQGtWDQLXWiQDDNOLHQVW
)RUGtWiV$IRUUiVNyGRWSDUDQFVVRURVIHOOHWUOLVIRUGtWKDWMXNHKKH]V]NVpJHVKRJ\D
FRPSLOHUMDYDFH[H HOpUKHWOHJ\HQD'26SDWKEyOD]D]DSDWKYiOWR]yEDQOHJ\HQEHQQHD]D könyvtár, amelyben a program megtalálható. Jelen esetben ez a c:\j2sdk1.4.2\bin könyvtár. Ebben az esetben bárhonnan indíthatjuk a javac.exe-t, de természetesen teljes elérési utat kell megadni a forrásfájlhoz. A lefordított class állomány megtalálható lesz ugyanabban a könyvtárban. A futtatás parancs kiadása már egy kicsit bonyolultabb. A java.exe ugyanis alapértelmezetten csak egy paramétert (a futtatandó osztály nevét) és nem állományt vár, de persze az osztályt WDUWDOPD]yIiMON|Q\YWiUiWLVPHJNHOODGQXQN(EEOSHUV]HD]LVN|YHWNH]LNKRJ\HJ\
N|Q\YWiUEDQQHPOHKHWNpWD]RQRVQHY&RV]WiO\PHUWDMDYDH[HQHPWXGNO|QEVpJHWWHQQL
közöttük.
ËJ\DSDUDQFVKHO\HVPHJDGiVDDN|YHWNH] java -cp könyvtárnév osztálynév Ez ebben az esetben a java -cp c:\javaprog\medzi AlapServer parancs lesz. Forráskód – AlapServer.java import java.io.*; import java.net.*; class AlapServer {
public static void main(String[] args) { System.out.println("AlapServer elindult"); try { // a szerver objektumok létrehozása, kapcsolat elfogadás ServerSocket ss = new ServerSocket(8008); Socket in = ss.accept(); // kapcsolat adatai System.out.println("IP cim: " + in.getInetAddress()); System.out.println("port szama: " + in.getLocalPort()); System.out.println("input stream: " + in.getInputStream()); System.out.println("output stream: " + in.getOutputStream()); System.out.println("send buffer: " + in.getSendBufferSize()); System.out.println("receive buffer: " + in.getReceiveBufferSize()); } catch (Exception e) { System.out.println("Hiba: " + e); } System.out.println("AlapServer leallt"); } }
Forráskód – AlapKliens.java import java.io.*; import java.net.*; class AlapKliens { public static void main(String[] args) { try { String host; // paraméterként is lehessen gazdagépet megnevezni if (args.length > 0 && args[0] != null) { host = args[0]; } else { host = "localhost"; } // a kapcsolóelem létrehozása Socket t = new Socket(host, 8008); } catch (Exception e) { System.out.println("Hiba: " + e); } } }
A futás eredménye
SOCKETIMPL OSZTÁLY Csomag: java.net
Deklaráció: public abstract class SocketImpl implements SocketOptions
.|]YHWOHQVjava.lang.Object
Fontosabb implementált interfészek: SocketOptions Ez az interfész szolgáltatja a Socket adatokat, mint például timeout és más egyebek. A SocketImpl absztrakt osztály a közös szuperosztálya mindazon osztályoknak, amelyek ténylegesen létrehozzák a kapcsolóelemeket. Ez használhatjuk mind a kliens, mind a szerver oldali kapcsolóelemek létrehozására. Egy "sima" kapcsolóelem ezeket a metódusokat pontosan úgy valósítja meg, ahogy azok OHtUiVUDNHUOWHNpVQHPNtVpUOLPHJKRJ\W&]IDORQYDJ\SUR[LV]HUYHUHQSUyEiOMRQPHJ áthatolni. 0H]N
y y y y
protected protected protected protected
FileDescriptor fd InetAddress address int port int localport
/iWKDWy KRJ\ D PH]N D]RNDW D] DGDWRNDW WDUWDOPD]]iN DPHO\HNUH D NDSFVROyHOHPHNQHN V]NVpJN YDQ D P&N|GpVN N|]EHQ D]D] D] ,3 FtP D KHO\L pV D távoli gép port azonosítója. Ehhez hozzájön még a FileDescriptor osztály példánya, amely azonosító a pufferbe írt, állományként kezelt karaktersorozatok mutatójaként szolgál.
Konstruktorok: Konstruktora nincs, hiszen absztrakt osztály. Metódusok
y y
void setSocket(Socket soc) void setServerSocket(ServerSocket soc)
Ez a két metódus állítja be, hogy a SocketImpl milyen típusú legyen, szerver, vagy NOLHQVROGDOLNDSFVROyHOHPNpQWP&N|GM|Q Természetesen a SocketImpl tartalmazza mindazon általános metódusokat, amelyeket akár a ServerSocket,
akár a SocketREMHNWXPRNP&N|GpVHNRUPHJKtYXQNKLV]HQDWpQ\OHJHV
munkát ez az osztály végzi. Ezeket itt most nem is soroljuk fel az ismétlés elkerülésére. Javasoljuk azonban a SocketImpl.java fájl tanulmányozását, mert a megjegyzésekkel, PDJ\DUi]DWRNNDOEVpJHVHQHOOiWRWWIRUUiVNyGEyOPLQGHQPHJpUWKHW
KLIENS-SZERVER ALKALMAZÁS Kis keresgélés után egy Java nyelvet oktató kolléga5 weboldalairól töltöttük le az alábbi két kis példaprogramot. Ez a program -legalább is ötletszinten - ugyanakkor megtalálható a Core Java könyvben is6. Egy-két helyen átírva a logikáját és persze az összes üzenetét, ezen
WDQXOPiQ\R]KDWMXND]DONDOPD]iVP&N|GpVpW)LJ\HOMQNDUUDKRJ\HORV]WRWWDONDOPD]iV
esetén a kliens és a szerver program önálló, azaz biztosítani kell a külön-külön futtatás
OHKHWVpJpWNO|QIiMONO|QPDLQ PHWyGXVD]D]NO|QYLUWXiOLVJpS GHDNpWSURJUDP asszimetrikusan összetartozik. Az asszimetria itt azt jelenti, hogy a szerver elindítható a kliens QpONOH]DWHUPpV]HWHVIXWiVD GHDNOLHQVSURJUDPP&N|GpVHV]HUYHUQpONOpUWHOPHWOHQ$ PHJIHOHOHQPHJtUWNOLHQVHOV]|UDV]HUYHUWSUyEiOMDHOpUQLDPHQQ\LEHQH]QHPVLNHUO hibajelzést fog adni.
$NpWHUHGHWLIRUUiVIiMOWPHJMHJ\]pVHNNHOOiWWXNHOKRJ\HJ\V]HU&HQpUWHOPH]KHWOHJ\HQ Szerverprogram - EchoServer.java //a szükséges csomagok import java.io.*; import java.net.*; class EchoServer { public static void main(String[] args) { System.out.println("EchoServer elindult"); try { //szerver kapcsolóelem objektum, amely a megadott porton figyel ServerSocket s = new ServerSocket(8008); /* elfogadja a kapcsolatot a kliens kérésére és általános kapcsolóelemet hoz létre */ Socket incoming = s.accept(); System.out.println("létrejött a kapcsolat a " + incoming.getInetAddress() +" IP számú gazdagéphez a " + incoming.getLocalPort()+ " portszámon."); //az íráshoz-olvasáshoz szükséges objektumok BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(incoming.getOutputStream())); //a kiíró objektumot fel is használjuk egy barátságos üdvözlésre out.println("Hahó! Itt a Java EchoServer.
5
Xiaoping Jia, School of Computer Science, Telecommunications, and Information Systems, De Paul University, Chicago, USA 6
Cay S. Horstmann, Gary Cornell Core Java (5. kiadás) 2. kötet 203. oldal
Kilépéshez BYE utasítás kell."); out.flush(); // végtelen ciklus, amely a szervert futtatja leállításig for (;;) {
6WULQJVWU LQUHDG/LQH EHROYDVVXNDEHM|YIRO\DPRW if (str == null) { break; // ha nem jött semmi, nem csinálunk semmit } else { /*az out objektum a kliensnek küldi vissza a paramétert, ez a kliens ablakában jelenik meg */ out.println("Ezt küldte a kliens: " + str); out.flush(); //ürítjük a puffert, ha nem tesszük, nincs kiíratás! System.out.println("Ezt kapta a szerver: " + str); } if (str.trim().equals("BYE")) //leállító utasítás break; } incoming.close(); // lezárjuk a socket objektumot } catch (Exception e) {
KDYDODPLQHPVLNHUOWPHJIHOHOKLED]HQHWHWNOGQN System.out.println("Hiba: " + e); } // Értesítést küldünk System.out.println("EchoServer leállt."); } }
Kliensprogram – EchoClient.java import java.io.*; import java.net.*; class EchoClient { public static void main(String[] args) { try { String host; /* a kliensnek paraméterként átadhatom a gazdagép nevét, alapértelmezetten ez a localhost */ if (args.length > 0 && args[0] != null) { host = args[0]; } else { host = "localhost"; } // általános célú kapcsolóelem objektum, gazdanévvel és portszámmal Socket s = new Socket(host, 8008); // olvasó-író objektumok BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream())); // legyen tíz üzenet for (int i = 1; i <= 10; i++) { System.out.println("Átküldve: " + i + ". sor"); // a ténylegesen kiküldött sor, ezt látja a szerver out.println(i + ". sor ");
// puffer ürítés out.flush(); } /* lezárjuk egy üzenettel a szervert, a szerver kódjába beleégetve a lezárást biztosító string */ out.println("BYE"); out.flush(); /* ez a végtelen ciklus figyeli a socket objektumra
pUNH]]HQHWHNHWPRVWDV]HUYHUUOpUNH]NHW for (;;) { String str = in.readLine(); if (str == null) { break; } else { // kiírjuk, ami beérkezett System.out.println(str); } } } catch (Exception e) { /* ha például nem megy a szerver, nincs kapcsolódás és itt kapunk hibaüzenetet, mint például: Hiba: java.net.ConnectException: Connection refused: connect */ System.out.println("Hiba: " + e); } } } )HODGDW )RUGtWVXN OH D NpW IRUUiVIiMOW pV IXWWDVVXN (O
V]|U WHUPpV]HWHVHQ D V]HUYHUSURJUDPRW ]HQHWHN KRQQDQ
LQGtWMXN XWiQD D NOLHQVW 9L]VJiOMXN PHJ KRJ\ D NLPHQHWHQ PHJMHOHQ
származnak, melyik jön közvetlenül a programból egy-egy System.out.println() utasításra, és melyek jönnek az in és out kommunikációs objektumokból.
A futás eredménye
Feladat: Alakítsuk át a kliens-szerver alkalmazást, hogy a kliens a konzolról beírt üzeneteket küldje HO D V]HUYHUQHN D] YLV]RQW DGMD YLVV]D H]HNHW PyGRVtWYD SpOGiXO QDJ\EHW
&V IRUPiEDQ $ OHiOOtWiV &V IRUPiEDQ
WRYiEEUD LV PHJHJ\H]pV V]HULQW D %<( ]HQHW OHJ\HQ GH EiUPLO\HQ NLV pV QDJ\EHW
megadható legyen. Ne csak a szerver álljon le a bye üzenetre, hanem a kliens program futása is
GM|QEH
IHMH]
Forráskód – AllCapServer.java import java.io.*; import java.net.*; class AllCapServer { public static void main(String[] args) { System.out.println("AllCapServer elindult"); try { // a szerver objektumok létrehozása, kapcsolat elfogadás ServerSocket s = new ServerSocket(8008); Socket incoming = s.accept(); // kapcsolat adatai System.out.println("Kapcsolat a " + incoming.getInetAddress() +
" IP cimen " + incoming.getLocalPort() + " portszamon"); // olvasó - író objektumok BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(incoming.getOutputStream())); // tájékoztató üzenet out.println("A kapott uzeneteket nagybetusre alakitjuk. Leallas bye uzenetre."); out.flush(); while (true) { // a beolvasó objektumból kivesszük az üzenetet String str = in.readLine(); if (str == null) { break; } else { /* az üzeneteket kiírjuk és utána átalakítjuk
tJpUHWQNQHNPHJIHOHOHQ System.out.println("Kapott uzenet: " + str); str = str.toUpperCase();
VWYLVV]DLVNOGMNEL]RQ\tWpNNpQW out.println("Visszakuldve: " + str); out.flush(); }
KDPHJIHOHO]HQHWHWNDSXQN, kilépünk a végtelen ciklusból... if (str.trim().equals("BYE")) break; } //és leállítjuk a szervert incoming.close(); } catch (Exception e) { System.out.println("Hiba: " + e); } System.out.println("AllCapServer leallt"); } }
Forráskód – AktivKliens.java import java.io.*; import java.net.*; class AktivKliens { private static String bemenet; // beolvasó függvény AE extra csomagja alapján private static String beolvas() { BufferedReader be = new BufferedReader(new InputStreamReader(System.in)); String beString = ""; try { beString = be.readLine(); } catch (IOException e) { System.out.println("Hiba: " + e);
} return beString; } public static void main(String[] args) { try { String host; // paraméterként is lehessen gazdagépet megnevezni if (args.length > 0 && args[0] != null) { host = args[0]; } else { host = "localhost"; } // a kapcsolóelem létrehozása Socket t = new Socket(host, 8008); // az olvasó - iró objektumok BufferedReader in = new BufferedReader(new InputStreamReader(t.getInputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(t.getOutputStream())); do { // beolvasunk a standard inputról, most DOS ablakból bemenet = beolvas(); // ezt olvastuk be és ki is írjuk System.out.println("Elkuldott uzenet: " + bemenet); // most viszont átküldjük a szerverre out.println(bemenet); //itt küldi el out.flush(); // itt fogadjuk a szerver out objektuma által küldött üzeneteket String str = in.readLine(); // és kiírjuk a standard kimenetre System.out.println(str); // amig bye üzenetet nem küldünk, erre a szerver és mi is leállunk } while (!bemenet.trim().toUpperCase().equals("BYE")); } catch (Exception e) { System.out.println("Hiba: " + e); } } }
A futás eredménye
Tg%%./,(16(*<,'(-%.(=(/e6(
+DHJ\6RFNHWDODSRQP&N|GV]HUYHUDPHO\PHJDGRWWSRUWKR]NDSFVROyGLN~MDEE
SpOGiQ\iWDNDUMXNHOLQGtWDQLDNNRUpUWKHWHQKLED]HQHWHWNDSXQNKLV]HQD]DGRWWSRUWRWHJ\ PiUIXWyDONDOPD]iVIRJODOMDHOeUGHNHVSUREOpPDKRJ\PLW|UWpQLNDNNRUKDHJ\P&N|G
V]HUYHUKH]W|EENOLHQVVHODNDUXQNNDSFVRODWRWWHUHPWHQL,WWVHPHJ\V]HU&DIHODGDWKLV]HQD
Socket alapú kommunikáció lényege a folyamatosan fenntartott kapcsolat, és ebben az
HVHWEHQHJ\~MNOLHQVSURJUDPPHJ]DYDUQiDNRPPXQLNiFLyW$]YLV]RQWHJ\V]HU&HQEHOiWKDWy hogy szálak alkalmazásával7 egymástól független kapcsolatok hozhatók létre és ezzel már meg is oldottuk - elvben - a problémát. Ehhez persze a szervert kell átalakítani, hiszen csak a V]HUYHUWXGDW|EEHJ\LGHM&NDSFVROyGiVLNpUHOHPUOD]HJ\HVNOLHQVSpOGiQ\RNQHP tudhatnak egymásról. Az alábbi példaprogram olyan szervert valósít meg, amely szálakon keresztül több klienst is
NpSHVHJ\LGHM&OHJNH]HOQL$7KUHDGRV]WiO\EyO|U|NtWHWW.OLHQV.H]HORRV]WiO\WKR]XQNOpWUH
amelynek konstruktora egy Socket típusú objektumot vesz át és egyetlen metódusa az
DODSpUWHOPH]HWWUXQ HOMiUiV(]D]HOMiUiVOpQ\HJpEHQDNRUiEEDQPiUPHJLVPHUWHJ\V]HU&
szerver teljes kódját tartalmazza, így egy teljes szerverpéldánynak felel meg. A kapcsolódási kérés beérkezésekor, az accept() metódus lefutásával létrejön a szál objektum és annak start() metódusa hívja meg a run() metódust. A korábban alkalmazott szerver leállításról most természetesen szó sem lehet, hiszen kliens oldalon nem tudhatunk arról, hogy a többi kliens mit csinál éppen. A szál viszont leállítható és az aktív szálak számát a run() metóduson belül elhelyezett activeCount() függvény adja vissza.
Forráskód – MultiAllCapServer.java import java.io.*; import java.net.*; class KliensKezelo extends Thread { protected Socket incoming; public KliensKezelo(Socket incoming) { this.incoming = incoming; } public void run() { try { // olvasó - író objektumok BufferedReader in = new BufferedReader(new InputStreamReader(incoming. getInputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(incoming. getOutputStream())); // tájékoztató üzenet out.println( "A kapott uzeneteket nagybetusre alakitjuk. Szal leall bye
7
Angster Erzsébet Objektumorientált tervezés és programozás 2. kötet xxx. oldal
uzenetre."); out.flush(); while (true) { // a beolvasó objektumból kivesszük az üzenetet String str = in.readLine(); if (str == null) { break; } else { /* az üzeneteket kiírjuk és utána átalakítjuk
tJpUHWQNQHNPHJIHOHOHQ // megszámoljuk a futó szálakat is... System.out.println("Jelenleg " + this.activeCount() + " aktiv szal fut"); System.out.println("Kapott uzenet: " + str); str = str.toUpperCase();
VWYLVV]DLVNOGMNEL]RQ\tWpNNpQW out.println("Visszakuldve: " + str); out.flush(); }
KDPHJIHOHO]HQHWHWNDSXQNNLOpSQNDYpJWHOHQFLNOXVEyO if (str.trim().equals("BYE")) break; } //és leállítjuk a szerver szálat incoming.close(); } catch (Exception e) { System.out.println("Hiba: " + e); } } } public class MultiAllCapServer { public static void main(String[] args) { System.out.println("MultiAllCapServer elindult."); try { ServerSocket s = new ServerSocket(8008); //a szerver szálakat futtató végtelen ciklus while (true) { //áll a ciklus, amíg nem jön kapcs. kérés Socket incoming = s.accept(); new KliensKezelo(incoming).start(); } } catch (Exception e) { System.out.println("Hiba: " + e); } System.out.println("MultiAllCapServer leallt."); } }