7. A hálózatkezelés osztályai
7.2.2 Összeköttetés-alapú kommunikáció megvalósítása
Az összeköttetés-alapú kommunikáció szerver oldalát Javában a ServerSocket osztály valósítja meg, ez egy TCP kommunikációs port abszt-rakciója. Két konstruktorát szokás használni:
ServerSocket(int port): egy egész számként meg kell adni a szerver számára lefoglalandó TCP-port számát
ServerSocket(int port, int sm): első paraméter itt is a portszám, a második a szerver várakozási sorának mérete, vagyis hogy a szerverrel való kommunikációra mennyi kliens várakozhat. Az alapér-telmezés szerinti érték 50.
A konstruktor egy IOException kivételt dobhat, amelynek lehetséges oka, hogy a lefoglalandó TCP-port már használatban van egy másik alkalmazás által, esetleg beteltek a rendszererőforrások és nincs lehetőség újabb TCP-port foglalására, az is előfordulhat, hogy némely operációs rendszer nem támogatja a hálózati protokollok elérését. Amennyiben a konstruktor sikerrel végrehajtó-dott, a kliensek már csatlakozhatnak a szerverhez.
A szerverhez csatlakozni akaró kliensek érkezési sorrendben egy várakozási sorba kerülnek, a szerver ebből a sorból az első klienssel hozhat létre kapcsola-tot a ServerSocket objektum accept metódusával. Amennyiben a várako-zási sor üres, akkor a metódus hívására a szerver addig vár, amíg egy kliens csatlakozni nem akar. Az accept metódus egy Socket objektumot ad vissza, ez valósítja meg a szerver-kliens közötti adatátviteli csatornának az egyik végét.
A socket a Javában egy végpontját jelenti egy kétirányú kommunikációs hálózati kapcsolatnak. A sockethez mindig tartozik egy IP-cím és egy portszám. A portszám alapján lehetőség van az adatcsomagok megfelelő alkalmazáshoz való juttatására. A Socket objektum rendelkezik egy getInputStream és egy getOutputStream metódussal, amelyekkel hozzáférhetünk a kommunikáci-ós csatorna feletti I/O-műveletekhez, ezekkel valkommunikáci-ósítható meg az adatátvitel a szerver és a kliens között. A szerveralkalmazás működésének befejezésekor meg kell hívni a ServerSocket objektum close metódusát, amely felsza-badítja a lefoglalt hálózati erőforrásokat (általában az operációs rendszer az alkalmazás befejezése után a close nélkül is felszabadítja az alkalmazás által lekötött hálózati erőforrásokat, de célszerű ezt nekünk megtenni). Egy szerver esetén különösen fontos, hogy az erőforrásokkal a lehető legjobban gazdálkod-junk!
Az kliens oldalon a szükséges szolgáltatásokat szintén a korábban említett Socket osztály valósítja meg. A leggyakrabban alkalmazott konstruktorában
meg kell adni a kommunikációs partner címét és portját. A cím lehet számító-gépnév és InetAddress objektummal megadott cím is. Az InetAddress osztály alkalmas a hálózatba kötött számítógépek IP-címének azonosítására.
Egy InetAddress objektum létrehozásához használhatjuk a következő stati-kus metódusokat:
InetAddress getLocalHost(): a saját számítógépünk címét reprezentálja
InetAddress getByName(String name): a paraméterben megadott számítógép címét reprezentálja, a name lehet pl. IP-cím is
InetAddress[] getAllByName(String name): a paramé-terben megadott számítógép összes címét visszaadja egy tömbben A Socket osztály néhány hasznos metódusa a korábban említett getInputStream és getOutputStream mellett:
InetAddress getLocalAddress(): a kommunikációs végpont-ként szolgáló számítógép (vagyis a szerver) címét adja vissza
InetAddress getInetAddress(): a kommunikációs partner (kliens) címét adja vissza
int getLocalPort(): a kommunikációs végpont portját adja visz-sza
int getPort(): a kommunikációs partner portját adja vissza
Amennyiben a kommunikációs kapcsolatra a továbbiakban nincs szükség, itt is meg kell hívni a close metódust a rendszererőforrások felszabadításá-hoz.
A következő feladat egy olyan összeköttetés-alapú szerver-kliens alkalma-zás megvalósítása, ahol a szerver meghatározza a kliens által küldött szám prím-tényezős felbontását, és természetesen ezt a kliens felé továbbítja is. A szervert párhuzamos szerkezetűként valósítjuk meg. Nézzük a szerverért felelős kódot, és utána rátérünk a magyarázatra:
1 public class TCPServerThreaded {
2 public static void main(String[] args) {
10 }
11 } catch (IOException ex) {}
12 } 13 }
14 class ServerThread extends Thread { 15 private Socket s;
42 public static String primeResolution(int value) {
49 }
A szerverünk a 9980-as porton fog futni. Elindítjuk a szervert (6. sor), majd várakozunk egy kliens kapcsolódási kérelmére az ss.accept()-tel. A szerve-rünk nem fog leállni a while(true) miatt. Be lehetne vezetni egy logikai változót a true helyett, amelynek valamilyen feltételtől függően változna az értéke, és a szerverünk leállna. Az accept által visszaadott sockettel elindí-tunk egy ServerThread szálat.
A ServerThread osztályt a Thread-ből származtatjuk (a párhuzamos programozásról késbb lesz szó), ez teszi lehetővé, hogy osztályunk egy példá-nyát programszálként futtassuk. A konstruktor egy Socket objektumot vár paraméterként. A start() (9. sor) hatására a ServerThread példányunk run() metódusa fog elindulni. Az input és output változókban eltároljuk a sockethez tartozó bemeneti és kimeneti I/O-csatornákat, majd az in-put.readLine() segítségével beolvasunk egy sort a socketről, amelyet a kliens küldött. A parseInt metódussal int-té alakítjuk (NumberFormatException kivétel váltódhat ki), majd a kimenetre (out-put) írjuk az átalakított érték prímtényezős felbontását, amelyet majd a kliens olvas be. A szerverünk ezzel elvégezte feladatát, a finally blokkban lezárjuk az I/O-csatornákat és a socketet. A ServerThread-nek van még egy statikus primeResolution metódusa, amely egy int érték prímtényezős felbontá-sát adja vissza sztringként.
Nézzük meg a kliensoldali alkalmazást:
1 public class TCPClient {
2 public static void main(String[] args) {
12 output=new egy helyi hálózatba tartozó gép IP-címe lesz (ezen fut a szerver). A try blokk-ban létrehozunk egy InetAddress példányt a socket példányosításához, lekérjük a hozzá tartozó I/O csatornákat, majd a szerver felé elküldjük a 258-as értéket (ennek kapjuk majd vissza a prímtényezős felbontását), majd ezután az input-ról beolvasunk egy sort, amely a szerver felől érkezik. A flush() azért kell, hogy a kimenetre írandó érték ne maradjon a pufferban. Végül a finally blokkban itt is lezárjuk az I/O-csatornákat és a socketet.
Hasznos tudni, hogy a szerver-kliens kommunikáció egyes lépéseinél meg lehet határozni időkorlátot. Ilyen lépés pl. a ServerSocket accept metó-dusa, vagy a Socket osztály getInputStream és getOutputStream metódusaival létrehozott adatcsatornák read metódusa, amely egy beolvasha-tó adat érkezéséig vár. Az időkorlát megadásával egy maximális várakozási időt állíthatunk be ezredmásodpercben, ezt a setSoTimeout(int i) metódus-sal tehetjük meg.