En guide til Java-stikkontakter

1. Oversigt

Begrebet stikkontakt programmering henviser til skriveprogrammer, der udføres på tværs af flere computere, hvor enhederne alle er forbundet til hinanden ved hjælp af et netværk.

Der er to kommunikationsprotokoller, som man kan bruge til socket-programmering: User Datagram Protocol (UDP) og Transfer Control Protocol (TCP).

Hovedforskellen mellem de to er, at UDP er forbindelsesløs, hvilket betyder, at der ikke er nogen session mellem klienten og serveren, mens TCP er forbindelsesorienteret, hvilket betyder at der først skal etableres en eksklusiv forbindelse mellem klient og server for at kommunikation kan finde sted.

Denne vejledning præsenterer en introduktion til sockets programmering via TCP / IP netværk og demonstrerer, hvordan man skriver klient- / serverapplikationer i Java. UDP er ikke en almindelig protokol, og som sådan kan det ofte ikke forekomme.

2. Opsætning af projekt

Java leverer en samling af klasser og grænseflader, der tager sig af kommunikationsdetaljer på lavt niveau mellem klienten og serveren.

Disse er for det meste indeholdt i java.net pakke, så vi skal foretage følgende import:

import java.net. *;

Vi har også brug for java.io pakke, som giver os input og output streams til at skrive til og læse fra, mens vi kommunikerer:

import java.io. *;

Af hensyn til enkelheden kører vi vores klient- og serverprogrammer på samme computer. Hvis vi skulle udføre dem på forskellige computere i netværket, er det eneste, der ville ændre sig IP-adressen, i dette tilfælde bruger vi lokal vært127.0.0.1.

3. Enkelt eksempel

Lad os få vores hænder beskidte med mest grundlæggende eksempler, der involverer en klient og en server. Det bliver en tovejskommunikationsapplikation, hvor klienten hilser på serveren, og serveren reagerer.

Lad os oprette serverapplikationen i en klasse kaldet GreetServer.java med følgende kode.

Vi inkluderer vigtigste metode og de globale variabler for at gøre opmærksom på, hvordan vi kører alle servere i denne artikel. I resten af ​​eksemplerne i artiklerne udelader vi denne slags mere gentagne kode:

offentlig klasse GreetServer {privat ServerSocket serverSocket; privat Socket-klient Socket; privat PrintWriter ud; privat BufferedReader i; offentlig ugyldig start (int-port) {serverSocket = ny ServerSocket (port); clientSocket = serverSocket.accept (); ud = ny PrintWriter (clientSocket.getOutputStream (), sand); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); Strenghilsen = in.readLine (); if ("hej server" .equals (hilsen)) {out.println ("hej klient"); } andet {out.println ("ukendt hilsen"); }} offentligt ugyldigt stop () {in.close (); out.close (); clientSocket.close (); serverSocket.close (); } offentlig statisk ugyldig hoved (String [] args) {GreetServer server = ny GreetServer (); server.start (6666); }}

Lad os også oprette en klient, der kaldes GreetClient.java med denne kode:

offentlig klasse GreetClient {private Socket clientSocket; privat PrintWriter ud; privat BufferedReader i; public void startConnection (String ip, int port) {clientSocket = new Socket (ip, port); ud = ny PrintWriter (clientSocket.getOutputStream (), sand); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); } public String sendMessage (String msg) {out.println (msg); Streng resp = in.readLine (); returnere resp. } public void stopConnection () {in.close (); out.close (); clientSocket.close (); }}

Lad os starte serveren; i din IDE gør du dette ved blot at køre det som et Java-program.

Og lad os nu sende en hilsen til serveren ved hjælp af en enhedstest, der bekræfter, at serveren faktisk sender en hilsen som svar:

@Test offentlig ugyldighed givenGreetingClient_whenServerRespondsWhenStarted_thenCorrect () {GreetClient-klient = ny GreetClient (); client.startConnection ("127.0.0.1", 6666); Strengrespons = client.sendMessage ("hej server"); assertEquals ("hej klient", svar); }

Bare rolig, hvis du ikke helt forstår, hvad der sker her, da dette eksempel er beregnet til at give os en fornemmelse af, hvad vi kan forvente senere i artiklen.

I de følgende afsnit dissekerer vi stikkontakt kommunikation ved hjælp af dette enkle eksempel og dybere dybere ned i detaljerne med flere eksempler.

4. Sådan fungerer stikkontakter

Vi bruger ovenstående eksempel til at gennemgå forskellige dele af dette afsnit.

Per definition er a stikkontakt er et slutpunkt for en tovejskommunikationsforbindelse mellem to programmer, der kører på forskellige computere på et netværk. En sokkel er bundet til et portnummer, så transportlaget kan identificere den applikation, som data er bestemt til at blive sendt til.

4.1. Serveren

Normalt kører en server på en bestemt computer på netværket og har et stik, der er bundet til et specifikt portnummer. I vores tilfælde bruger vi den samme computer som klienten og startede serveren på porten 6666:

ServerSocket serverSocket = ny ServerSocket (6666);

Serveren venter bare og lytter til stikkontakten, hvor en klient fremsætter en anmodning om forbindelse. Dette sker i næste trin:

Socket clientSocket = serverSocket.accept ();

Når serverkoden støder på acceptere metode blokerer den, indtil en klient fremsætter en forbindelsesanmodning til den.

Hvis alt går godt, skal serveren accepterer forbindelsen. Efter accept får serveren et nyt stik, clientSocket, bundet til den samme lokale havn, 6666, og har også sit eksterne slutpunkt indstillet til klientens adresse og port.

På dette tidspunkt er det nye Stikkontakt objekt sætter serveren i direkte forbindelse med klienten, så kan vi få adgang til output- og inputstrømmene til henholdsvis at skrive og modtage meddelelser til og fra klienten:

PrintWriter out = ny PrintWriter (clientSocket.getOutputStream (), sand); BufferedReader in = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ()));

Herfra og frem er serveren i stand til at udveksle meddelelser med klienten uendeligt, indtil stikket lukkes med dets streams.

Imidlertid kan serveren i vores eksempel kun sende et hilsen-svar, før den lukker forbindelsen, det betyder, at hvis vi kørte vores test igen, ville forbindelsen blive nægtet.

For at muliggøre kontinuitet i kommunikationen bliver vi nødt til at læse fra inputstrømmen inde i a mens loop og kun afslutte, når klienten sender en anmodning om opsigelse, vi ser dette i aktion i det følgende afsnit.

For hver ny klient har serveren brug for et nyt stik, der returneres af acceptere opkald. Det serverSocket bruges til at fortsætte med at lytte efter forbindelsesanmodninger, mens de passer til de tilsluttede klients behov. Vi har ikke tilladt dette endnu i vores første eksempel.

4.2. Klienten

Klienten skal kende værtsnavnet eller IP'en på den maskine, som serveren kører på, og det portnummer, som serveren lytter til.

For at fremsætte en forbindelsesanmodning prøver klienten at mødes med serveren på serverens maskine og port:

Socket clientSocket = ny Socket ("127.0.0.1", 6666);

Klienten skal også identificere sig til serveren, så den binder sig til et lokalt portnummer, der er tildelt af systemet, som det vil bruge under denne forbindelse. Vi beskæftiger os ikke med dette.

Ovenstående konstruktør opretter kun et nyt stik, når serveren har det accepteret forbindelsen, ellers får vi en forbindelse nægtet undtagelse. Når det er oprettet, kan vi derefter få input- og outputstrømme fra det til at kommunikere med serveren:

PrintWriter out = ny PrintWriter (clientSocket.getOutputStream (), sand); BufferedReader in = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ()));

Klientens inputstrøm er forbundet til serverens outputstrøm, ligesom serverens inputstrøm er forbundet til klientens outputstrøm.

5. Kontinuerlig kommunikation

Vores nuværende server blokerer, indtil en klient opretter forbindelse til den, og blokerer derefter igen for at lytte til en besked fra klienten, efter den enkelte besked lukker den forbindelsen, fordi vi ikke har behandlet kontinuitet.

Så det er kun nyttigt ved ping-anmodninger, men forestil dig, at vi gerne vil implementere en chat-server, kontinuerlig frem og tilbage kommunikation mellem server og klient vil helt sikkert være påkrævet.

Vi bliver nødt til at oprette en while-loop for kontinuerligt at observere serverens inputstrøm for indgående meddelelser.

Lad os oprette en ny server kaldet EchoServer.java hvis eneste formål er at gentage de meddelelser, det modtager fra klienter:

public class EchoServer {public void start (int port) {serverSocket = new ServerSocket (port); clientSocket = serverSocket.accept (); ud = ny PrintWriter (clientSocket.getOutputStream (), sand); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); Streng inputLine; mens ((inputLine = in.readLine ())! = null) {if (".". er lig med (inputLine)) {out.println ("farvel"); pause; } out.println (inputLine); }}

Bemærk, at vi har tilføjet en opsigelsesbetingelse, hvor while-sløjfen udløber, når vi modtager et periodetegn.

Vi starter EchoServer ved hjælp af hovedmetoden, ligesom vi gjorde for GreetServer. Denne gang starter vi det på en anden port som f.eks 4444 for at undgå forvirring.

Det EchoClient ligner Hils klient, så vi kan duplikere koden. Vi adskiller dem for klarhedens skyld.

I en anden testklasse skal vi oprette en test for at vise, at flere anmodninger til EchoServer serveres uden at serveren lukker soklen. Dette gælder, så længe vi sender anmodninger fra den samme klient.

At håndtere flere kunder er en anden sag, som vi vil se i et efterfølgende afsnit.

Lad os oprette en Opsætning metode til at starte en forbindelse med serveren:

@Før offentlig ugyldig opsætning () {klient = ny EchoClient (); client.startConnection ("127.0.0.1", 4444); }

Vi opretter ligeledes en rive ned metode til frigivelse af alle vores ressourcer, dette er den bedste praksis i alle tilfælde, hvor vi bruger netværksressourcer:

@Efter offentlig ugyldighed tearDown () {client.stopConnection (); }

Lad os derefter teste vores ekkoserver med et par anmodninger:

@Test offentlig ugyldighed givetClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hej"); Streng resp2 = client.sendMessage ("verden"); Streng resp3 = client.sendMessage ("!"); Streng resp4 = client.sendMessage ("."); assertEquals ("hej", resp1); assertEquals ("verden", resp2); assertEquals ("!", resp3); assertEquals ("farvel", resp4); }

Dette er en forbedring i forhold til det oprindelige eksempel, hvor vi kun ville kommunikere en gang, før serveren lukkede vores forbindelse; nu sender vi et afslutningssignal for at fortælle serveren, når vi er færdige med sessionen.

6. Server med flere klienter

Meget som det foregående eksempel var en forbedring i forhold til det første, er det stadig ikke så stor en løsning. En server skal have kapacitet til at betjene mange klienter og mange anmodninger samtidigt.

Håndtering af flere kunder er, hvad vi skal dække i dette afsnit.

En anden funktion, vi vil se her, er, at den samme klient kunne afbryde og oprette forbindelse igen uden at få en undtagelse fra en forbindelse nægtet eller en nulstilling af forbindelsen på serveren. Tidligere var vi ikke i stand til at gøre dette.

Dette betyder, at vores server bliver mere robust og modstandsdygtig på tværs af flere anmodninger fra flere klienter.

Hvordan vi vil gøre dette er at oprette en ny sokkel til hver nye klient og service, som klienten anmoder om på en anden tråd. Antallet af klienter, der betjenes samtidigt, svarer til antallet af kørende tråde.

Hovedtråden kører et stykke løb, da den lytter efter nye forbindelser.

Nok snak, lad os oprette en anden server, der hedder EchoMultiServer.java. Inde i det opretter vi en handlertrådsklasse til at styre hver klients kommunikation på dens stikkontakt:

offentlig klasse EchoMultiServer {privat ServerSocket serverSocket; offentlig ugyldig start (int-port) {serverSocket = ny ServerSocket (port); mens (sand) ny EchoClientHandler (serverSocket.accept ()). start (); } offentligt ugyldigt stop () {serverSocket.close (); } privat statisk klasse EchoClientHandler udvider tråd {private Socket clientSocket; privat PrintWriter ud; privat BufferedReader i; offentlig EchoClientHandler (stikkontakt) {this.clientSocket = stikkontakt; } public void run () {out = new PrintWriter (clientSocket.getOutputStream (), true); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); Streng inputLine; mens ((inputLine = in.readLine ())! = null) {if (".". er lig med (inputLine)) {out.println ("bye"); pause; } out.println (inputLine); } in.close (); out.close (); clientSocket.close (); }}

Bemærk, at vi nu ringer acceptere inde i en mens løkke. Hver gang mens loop udføres, det blokerer på acceptere ring, indtil en ny klient opretter forbindelse, derefter handler tråden, EchoClientHandler, oprettes til denne klient.

Hvad der sker inde i tråden er, hvad vi tidligere gjorde i EchoServer hvor vi kun håndterede en enkelt klient. Så EchoMultiServer delegerer dette arbejde til EchoClientHandler så det kan fortsætte med at lytte efter flere kunder i mens løkke.

Vi vil stadig bruge EchoClient for at teste serveren opretter vi denne gang flere klienter, der hver sender og modtager flere meddelelser fra serveren.

Lad os starte vores server ved hjælp af dens vigtigste metode på port 5555.

For klarhedens skyld vil vi stadig stille tests i en ny suite:

@Test offentlig ugyldighed givenClient1_whenServerResponds_thenCorrect () {EchoClient client1 = new EchoClient (); client1.startConnection ("127.0.0.1", 5555); Streng msg1 = client1.sendMessage ("hej"); Streng msg2 = client1.sendMessage ("verden"); String terminate = client1.sendMessage ("."); assertEquals (msg1, "hej"); assertEquals (msg2, "verden"); assertEquals (afsluttes, "farvel"); } @ Test offentligt ugyldigt givenClient2_whenServerResponds_thenCorrect () {EchoClient client2 = new EchoClient (); client2.startConnection ("127.0.0.1", 5555); Streng msg1 = client2.sendMessage ("hej"); Streng msg2 = client2.sendMessage ("verden"); String terminate = client2.sendMessage ("."); assertEquals (msg1, "hej"); assertEquals (msg2, "verden"); assertEquals (afsluttes, "farvel"); }

Vi kunne oprette så mange af disse testtilfælde, som vi vil, hver gyder en ny klient, og serveren serverer dem alle.

7. Konklusion

I denne vejledning har vi fokuseret på en introduktion til sockets programmering via TCP / IP og skrev en simpel klient / server-applikation i Java.

Den fulde kildekode til artiklen findes - som sædvanlig - i GitHub-projektet.