Sprievodca zásuvkami Java

1. Prehľad

Termín zásuvka programovanie označuje písanie programov, ktoré sa vykonávajú na viacerých počítačoch, v ktorých sú všetky zariadenia navzájom spojené pomocou siete.

Existujú dva komunikačné protokoly, ktoré je možné použiť na programovanie zásuviek: User Datagram Protocol (UDP) a Transfer Control Protocol (TCP).

Hlavný rozdiel medzi nimi spočíva v tom, že UDP je bez pripojenia, čo znamená, že medzi klientom a serverom neexistuje žiadna relácia, zatiaľ čo protokol TCP je orientovaný na pripojenie, čo znamená, že pre komunikáciu musí byť medzi klientom a serverom najskôr nadviazané exkluzívne spojenie.

Tento návod predstavuje úvod do programovania zásuviek cez TCP / IP sietí a demonštruje, ako písať aplikácie klient / server v prostredí Java. UDP nie je hlavný protokol a ako taký sa s ním nemusí často stretnúť.

2. Nastavenie projektu

Java poskytuje kolekciu tried a rozhraní, ktoré sa starajú o podrobnosti komunikácie na nízkej úrovni medzi klientom a serverom.

Väčšinou sú obsiahnuté v java.net balíka, takže musíme vykonať nasledujúci import:

import java.net. *;

Potrebujeme tiež java.io balík, ktorý nám poskytuje vstupné a výstupné toky, do ktorých môžeme počas komunikácie zapisovať a čítať:

import java.io. *;

Z dôvodu jednoduchosti spustíme naše klientske a serverové programy na rovnakom počítači. Ak by sme ich mali vykonať na rôznych počítačoch v sieti, zmenila by sa iba IP adresa, v tomto prípade použijeme localhost na 127.0.0.1.

3. Jednoduchý príklad

Zašpiníme si tým najviac ruky základné príklady týkajúce sa klienta a servera. Bude to obojsmerná komunikačná aplikácia, v ktorej klient pozdraví server a server odpovie.

Vytvorme serverovú aplikáciu v triede s názvom GreetServer.java s nasledujúcim kódom.

Zahŕňame hlavný metóda a globálne premenné upriamiť pozornosť na to, ako budeme v tomto článku spúšťať všetky servery. V ďalších príkladoch v článkoch tento druh opakujúceho sa kódu vynecháme:

verejná trieda GreetServer {private ServerSocket serverSocket; private Socket clientSocket; vypíše sa súkromný PrintWriter; privátny BufferedReader v; public void start (int port) {serverSocket = nový ServerSocket (port); clientSocket = serverSocket.accept (); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (nový InputStreamReader (clientSocket.getInputStream ())); Reťazcový pozdrav = in.readLine (); if ("ahoj server" .equals (pozdrav)) {out.println ("ahoj klient"); } else {out.println ("nerozpoznany pozdrav"); }} public void stop () {in.close (); out.close (); clientSocket.close (); serverSocket.close (); } public static void main (String [] args) {server GreetServer = nový GreetServer (); server.start (6666); }}

Vytvorme tiež klienta s názvom GreetClient.java s týmto kódom:

verejná trieda GreetClient {private Socket clientSocket; vypíše sa súkromný PrintWriter; privátny BufferedReader v; public void startConnection (reťazec ip, int port) {clientSocket = new Socket (ip, port); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (nový InputStreamReader (clientSocket.getInputStream ())); } public String sendMessage (String msg) {out.println (msg); Reťazec resp = in.readLine (); vrátiť resp; } public void stopConnection () {in.close (); out.close (); clientSocket.close (); }}

Spustíme server; vo svojom IDE to urobíte jednoduchým spustením ako Java aplikácie.

Teraz pošleme pozdrav na server pomocou testu jednotky, ktorý potvrdí, že server skutočne odošle pozdrav ako odpoveď:

@Test public void givenGreetingClient_whenServerRespondsWhenStarted_thenCorrect () {GreetClient client = new GreetClient (); client.startConnection ("127.0.0.1", 6666); Reťazcová odpoveď = client.sendMessage ("ahoj server"); assertEquals ("ahoj klient", odpoveď); }

Nerobte si starosti, ak úplne nerozumiete tomu, čo sa tu deje, pretože tento príklad nám má poskytnúť predstavu o tom, čo môžeme čakať ďalej v článku.

V nasledujúcich častiach si to rozoberieme soketová komunikácia pomocou tohto jednoduchého príkladu a ponorte sa hlbšie do podrobností s ďalšími príkladmi.

4. Ako fungujú zásuvky

Vyššie uvedený príklad použijeme na prechádzanie rôznymi časťami tejto časti.

Podľa definície a zásuvka je jeden koncový bod obojsmerného komunikačného spojenia medzi dvoma programami bežiacimi na rôznych počítačoch v sieti. Soket je viazaný na číslo portu, aby transportná vrstva mohla identifikovať aplikáciu, do ktorej sú údaje určené na odoslanie.

4.1. Server

Server zvyčajne beží na konkrétnom počítači v sieti a má soket, ktorý je viazaný na konkrétne číslo portu. V našom prípade použijeme rovnaký počítač ako klienta a server sme spustili na porte 6666:

ServerSocket serverSocket = nový ServerSocket (6666);

Server len čaká a počúva soket, aby klient urobil žiadosť o pripojenie. To sa stane v ďalšom kroku:

Socket clientSocket = serverSocket.accept ();

Keď serverový kód narazí na súhlasiť metóda blokuje, kým k nej klient nepodá žiadosť o pripojenie.

Ak bude všetko v poriadku, server prijíma spojenie. Po prijatí dostane server nový soket, clientSocket, viazaný na ten istý miestny prístav, 6666, a tiež má svoj vzdialený koncový bod nastavený na adresu a port klienta.

V tomto okamihu nový Zásuvka objekt dáva server do priameho spojenia s klientom, môžeme potom pristupovať k výstupným a vstupným tokom na písanie a prijímanie správ klientovi a od klienta:

PrintWriter out = nový PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = nový BufferedReader (nový InputStreamReader (clientSocket.getInputStream ()));

Od tejto chvíle je server schopný nekonečnej výmeny správ s klientom, kým sa soket nezatvorí jeho prúdmi.

V našom príklade však môže server odoslať pozdravnú odpoveď iba pred ukončením pripojenia, čo znamená, že ak by sme test spustili znova, pripojenie by bolo odmietnuté.

Aby sme umožnili kontinuitu v komunikácii, budeme musieť čítať zo vstupného toku vo vnútri a zatiaľ čo slučky a ukončíme ho, až keď klient pošle žiadosť o ukončenie, uvidíme to v akcii v nasledujúcej časti.

Pre každého nového klienta potrebuje server nový soket vrátený serverom súhlasiť hovor. The serverSocket sa používa na pokračovanie v načúvaní požiadaviek na pripojenie, pričom má sklon k potrebám pripojených klientov. V prvom príklade sme to zatiaľ neumožnili.

4.2. Klient

Klient musí poznať názov hostiteľa alebo IP zariadenia, na ktorom je server spustený, a číslo portu, na ktorom server počúva.

Ak chcete podať žiadosť o pripojenie, klient sa pokúsi stretnúť so serverom v počítači a porte servera:

Socket clientSocket = nový Socket ("127.0.0.1", 6666);

Klient sa tiež musí identifikovať na serveri, aby sa naviazal na číslo lokálneho portu pridelené systémom, ktoré použije počas tohto pripojenia. Sami to neriešime.

Vyššie uvedený konštruktor vytvorí nový soket, iba ak to má server prijatý pripojenie, inak dostaneme výnimku odmietnutú pripojením. Po úspešnom vytvorení z neho potom môžeme získať vstupné a výstupné toky na komunikáciu so serverom:

PrintWriter out = nový PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = nový BufferedReader (nový InputStreamReader (clientSocket.getInputStream ()));

Vstupný tok klienta je pripojený k výstupnému toku servera, rovnako ako je pripojený vstupný prúd servera k výstupnému toku klienta.

5. Nepretržitá komunikácia

Náš súčasný server blokuje, kým sa k nemu klient nepripojí, a potom znova blokuje, aby si vypočul správu od klienta. Po vykonaní jednej správy spojenie ukončí, pretože sme sa nezaoberali kontinuitou.

Je teda užitočný iba pri požiadavkách na príkaz ping, ale predstavte si, že by sme chceli implementovať chatovací server, určite by sa vyžadovala nepretržitá komunikácia medzi serverom a klientom.

Budeme musieť vytvoriť chvíľu slučku, aby sme neustále sledovali vstupný tok servera pre prichádzajúce správy.

Vytvorme nový server s názvom EchoServer.java ktorých jediným účelom je spätná väzba všetkých správ, ktoré dostane od klientov:

public class EchoServer {public void start (int port) {serverSocket = new ServerSocket (port); clientSocket = serverSocket.accept (); out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (nový InputStreamReader (clientSocket.getInputStream ())); Reťazec inputLine; while ((inputLine = in.readLine ())! = null) {if (".". equals (inputLine)) {out.println ("good bye"); prestávka; } out.println (inputLine); }}

Všimnite si, že sme pridali podmienku ukončenia, pri ktorej smyčka while končí, keď dostaneme znak obdobia.

Začneme EchoServer pomocou hlavnej metódy rovnako ako v prípade GreetServer. Tentokrát to začíname na inom porte ako napr 4444 aby nedošlo k zámene.

The EchoClient je podobný GreetClient, aby sme mohli duplikovať kód. Pre lepšiu prehľadnosť ich oddeľujeme.

V inej testovacej triede vytvoríme test, ktorý preukáže, že viaceré požiadavky pre EchoServer bude obsluhované bez toho, aby server uzavrel soket. To platí, pokiaľ zasielame žiadosti od toho istého klienta.

Rokovanie s viacerými klientmi je iný prípad, o čom sa dozvieme v nasledujúcej časti.

Vytvorme a nastaviť spôsob nadviazania spojenia so serverom:

@ Pred public void setup () {client = new EchoClient (); client.startConnection ("127.0.0.1", 4444); }

Rovnakým spôsobom vytvoríme a strhnúť metóda na uvoľnenie všetkých našich zdrojov, je to najlepší postup pre všetky prípady, keď používame sieťové zdroje:

@After public void tearDown () {client.stopConnection (); }

Potom otestujme náš echo server s niekoľkými požiadavkami:

@Test public void givenClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("ahoj"); Reťazec resp2 = client.sendMessage ("svet"); Reťazec resp3 = client.sendMessage ("!"); Reťazec resp4 = client.sendMessage ("."); assertEquals ("ahoj", resp1); assertEquals ("svet", resp2); assertEquals ("!", resp3); assertEquals ("dovidenia", resp4); }

Toto je vylepšenie oproti pôvodnému príkladu, kde by sme komunikovali iba raz, kým server neukončil naše pripojenie; teraz pošleme signál na ukončenie, aby sme serveru povedali, keď skončíme s reláciou.

6. Server s viacerými klientmi

Nakoľko predchádzajúci príklad predstavoval zlepšenie oproti prvému, stále to nie je také skvelé riešenie. Server musí mať kapacitu na obsluhu mnohých klientov a mnohých požiadaviek súčasne.

V tejto časti sa budeme zaoberať manipuláciou s viacerými klientmi.

Ďalšou funkciou, ktorú tu uvidíme, je, že ten istý klient sa mohol odpojiť a znova pripojiť, bez toho, aby na serveri dostal výnimku odmietnutú pripojením alebo obnovil pripojenie. Predtým sme to nedokázali.

To znamená, že náš server bude odolnejší a odolnejší voči viacerým požiadavkám od viacerých klientov.

Ako to urobíme, je vytvoriť nový soket pre každého nového klienta a službu, ktorú klient požaduje, v inom vlákne. Počet súčasne obsluhovaných klientov sa bude rovnať počtu spustených vlákien.

Hlavné vlákno bude bežať s cyklom while, keď bude počúvať nové pripojenia.

Dosť bolo rečí, vytvorme ďalší server s názvom EchoMultiServer.java. V jeho vnútri vytvoríme triedu vlákna obslužného programu na správu komunikácií každého klienta na jeho sokete:

verejná trieda EchoMultiServer {private ServerSocket serverSocket; public void start (int port) {serverSocket = nový ServerSocket (port); while (true) new EchoClientHandler (serverSocket.accept ()). start (); } public void stop () {serverSocket.close (); } súkromná statická trieda EchoClientHandler rozširuje vlákno {private Socket clientSocket; súkromný výstup PrintWriter; privátny BufferedReader v; public EchoClientHandler (Socket socket) {this.clientSocket = socket; } public void run () {out = new PrintWriter (clientSocket.getOutputStream (), true); in = new BufferedReader (nový InputStreamReader (clientSocket.getInputStream ())); Reťazec inputLine; while ((inputLine = in.readLine ())! = null) {if (".". equals (inputLine)) {out.println ("bye"); prestávka; } out.println (inputLine); } in.close (); out.close (); clientSocket.close (); }}

Všimnite si, že teraz voláme súhlasiť vo vnútri a zatiaľ čo slučka. Zakaždým, keď zatiaľ čo slučka je vykonaná, blokuje sa na súhlasiť hovor, kým sa nepripojí nový klient, potom vlákno obslužnej rutiny, EchoClientHandler, je vytvorený pre tohto klienta.

Čo sa deje vo vnútri vlákna, je to, čo sme predtým robili v EchoServer kde sme vybavovali iba jediného klienta. Takže EchoMultiServer deleguje túto prácu na EchoClientHandler aby mohla naďalej počúvať ďalších klientov v zatiaľ čo slučka.

Budeme stále používať EchoClient na otestovanie servera tentokrát vytvoríme viac klientov, z ktorých každý bude odosielať a prijímať viac správ zo servera.

Začnime náš server používať jeho hlavnú metódu na porte 5555.

Kvôli prehľadnosti budeme stále testovať nový balík:

@Test public void givenClient1_whenServerResponds_thenCorrect () {EchoClient client1 = nový EchoClient (); client1.startConnection ("127.0.0.1", 5555); Reťazec msg1 = client1.sendMessage ("ahoj"); Reťazec msg2 = client1.sendMessage ("svet"); Reťazec terminate = client1.sendMessage ("."); assertEquals (msg1, "ahoj"); assertEquals (správa 2, "svet"); assertEquals (ukončiť, „ahoj“); } @Test public void givenClient2_whenServerResponds_thenCorrect () {EchoClient client2 = nový EchoClient (); client2.startConnection ("127.0.0.1", 5555); Reťazec msg1 = client2.sendMessage ("ahoj"); Reťazec msg2 = client2.sendMessage ("svet"); Reťazec terminate = client2.sendMessage ("."); assertEquals (msg1, "ahoj"); assertEquals (správa 2, "svet"); assertEquals (ukončiť, „ahoj“); }

Mohli by sme vytvoriť toľko testovacích prípadov, koľko chceme, každý by priniesol nového klienta a server by slúžil všetkým.

7. Záver

V tomto tutoriáli sme sa zamerali na úvod do programovania zásuviek cez TCP / IP a napísal jednoduchú aplikáciu Klient / Server v Jave.

Celý zdrojový kód článku nájdete - ako obvykle - v projekte GitHub.