A Jáva hálózati könyvtára (java.net) jelenleg a TCP/IP protokollcsaládra épül - ez a szorosan vett Internet hálózat alapja is -, bár ennek részleteit csaknem teljesen elfedi a programozó elõl, így elvileg nem kizárt, hogy más alapprotokollokat használó könyvtár-implementációk is megjelenjenek. Ez a cikk korlátozott terjedelme miatt mindössze a szállítási szintû protokollok közül a TCP kapcsolatorientált protokoll használatával foglalkozik, de érdemes megemlíteni, hogy a java.net könyvtár a datagrammokra épülõ UDP protokollt is támogatja. Ezeken túl jelenleg az alkalmazói szintû protokollok közül csak a Web alapját jelentõ HTTP (Hypertext Transfer Protocol) protokoll közvetlen használatát támogatja, de lehetõséget ad a programozóknak, hogy egyéb kezelõ eljárásokat (protocol handler) is beilleszthessen a rendszerbe. A programkákhoz tartozó néhány speciális hálózattal kapcsolatos tevékenység is megtalálható vagy a könyvtárakban, vagy magában a virtuális gépben.
Az alkalmazások egy részének nincs szüksége ekkora megbízhatóságra, ezek beérik az UDP (User Datagram Protocol) protokoll összeköttetés-mentes szolgáltatásával. Ebben az esetben a könyvtárak és a hálózati architektúra mindössze annyit ígér, hogy egy - nem túl nagy - csomagba összepakolt Byte-okat egyben elküldi a címzett felé, és mindent megtesz azért, hogy az odataláljon, az elõforduló hibák kiküszöbölése érdekében viszont különösképpen nem fáradozik.
Mivel mindkét esetben konkrét programok kommunikálnak, ezek egymásra találásához a számítógéphez tartozó egyedi hálózati címet - az ún. IP címet - ki kell egészítenünk a gépeken futó programok megkülönböztetésére szolgáló címmel. A TCP/IP hálózati rendszerben nem a programokat, hanem a kommunikációra szolgáló kapcsolódási pontokat, az ún. kapukat (port) azonosítjuk. Az elsõ 1024 kapu cím használatát általában korlátozzák, csak rendszergazda jogosultságú programok olvashatják, a többi a "mezei" felhasználók rendelkezésére áll. A kapuk egy része, az ún. jól ismert kapuk (well-known ports) minden számítógépen szabványos funkciókat betöltõ programokhoz tartoznak.
Egy tipikus szerver mûködésének lépései:
A kliens oldali program még ennél is egyszerûbb. Elõször felvesszük a kapcsolatot az adott Internet címmel (vagy névvel) rendelkezõ számítógép megfelelõ kapuján várakozó kiszolgáló programmal:
Innen a lépések megegyeznek a szerver oldal 3-5-ös lépéseivel.
Megjegyzés: terjedelmi korlátok miatt a programot kissé megkurtítottam. A hálózati hibák részletesebb kezelése és a szerver kulturált leállítása szenvedte a legtöbb csorbát.
import java.net.*; import java.io.*; import java.util.*;Az osztály minden egyes példánya megvalósít egy TCP szervert. Ehhez párhuzamos szálat indítunk el, hogy ne kelljen a konstruktorban várakozni mindaddig, amíg a szerver le nem áll.
public class TCPListener implements Runnable { private ServerSocket server; private int port; private TCPServerFactory factory; private Thread listener;A konstruktor megkapja annak a kapunak a címét, ahol a szerver a kapcsolatokat várja. A második paraméter egy speciális objektum, amely képes arra, hogy TCPServer típusú objektumokat állítson elõ (ezért nevezik gyárnak, factory-nak). Ezek az objektumok a figyelõ szállal párhuzamosan bonyolítják majd az egyes kliensekkel a kapcsolatot. A konstruktor a paraméterek egyedváltozókba tárolása után elindítja a kapcsolatra figyelõ szálat.
public TCPListener(int port, TCPServerFactory factory) { this.port = port; this.factory = factory; listener = new Thread(this); listener.start(); } public void run () { try {A hálózati program elsõ lépései az általános modellt követik. A szerver kapu létrehozása után a kiszolgáló végtelen ciklusban accept-tal egy kliens által kezdeményezett kapcsolatra vár. Ha ez megjött, a kiszolgálógyárral elkészíttetünk egy újabb kiszolgáló objektumot.
server = new ServerSocket(port); while (true) { Socket socket = server.accept(); TCPServer server = factory.createServer(socket); }A hálózati kapcsolat kialakítás közben elõforduló összes hibát együttesen kezeljük - ebben az esetben elhanyagoljuk -, a kiszolgáló leáll.
} catch (IOException e) {} shutdown(); }A shutdown módszer feladata a figyelõ szál és az élõ kiszolgálók teljes leállítása.
public void shutdown() { if (listener != null) listener.stop(); listener = null; } }
import java.net.*; import java.io.*; abstract public class TCPServer implements Runnable { private Socket socket = null; private Thread life = null; protected DataInputStream in = null; protected PrintStream out = null; protected TCPServer (Socket s) throws IOException { socket = s;Az osztály konstruktora egyedváltozókban tárolja a paramétereit, majd megszerzi a kiépült kapcsolatot jellemzõ ki- és bemeneti adatfolyamot.
Érdemes megfigyelni, hogy a get…Stream eljárásokból visszakapott egyszerû adatfolyamokat a kényelmesebb kezelésük érdekében különbözõ szûrõkön vezetjük át. A Data… és Print… osztályok a beépített Jáva adattípusok közvetlen be/kivitelét valósítják meg.
in = new DataInputStream(socket.getInputStream())); out = new PrintStream(socket.getOutputStream()),true);Az új szálból elindulása elõtt démont csinálunk, azaz olyan szálat amelyet a szerver leállásánál a Jáva virtuális gép magától leállít.
life = new Thread(this); life.setDaemon(true); life.start(); }A kiszolgáló tevékenysége a run eljárásban zajlik. Ez nem más, mint egy általános handleSession módszer meghívása. Az esetleges hibákat a program elkapja, de nem kezeli, hiszen nem is tudna velük mit kezdeni.
public void run() { try { handleSession(); } catch(IOException e) {}; shutdown(); }A szerver lezárása egyszerû, mind a nyitott adatfolyamokat, mind a socket-et lezárjuk.
public void shutdown() { try { if (in != null) in.close(); if (out != null) out.close(); if (socket != null) socket.close(); } catch (IOException e) { } }Szeretnénk a szerverünket általánosan megírni, amely kezeli lehetõleg az egyes kiszolgáló szálak adminisztrációjának minden részletét, a programozónak csak a kapcsolat konkrét lebonyolításával kell foglalkoznia. Erre szolgál az itt következõ handleSession absztrakt módszer. Ezért a TCPServer osztályunk is absztrakt, kell hogy legyen, de az ebbõl leszármaztatott osztályok majd definiálhatják a szükséges módszer törzsét.
A módszer belsejében használhatjuk a nyitott in és out adatfolyamokat. A módszer lenyomatának tanúsága szerint a belsejében az átviteli hibák nem kell foglalkoznunk, ezt majd a felsõ szint (az elõbbi run módszer) kezeli majd.
abstract public void handleSession() throws IOException; }
Jávában az ilyen általános osztályokat interfészek segítségével is megadhatjuk. Esetünkben ez különösen kézenfekvõ megoldás, mert a módszer belsejét amúgy sem tudnánk itt megírni (TCPServerFactory.java).
import java.net.*; interface TCPServerFactory { TCPServer createServer(Socket socket); }
import java.io.*; import java.net.*;A szerverünket az általános TCPServer osztályból származtatjuk le. Csak egy konstruktort illetve az absztrakt handleSession módszert kell megírnunk. Hasonló könnyedséggel írhatunk bonyolultabb kiszolgálókat is.
class EchoServer extends TCPServer {A konstruktor egyszerûen lehívja a szülõ konstruktorát.
EchoServer (Socket socket) throws IOException { super(socket); }A szerver szálak tényleges tevékenységét itt kell megadnunk. Mivel a módszer belsejében már hozzáférhetünk a 2 megnyitott adatfolyamhoz, egyszerûen csak az egyikrõl olvasunk egy sort, majd kiírjuk a másikra mindaddig, amíg - kis és nagybetûket tetszõlegesen tartalmazó - "EXIT" szöveg nem érkezik. Az egyedül arra érdemes figyelni, hogy a kapcsolat elvesztésével elõfordul, hogy szöveg helyett null-t olvasunk.
public void handleSession() throws IOException { String s; do { s = in.readLine(); if (s != null) out.println(s); } while (!(s == null || s.toUpperCase().equals("EXIT"))); }Szerverünk kipróbálásához elindítunk egy többszálú szerver-csonkot az 5555-ös kapun, megadva neki egy olyan "gyárat", amely termeli majd a kiszolgáló objektumainkat.
public static void main (String[] args) { new TCPListener(5555, new EchoServerFactory()); } }A gyár definiálása hasonlóan egyszerû, hiszen csak egyetlen módszert kell megadnunk. A termelõ eljárás egyszerûen lehívja a szerver osztály konstruktorát. Ha ez esetleg hibát okoz, akkor a termelõ null-lal tér majd vissza.
class EchoServerFactory implements TCPServerFactory { public TCPServer createServer(Socket socket) { try { return new EchoServer(socket); } catch (IOException e) { return null; } } }A szerver kipróbálásához magyarázat nélkül közlöm egy egyszerû kliens program (EchoClient.java) forrását. A program követi a cikk elején az általános kliens mûködésnél leírtakat.
import java.net.*; import java.io.*; class EchoClient { public static void main (String[] args) { Socket socket = null; DataInputStream in = null, console = null; PrintStream out = null; String s; try { socket = new Socket(args[0], 5555); in = new DataInputStream( new BufferedInputStream(socket.getInputStream())); out = new PrintStream( new BufferedOutputStream(socket.getOutputStream()), true); console = new DataInputStream(System.in); System.out.println("Echo client started, type exit to finish!"); do { out.println(console.readLine()); s = in.readLine(); } while (!( s == null || s.toUpperCase().equals("EXIT"))); if (in != null) in.close(); if (out != null) out.close(); if (socket != null) socket.close(); } catch (IOException e) {}; } }
updated: 97/05/01, http://www.eunet.hu/infopen/cikkek/java/network.html