Útmutató a Java Socketekhez

1. Áttekintés

A kifejezés foglalat programozás olyan programok írására utal, amelyek több olyan számítógépen futtathatók, amelyekben az eszközök hálózaton keresztül kapcsolódnak egymáshoz.

Két kommunikációs protokoll használható a socket programozásához: Felhasználói Datagram Protokoll (UDP) és Transfer Control Protocol (TCP).

A legfőbb különbség a kettő között az, hogy az UDP kapcsolat nélküli, vagyis nincs munkamenet az ügyfél és a szerver között, miközben a TCP kapcsolatorientált, vagyis először a kliens és a szerver között kizárólagos kapcsolatot kell létrehozni a kommunikáció érdekében.

Ez az oktatóanyag bemutatja bevezetés a socketek TCP / IP-n keresztüli programozásához hálózatokon, és bemutatja, hogyan kell kliens / szerver alkalmazásokat írni Java-ban. Az UDP nem mainstream protokoll, és mint ilyen nem biztos, hogy gyakran találkozni vele.

2. Projekt beállítása

A Java olyan osztályok és interfészek gyűjteményét biztosítja, amelyek gondoskodnak az ügyfél és a szerver közötti alacsony szintű kommunikáció részleteiről.

Ezeket többnyire a java.net csomagot, ezért a következő importálást kell végrehajtanunk:

import java.net. *;

Szükségünk van a java.io csomag, amely be- és kimeneti adatfolyamokat ad számunkra, ahová kommunikáció közben írhatunk és olvashatunk:

import java.io. *;

Az egyszerűség kedvéért kliens és szerver programjainkat ugyanazon a számítógépen futtatjuk. Ha különböző hálózati számítógépeken hajtanánk végre őket, az egyetlen dolog, ami megváltozik, az az IP-cím, ebben az esetben helyi kiszolgáló tovább 127.0.0.1.

3. Egyszerű példa

A legtöbbet mocskoljuk be a kezünket klienseket és szervereket magában foglaló példák alapjai. Ez egy kétirányú kommunikációs alkalmazás lesz, ahol az ügyfél üdvözli a szervert, és a szerver válaszol.

Hozzuk létre a kiszolgáló alkalmazást egy úgynevezett osztályban GreetServer.java a következő kóddal.

Beleértve a fő- módszer és a globális változók felhívják a figyelmet arra, hogyan fogjuk futtatni az összes szervert ebben a cikkben. A cikkek többi példájában kihagyjuk az ilyen jellegű, ismétlődő kódokat:

nyilvános osztály GreetServer {private ServerSocket serverSocket; privát Socket kliensSocket; saját PrintWriter ki; privát BufferedReader in; public void start (int port) {serverSocket = új ServerSocket (port); clientSocket = serverSocket.accept (); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ())); Karakterlánc üdvözlet = in.readLine (); if ("hello server" .egyenlő (üdvözlet)) {out.println ("hello kliens"); } else {out.println ("fel nem ismert üdvözlet"); }} public void stop () {in.close (); out.close (); clientSocket.close (); serverSocket.close (); } public static void main (String [] args) {GreetServer szerver = new GreetServer (); server.start (6666); }}

Hozzunk létre egy nevű klienst is GreetClient.java ezzel a kóddal:

public class GreetClient {private Socket clientSocket; saját PrintWriter ki; privát BufferedReader in; public void startConnection (String ip, int port) {clientSocket = új Socket (ip, port); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ())); } public String sendMessage (String msg) {out.println (msg); Karakterlánc ill = in.readLine (); visszatérés ill; } public void stopConnection () {in.close (); out.close (); clientSocket.close (); }}

Indítsuk el a szervert; IDE-jében ezt egyszerűen Java alkalmazásként futtatja.

Most pedig küldjünk üdvözletet a kiszolgálónak egy egység teszt segítségével, amely megerősíti, hogy a szerver valóban üdvözletet küld válaszként:

@Test public void givenGreetingClient_whenServerRespondsWhenStarted_thenCorrect () {GreetClient client = new GreetClient (); client.startConnection ("127.0.0.1", 6666); Karakterlánc-válasz = client.sendMessage ("hello szerver"); assertEquals ("hello client", válasz); }

Ne aggódjon, ha nem teljesen érti, mi történik itt, mivel ez a példa arra hivatott, hogy átérezzük, mire számíthatunk a cikk későbbi részében.

A következő szakaszokban boncolgatni fogjuk socket kommunikáció használva ezt az egyszerű példát, és merüljön el mélyebben a részletekben további példákkal.

4. Hogyan működnek a foglalatok

A fenti példán keresztül áttekinthetjük a szakasz különböző részeit.

Definíció szerint a foglalat a hálózat két számítógépén futó két program közötti kétirányú kommunikációs kapcsolat egyik végpontja. Egy aljzat egy portszámhoz van kötve, hogy a szállítási réteg azonosítani tudja azt az alkalmazást, amelynek az adatokat elküldi.

4.1. A szerver

Általában a szerver egy adott számítógépen fut a hálózaton, és rendelkezik egy foglalattal, amely egy adott portszámhoz van kötve. Esetünkben ugyanazt a számítógépet használjuk, mint az ügyfél, és a kiszolgálót a porton indítottuk 6666:

ServerSocket serverSocket = új ServerSocket (6666);

A szerver csak vár, és hallgatja a socketet, hogy az ügyfél csatlakozási kérelmet küldjön. Ez a következő lépésben történik:

Socket clientSocket = serverSocket.accept ();

Amikor a szerver kód találkozik a elfogad metódust, addig blokkol, amíg az ügyfél csatlakozási kérelmet nem küld hozzá.

Ha minden jól megy, a szerver elfogadja a kapcsolat. Az elfogadás után a szerver új foglalatot kap, clientSocket, ugyanahhoz a helyi kikötőhöz kötve, 6666, és a távoli végpontját az ügyfél címére és portjára is beállította.

Ezen a ponton az új Foglalat Az objektum a kiszolgálót közvetlen kapcsolatba hozza az ügyféllel, majd hozzáférhetünk a kimeneti és a bemeneti adatfolyamhoz, hogy üzeneteket írhassunk és fogadhassunk az ügyféltől, illetve onnan:

PrintWriter out = new PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ()));

Innentől kezdve a szerver képes végtelenül üzeneteket cserélni az ügyféllel, amíg a socket nem zár le a folyamaival.

Példánkban azonban a szerver csak a kapcsolat lezárása előtt küldhet üdvözlő választ, ez azt jelenti, hogy ha újra lefuttatnánk a tesztünket, akkor a kapcsolatot megtagadnánk.

A kommunikáció folyamatosságának lehetővé tétele érdekében le kell olvasnunk az a belsejében lévő bemeneti adatfolyamról míg ciklus és csak akkor lépjen ki, amikor az ügyfél felmondási kérelmet küld, ezt a következő szakaszban látjuk működés közben.

Minden új klienshez a kiszolgálónak szüksége van egy új foglalatra, amelyet a elfogad hívás. A serverSocket arra használják, hogy továbbra is figyelje a csatlakozási kérelmeket, miközben megfelel a csatlakoztatott kliensek igényeinek. Az első példánkban ezt még nem engedtük meg.

4.2. Az ügyfél

Az ügyfélnek ismernie kell annak a gépnek a gazdagépnevét vagy IP-jét, amelyen a kiszolgáló fut, és a portszámot, amelyen a kiszolgáló figyel.

Csatlakozási kérelem benyújtásához az ügyfél megpróbál találkozni a kiszolgálóval a kiszolgáló gépén és portján:

Socket clientSocket = új Socket ("127.0.0.1", 6666);

Az ügyfélnek azonosítania kell magát a kiszolgálóhoz is, hogy az kapcsolódjon a rendszer által hozzárendelt helyi portszámhoz, amelyet a kapcsolat során használni fog. Magunk nem foglalkozunk ezzel.

A fenti konstruktor csak akkor hoz létre új foglalatot, ha a szerver rendelkezik elfogadott a kapcsolat, különben kapunk egy elutasított kapcsolatot. Sikeres létrehozás után be- és kimeneti adatfolyamokat szerezhetünk belőle, hogy kommunikálhassunk a szerverrel:

PrintWriter out = new PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ()));

Az ügyfél bemeneti adatfolyamja a szerver kimeneti adatfolyamához van csatlakoztatva, ugyanúgy, mint a szerver bemeneti folyama az ügyfél kimeneti adatfolyamához.

5. Folyamatos kommunikáció

Jelenlegi kiszolgálónk blokkolódik, amíg az ügyfél csatlakozik hozzá, majd ismét blokkol, hogy meghallgassa az ügyfél üzenetét, az egyetlen üzenet után bezárja a kapcsolatot, mert nem folytattuk a folyamatosságot.

Tehát ez csak a ping kéréseknél hasznos, de képzelje el, hogy szeretnénk megvalósítani egy chat szervert, a szerver és az ügyfél közötti folyamatos oda-vissza kommunikációra mindenképpen szükség lenne.

Létre kell hoznunk egy darab ciklust, hogy folyamatosan figyeljük a szerver bemeneti adatfolyamát a bejövő üzenetek esetén.

Hozzunk létre egy új szervert EchoServer.java amelynek egyetlen célja visszhangozni az ügyfelektől kapott üzeneteket:

public class EchoServer {public void start (int port) {serverSocket = új ServerSocket (port); clientSocket = serverSocket.accept (); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". egyenlő (inputLine)) {out.println ("viszlát"); szünet; } out.println (inputLine); }}

Figyelje meg, hogy hozzáadtunk egy befejezési feltételt, ahol a while ciklus kilép, amikor periódus karaktert kapunk.

El fogjuk kezdeni EchoServer a fő módszert használva, ugyanúgy, mint a GreetServer. Ezúttal egy másik porton indítjuk, például 4444 a zavart elkerülése érdekében.

A EchoClient hasonló GreetClient, így lemásolhatjuk a kódot. Az egyértelműség érdekében szétválasztjuk őket.

Egy másik tesztosztályban létrehozunk egy tesztet, amely megmutatja, hogy több kérés érkezett a EchoServer a kiszolgáló bezárja a foglalatot. Ez mindaddig igaz, amíg ugyanattól az ügyféltől küldünk kéréseket.

Több ügyféllel való foglalkozás egy másik eset, amelyet egy következő szakaszban fogunk látni.

Hozzunk létre egy beállít módszer a szerverrel való kapcsolat létrehozására:

@A nyilvános void beállítása előtt () {client = new EchoClient (); client.startConnection ("127.0.0.1", 4444); }

Egyformán létrehozunk egy tearDown módszer az összes erőforrás felszabadítására, ez a legjobb gyakorlat minden olyan esetben, amikor hálózati erőforrásokat használunk:

@A nyilvános void után tearDown () {client.stopConnection (); }

Ezután teszteljük az echo szervert néhány kéréssel:

@Test public void givenClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hello"); Karakterlánc resp2 = client.sendMessage ("világ"); Karakterlánc resp3 = client.sendMessage ("!"); Karakterlánc resp4 = client.sendMessage ("."); assertEquals ("hello", resp1); assertEquals ("világ", ill2); assertEquals ("!", resp3); assertEquals ("viszlát", ill4); }

Ez egy javulás a kezdeti példához képest, ahol csak egyszer kommunikáltunk, mielőtt a szerver lezárta a kapcsolatunkat; most befejező jelet küldünk, hogy elmondjuk a szervernek, ha befejeztük a munkamenetet.

6. Több klienssel rendelkező szerver

Akárcsak az előző példa javulás volt az elsővel szemben, még mindig nem olyan nagyszerű megoldás. A szervernek képesnek kell lennie arra, hogy egyszerre számos ügyfelet és sok kérést kiszolgáljon.

Több ügyfél kezelésével foglalkozunk ebben a szakaszban.

Egy másik funkció, amelyet itt látni fogunk, az az, hogy ugyanaz az ügyfél le tud kapcsolódni és újra csatlakozni, anélkül, hogy a szerveren egy elutasított kapcsolatot vagy egy kapcsolat visszaállítását kapná. Korábban nem voltunk képesek erre.

Ez azt jelenti, hogy kiszolgálónk robusztusabb és rugalmasabb lesz, ha több kérést kér több ügyféltől.

Hogyan fogjuk ezt megtenni, új socket-t kell létrehozni minden új ügyfélhez és szolgáltatáshoz, amelyet az ügyfél más szálon kér. Az egyidejűleg kiszolgált ügyfelek száma megegyezik a futó szálak számával.

A fő szál egy ideig fut, miközben új kapcsolatokat hallgat.

Elég beszéd, hozzunk létre egy másik kiszolgálót EchoMultiServer.java. Belül létrehozunk egy kezelő szálosztályt, amely az egyes kliensek kommunikációját kezeli a foglalatán:

public class EchoMultiServer {private ServerSocket serverSocket; public void start (int port) {serverSocket = új ServerSocket (port); while (true) új EchoClientHandler (serverSocket.accept ()). start (); } public void stop () {serverSocket.close (); } privát statikus osztály, az EchoClientHandler kiterjeszti a Thread {private Socket clientSocket; magán PrintWriter ki; privát BufferedReader in; public EchoClientHandler (Socket socket) {this.clientSocket = socket; } public void run () {out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (új InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". egyenlő (inputLine)) {out.println ("viszlát"); szünet; } out.println (inputLine); } in.close (); out.close (); clientSocket.close (); }}

Figyelje meg, hogy most hívunk elfogad belül a míg hurok. Minden alkalommal, amikor a míg a ciklus végrehajtásra kerül, blokkolja a elfogad hívjon, amíg egy új ügyfél csatlakozik, majd a kezelő szál, EchoClientHandler, létrejön ehhez az ügyfélhez.

A szál belsejében az történik, amit korábban a EchoServer ahol csak egyetlen ügyfelet kezeltünk. Így a EchoMultiServer delegálja ezt a munkát EchoClientHandler hogy továbbra is hallgasson több ügyfelet a míg hurok.

Mi továbbra is használni fogjuk EchoClient A szerver teszteléséhez ezúttal több klienst hozunk létre, amelyek mindegyike több üzenetet küld és fogad a szervertől.

Indítsuk el szerverünket a porton lévő fő módszerével 5555.

Az egyértelműség kedvéért továbbra is teszteket fogunk elhelyezni egy új csomagban:

@Test public void givenClient1_whenServerResponds_thenCorrect () {EchoClient ügyfél1 = új EchoClient (); client1.startConnection ("127.0.0.1", 5555); Karakterlánc msg1 = client1.sendMessage ("hello"); Karakterlánc msg2 = client1.sendMessage ("világ"); Karaktersorozat befejezése = client1.sendMessage ("."); assertEquals (msg1, "hello"); assertEquals (msg2, "világ"); assertEquals (megszüntet, "viszlát"); } @Test public void givenClient2_whenServerResponds_thenCorrect () {EchoClient ügyfél2 = új EchoClient (); client2.startConnection ("127.0.0.1", 5555); Karakterlánc msg1 = client2.sendMessage ("hello"); Karakterlánc msg2 = client2.sendMessage ("világ"); Karaktersorozat befejezése = client2.sendMessage ("."); assertEquals (msg1, "hello"); assertEquals (msg2, "világ"); assertEquals (megszüntet, "viszlát"); }

Hozhatunk létre annyi tesztesetet, amennyit csak akarunk, mindegyik új klienst hoz létre, és a szerver mindegyiket kiszolgálja.

7. Következtetés

Ebben az oktatóanyagban arra összpontosítottunk bevezetés a socketek TCP / IP feletti programozásához és írt egy egyszerű Client / Server alkalmazást Java-ban.

A cikk teljes forráskódja - szokás szerint - a GitHub projektben található.