Vodič za Java utičnice

1. Pregled

Uvjet utičnica programiranje odnosi se na pisanje programa koji se izvršavaju na više računala u kojima su svi uređaji povezani međusobno pomoću mreže.

Postoje dva komunikacijska protokola koja se mogu koristiti za programiranje soketa: Korisnički protokol datagrama (UDP) i protokol kontrole prijenosa (TCP).

Glavna razlika između njih dvije je ta što je UDP bez veze, što znači da između klijenta i poslužitelja nema sesije dok je TCP orijentiran na vezu, što znači da se prvo mora uspostaviti ekskluzivna veza između klijenta i poslužitelja da bi se odvijala komunikacija.

Ovaj tutorial predstavlja uvod u programiranje soketa preko TCP / IP-a mreže i pokazuje kako pisati klijentske / poslužiteljske programe na Javi. UDP nije glavni protokol i kao takav možda se neće često susresti.

2. Postavljanje projekta

Java nudi kolekciju klasa i sučelja koja vode računa o detaljima komunikacije na niskoj razini između klijenta i poslužitelja.

Oni su uglavnom sadržani u java.net paketa, pa moramo izvršiti sljedeći uvoz:

import java.net. *;

Također nam je potreban java.io paket koji nam daje ulazne i izlazne tokove za pisanje i čitanje iz komunikacije:

import java.io. *;

Radi jednostavnosti, pokrećemo programe klijenta i poslužitelja na istom računalu. Ako bismo ih izvršavali na različitim umreženim računalima, jedino što bi se promijenilo je IP adresa, u ovom ćemo slučaju koristiti lokalnihost na 127.0.0.1.

3. Jednostavan primjer

Prljajmo ruke s najviše osnovni primjeri koji uključuju klijenta i poslužitelja. Bit će to dvosmjerna komunikacijska aplikacija u kojoj klijent pozdravi poslužitelj, a poslužitelj odgovori.

Stvorimo poslužiteljsku aplikaciju u klasi koja se zove GreetServer.java sa sljedećim kodom.

Uključujemo glavni metodu i globalne varijable kako bismo skrenuli pozornost na to kako ćemo pokretati sve poslužitelje u ovom članku. U ostalim primjerima članaka izostavit ćemo ovu vrstu ponavljajućih kodova:

javna klasa GreetServer {private ServerSocket serverSocket; privatna utičnica clientSocket; privatni PrintWriter out; private BufferedReader u; javni void start (int port) {serverSocket = novi ServerSocket (port); clientSocket = serverSocket.accept (); out = novi PrintWriter (clientSocket.getOutputStream (), true); in = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ())); Pozdrav niza = in.readLine (); if ("hello server" .equals (pozdrav)) {out.println ("hello client"); } else {out.println ("neprepoznati pozdrav"); }} javna void stop () {in.close (); out.close (); clientSocket.close (); serverSocket.close (); } javna statička void glavna (String [] args) {GreetServer server = new GreetServer (); server.start (6666); }}

Stvorimo i klijenta pod nazivom GreetClient.java s ovim kodom:

javna klasa GreetClient {private Socket clientSocket; privatni PrintWriter out; private BufferedReader u; javna void startConnection (niz ip, int port) {clientSocket = nova utičnica (ip, port); out = novi PrintWriter (clientSocket.getOutputStream (), true); in = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ())); } javni String sendMessage (String msg) {out.println (msg); Niz resp = in.readLine (); povratak odn .; } javna void stopConnection () {in.close (); out.close (); clientSocket.close (); }}

Pokrenimo poslužitelj; u svom IDE-u to radite jednostavnim pokretanjem kao Java program.

A sada, pošaljite pozdrav poslužitelju pomoću jediničnog testa, koji potvrđuje da poslužitelj zapravo šalje pozdrav kao odgovor:

@Test javna praznina givenGreetingClient_whenServerRespondsWhenStarted_thenCorrect () {GreetClient client = new GreetClient (); client.startConnection ("127.0.0.1", 6666); String odgovor = client.sendMessage ("pozdrav poslužitelju"); assertEquals ("zdravo klijent", odgovor); }

Ne brinite ako ne razumijete u potpunosti što se ovdje događa, jer je ovaj primjer namijenjen da nam pruži osjećaj što možemo očekivati ​​kasnije u članku.

U sljedećim ćemo odjeljcima secirati socket komunikacija koristeći ovaj jednostavan primjer i s više primjera zaronite dublje u detalje.

4. Kako rade utičnice

Gornji ćemo primjer upotrijebiti za prolazak kroz različite dijelove ovog odjeljka.

Po definiciji, a utičnica jedna je krajnja točka dvosmjerne komunikacijske veze između dva programa koja se izvode na različitim računalima u mreži. Socket je vezan za broj porta tako da transportni sloj može identificirati aplikaciju kojoj su podaci namijenjeni za slanje.

4.1. Poslužitelj

Obično poslužitelj radi na određenom računalu u mreži i ima utičnicu koja je vezana za određeni broj porta. U našem slučaju koristimo isto računalo kao klijent i poslužitelj smo pokrenuli na portu 6666:

ServerSocket serverSocket = novi ServerSocket (6666);

Poslužitelj samo čeka, preslušavajući utičnicu kako bi klijent mogao napraviti zahtjev za povezivanjem. To se događa u sljedećem koraku:

Socket clientSocket = serverSocket.accept ();

Kada kod poslužitelja naiđe na prihvatiti metodu blokira dok mu klijent ne uputi zahtjev za povezivanje.

Ako sve bude u redu, poslužitelj prihvaća veza. Nakon prihvaćanja, poslužitelj dobiva novu utičnicu, clientSocket, povezan s istom lokalnom lukom, 6666, a također ima svoju udaljenu krajnju točku postavljenu na adresu i priključak klijenta.

U ovom trenutku novi Utičnica objekt stavlja poslužitelj u izravnu vezu s klijentom, zatim možemo pristupiti izlaznim i ulaznim tokovima za pisanje i primanje poruka od i od klijenta:

PrintWriter out = novi PrintWriter (clientSocket.getOutputStream (), true); BufferedReader u = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ()));

Od ovog trenutka nadalje, poslužitelj je u stanju beskrajno razmjenjivati ​​poruke s klijentom sve dok se utičnica ne zatvori svojim tokovima.

Međutim, u našem primjeru poslužitelj može poslati samo pozdravni odgovor prije nego što zatvori vezu, to znači da bi, ako ponovno pokrenemo test, veza bila odbijena.

Da bismo omogućili kontinuitet u komunikaciji, morat ćemo čitati iz ulaznog toka unutar a dok petlja i izaći samo kada klijent pošalje zahtjev za ukidanjem, to ćemo vidjeti na djelu u sljedećem odjeljku.

Za svakog novog klijenta, poslužitelj treba novu utičnicu koju vraća prihvatiti poziv. The serverSocket koristi se za nastavak osluškivanja zahtjeva za povezivanje, dok se teži potrebama povezanih klijenata. To još nismo dopustili u našem prvom primjeru.

4.2. Klijent

Klijent mora znati ime hosta ili IP računala na kojem poslužitelj radi i broj porta na kojem poslužitelj sluša.

Da bi izvršio zahtjev za povezivanjem, klijent se pokušava sastati sa poslužiteljem na stroju i priključku poslužitelja:

Socket clientSocket = nova utičnica ("127.0.0.1", 6666);

Klijent se također mora identificirati s poslužiteljem tako da se veže na lokalni broj porta koji mu je dodijelio sustav koji će koristiti tijekom ove veze. Ne bavimo se time sami.

Gornji konstruktor stvara novu utičnicu samo kada je poslužitelj ima prihvaćen veze, u suprotnom ćemo dobiti iznimku odbijenu vezu. Kada se uspješno kreira, od njega možemo dobiti ulazne i izlazne tokove za komunikaciju s poslužiteljem:

PrintWriter out = novi PrintWriter (clientSocket.getOutputStream (), true); BufferedReader u = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ()));

Ulazni tok klijenta povezan je s izlaznim tokom poslužitelja, baš kao što je ulazni tok poslužitelja povezan s izlaznim tokom klijenta.

5. Kontinuirana komunikacija

Naš trenutni poslužitelj blokira dok se klijent ne poveže s njim, a zatim ponovno blokira kako bi preslušao poruku od klijenta, nakon jedne poruke zatvara vezu jer nismo imali posla s kontinuitetom.

Dakle, korisno je samo u ping zahtjevima, ali zamislite da bismo željeli implementirati chat poslužitelj, definitivno bi bila potrebna kontinuirana naprijed-natrag komunikacija između poslužitelja i klijenta.

Morat ćemo stvoriti while petlju kako bismo kontinuirano promatrali ulazni tok poslužitelja za dolazne poruke.

Stvorimo novi poslužitelj pod nazivom EchoServer.java čija je jedina svrha ponoviti povrat svih poruka koje primi od klijenata:

javna klasa EchoServer {javni void start (int port) {serverSocket = novi ServerSocket (port); clientSocket = serverSocket.accept (); out = novi PrintWriter (clientSocket.getOutputStream (), true); in = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". jednako (inputLine)) {out.println ("zbogom"); pauza; } out.println (inputLine); }}

Primijetimo da smo dodali uvjet prekida u kojem while petlja izlazi kad primimo znak točke.

Počet ćemo EchoServer koristeći glavnu metodu baš kao i mi za Pozdrav poslužitelju. Ovaj put ga pokrećemo na drugoj luci kao što je 4444 kako bi se izbjegla zabuna.

The EchoClient je sličan GreetClient, tako da možemo duplicirati kod. Razdvajamo ih radi jasnoće.

U drugoj klasi testa stvorit ćemo test koji će pokazati da višestruki zahtjevi za EchoServer poslužit će se bez poslužitelja koji zatvara utičnicu. To je istina sve dok šaljemo zahtjeve od istog klijenta.

Suočavanje s više klijenata različit je slučaj, što ćemo vidjeti u sljedećem odjeljku.

Stvorimo a postaviti metoda za uspostavljanje veze s poslužiteljem:

@ Prije javne void postavke () {client = novi EchoClient (); client.startConnection ("127.0.0.1", 4444); }

Jednako ćemo stvoriti i srušiti za oslobađanje svih naših resursa, ovo je najbolja praksa za svaki slučaj kada koristimo mrežne resurse:

@Nakon javne void tearDown () {client.stopConnection (); }

Isprobajmo onda naš echo poslužitelj s nekoliko zahtjeva:

@Test javna praznina givenClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hello"); Niz resp2 = client.sendMessage ("svijet"); Niz resp3 = client.sendMessage ("!"); Niz resp4 = client.sendMessage ("."); assertEquals ("zdravo", resp1); assertEquals ("svijet", odnosno 2); assertEquals ("!", resp3); assertEquals ("zbogom", resp4); }

Ovo je poboljšanje u odnosu na početni primjer, gdje bismo komunicirali samo jednom prije nego što nam je poslužitelj zatvorio vezu; sada šaljemo signal za prekid da bismo rekli poslužitelju kad završimo sa sesijom.

6. Poslužitelj s više klijenata

Kao što je prethodni primjer bio napredak u odnosu na prvi, to još uvijek nije tako veliko rješenje. Poslužitelj mora imati kapacitet istodobnog servisiranja mnogih klijenata i mnogih zahtjeva.

Rukovanje s više klijenata ono je što ćemo pokriti u ovom odjeljku.

Još jedna značajka koju ćemo ovdje vidjeti je da bi se isti klijent mogao ponovno odspojiti i ponovo povezati, bez dobivanja odbijene veze ili resetiranja veze na poslužitelju. Prije toga nismo bili u mogućnosti to učiniti.

To znači da će naš poslužitelj biti robusniji i otporniji na više zahtjeva više klijenata.

Kako ćemo to učiniti je stvoriti novu utičnicu za svakog novog klijenta i uslugu koje klijentovi zahtjevi imaju na drugoj niti. Broj klijenata kojima se istovremeno pruža usluga jednak je broju pokrenutih niti.

Glavna nit će izvoditi while petlju dok osluškuje nove veze.

Dosta priče, stvorimo još jedan poslužitelj pod nazivom EchoMultiServer.java. Unutar nje stvorit ćemo klasu niti rukovatelja za upravljanje komunikacijom svakog klijenta na njegovoj utičnici:

javna klasa EchoMultiServer {private ServerSocket serverSocket; javni void start (int port) {serverSocket = novi ServerSocket (port); while (true) novi EchoClientHandler (serverSocket.accept ()). start (); } javna void stop () {serverSocket.close (); } privatna statička klasa EchoClientHandler proširuje nit {private Socket clientSocket; privatni PrintWriter out; private BufferedReader u; javni EchoClientHandler (utičnica utičnice) {this.clientSocket = utičnica; } javni void run () {out = novi PrintWriter (clientSocket.getOutputStream (), true); in = novi BufferedReader (novi InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". jednako (inputLine)) {out.println ("bye"); pauza; } out.println (inputLine); } in.close (); out.close (); clientSocket.close (); }}

Primijetite da sada zovemo prihvatiti unutar a dok petlja. Svaki put kad dok petlja se izvršava, blokira na prihvatiti poziva dok se novi klijent ne poveže, zatim nit obrađivača, EchoClientHandler, stvoren je za ovog klijenta.

Unutar niti događa se ono što smo prethodno radili u EchoServer gdje smo rješavali samo jednog klijenta. Dakle EchoMultiServer delegira ovo djelo na EchoClientHandler tako da može nastaviti slušati više klijenata u dok petlja.

I dalje ćemo koristiti EchoClient da bismo testirali poslužitelj, ovog puta stvorit ćemo više klijenata koji svaki šalju i primaju više poruka s poslužitelja.

Pokrenimo naš poslužitelj koristeći njegovu glavnu metodu na portu 5555.

Radi jasnoće, ipak ćemo staviti testove u novi paket:

@Test javna praznina givenClient1_whenServerResponds_thenCorrect () {EchoClient client1 = new EchoClient (); client1.startConnection ("127.0.0.1", 5555); Niz msg1 = client1.sendMessage ("zdravo"); Niz msg2 = client1.sendMessage ("svijet"); Niz završava = client1.sendMessage ("."); assertEquals (msg1, "zdravo"); assertEquals (msg2, "svijet"); assertEquals (terminate, "bye"); } @Test javna praznina givenClient2_whenServerResponds_thenCorrect () {EchoClient client2 = new EchoClient (); client2.startConnection ("127.0.0.1", 5555); Niz msg1 = client2.sendMessage ("zdravo"); Niz msg2 = client2.sendMessage ("svijet"); Niz završava = client2.sendMessage ("."); assertEquals (msg1, "zdravo"); assertEquals (msg2, "svijet"); assertEquals (terminate, "bye"); }

Mogli bismo stvoriti onoliko testnih slučajeva koliko želimo, svaki će stvoriti novi klijent i poslužitelj će ih opsluživati.

7. Zaključak

U ovom smo se vodiču usredotočili na uvod u programiranje soketa preko TCP / IP-a i napisao jednostavnu klijentsku / poslužiteljsku aplikaciju na Javi.

Cjeloviti izvorni kôd članka može se naći - kao i obično - u projektu GitHub.