En guide til Java-stikkontakter

1. Oversikt

Begrepet stikkontakt programmering refererer til skriveprogrammer som kjøres på flere datamaskiner der enhetene er koblet til hverandre ved hjelp av et nettverk.

Det er to kommunikasjonsprotokoller som man kan bruke til stikkontaktprogrammering: User Datagram Protocol (UDP) og Transfer Control Protocol (TCP).

Hovedforskjellen mellom de to er at UDP er tilkoblingsfri, noe som betyr at det ikke er noen økt mellom klienten og serveren mens TCP er tilkoblingsorientert, noe som betyr at en eksklusiv forbindelse først må opprettes mellom klient og server for at kommunikasjon skal finne sted.

Denne opplæringen presenterer en introduksjon til sockets programmering over TCP / IP nettverk og demonstrerer hvordan du skriver klient- / serverapplikasjoner i Java. UDP er ikke en vanlig protokoll, og som sådan blir det kanskje ikke ofte.

2. Prosjektoppsett

Java tilbyr en samling klasser og grensesnitt som tar seg av kommunikasjonsdetaljer på lavt nivå mellom klienten og serveren.

Disse er for det meste inneholdt i java.net pakken, så vi må gjøre følgende import:

importer java.net. *;

Vi trenger også java.io pakke som gir oss input og output streams å skrive til og lese fra mens vi kommuniserer:

importer java.io. *;

For enkelhets skyld vil vi kjøre klient- og serverprogrammene på samme datamaskin. Hvis vi skulle kjøre dem på forskjellige datamaskiner i nettverk, er det eneste som vil endre seg IP-adressen, i dette tilfellet vil vi bruke lokal vert127.0.0.1.

3. Enkelt eksempel

La oss skitne hendene våre mest grunnleggende eksempler som involverer en klient og en server. Det kommer til å være en toveiskommunikasjonsapplikasjon der klienten hilser på serveren og serveren svarer.

La oss lage serverapplikasjonen i en klasse som heter GreetServer.java med følgende kode.

Vi inkluderer hoved- metode og de globale variablene for å gjøre oppmerksom på hvordan vi skal kjøre alle servere i denne artikkelen. I resten av eksemplene i artiklene vil vi utelate denne typen mer repeterende kode:

offentlig klasse GreetServer {private ServerSocket serverSocket; privat Socket-klient; privat PrintWriter ut; privat BufferedReader i; offentlig ugyldig start (int-port) {serverSocket = ny ServerSocket (port); clientSocket = serverSocket.accept (); ut = ny PrintWriter (clientSocket.getOutputStream (), sant); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); Stringhilsen = in.readLine (); if ("hallo server" .equals (greeting)) {out.println ("hallo client"); } annet {out.println ("ukjent hilsen"); }} offentlig ugyldig stopp () {in.close (); ut. lukk (); clientSocket.close (); serverSocket.close (); } public static void main (String [] args) {GreetServer server = new GreetServer (); server.start (6666); }}

La oss også opprette en klient som heter GreetClient.java med denne koden:

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

La oss starte serveren; i din IDE gjør du dette ved å kjøre det som et Java-program.

Og la oss nå sende en hilsen til serveren ved hjelp av en enhetstest, som bekrefter at serveren faktisk sender en hilsen som svar:

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

Ikke bekymre deg hvis du ikke helt forstår hva som skjer her, da dette eksemplet er ment å gi oss en følelse av hva vi kan forvente senere i artikkelen.

I de følgende avsnittene vil vi dissekere stikkontaktkommunikasjon ved hjelp av dette enkle eksemplet og dykk dypere ned i detaljene med flere eksempler.

4. Hvordan stikkontakter fungerer

Vi vil bruke eksemplet ovenfor for å gå gjennom forskjellige deler av denne delen.

Per definisjon, a stikkontakt er et endepunkt for en toveiskommunikasjonslink mellom to programmer som kjører på forskjellige datamaskiner i et nettverk. En stikkontakt er bundet til et portnummer slik at transportlaget kan identifisere applikasjonen som data er bestemt til å sendes til.

4.1. Serveren

Vanligvis kjører en server på en bestemt datamaskin i nettverket og har en stikkontakt som er bundet til et bestemt portnummer. I vårt tilfelle bruker vi samme datamaskin som klienten og startet serveren på porten 6666:

ServerSocket serverSocket = ny ServerSocket (6666);

Serveren bare venter og hører på kontakten for at en klient skal sende en tilkoblingsforespørsel. Dette skjer i neste trinn:

Socket clientSocket = serverSocket.accept ();

Når serverkoden møter aksepterer metode blokkeres den til en klient ber om en tilkoblingsforespørsel.

Hvis alt går bra, serveren godtar tilkoblingen. Ved aksept får serveren en ny kontakt, clientSocket, bundet til samme lokale havn, 6666, og har også sitt eksterne sluttpunkt satt til adressen og porten til klienten.

På dette punktet, den nye Stikkontakt objekt setter serveren i direkte forbindelse med klienten, så kan vi få tilgang til utgangs- og inngangsstrømmene for å skrive og motta meldinger til og fra klienten henholdsvis:

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

Herfra og utover er serveren i stand til å utveksle meldinger med klienten uendelig til kontakten er lukket med strømmen.

Imidlertid kan serveren i vårt eksempel bare sende et hilsningssvar før den lukker forbindelsen, dette betyr at hvis vi kjørte testen vår igjen, ville forbindelsen bli nektet.

For å tillate kontinuitet i kommunikasjonen, må vi lese fra inngangsstrømmen inne i samtidig som sløyfe og bare avslutte når klienten sender en avslutningsforespørsel, vil vi se dette i aksjon i det følgende avsnittet.

For hver nye klient trenger serveren en ny kontakt som returneres av aksepterer anrop. De serverSocket brukes til å fortsette å lytte etter tilkoblingsforespørsler mens du ivaretar behovene til de tilkoblede klientene. Vi har ikke tillatt dette ennå i vårt første eksempel.

4.2. Klienten

Klienten må kjenne vertsnavnet eller IP-en til maskinen serveren kjører på og portnummeret som serveren lytter til.

For å gjøre en tilkoblingsforespørsel, prøver klienten å treffe serveren på serverens maskin og port:

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

Klienten trenger også å identifisere seg mot serveren slik at den binder seg til et lokalt portnummer, tildelt av systemet, som den vil bruke under denne tilkoblingen. Vi takler ikke dette selv.

Ovennevnte konstruktør oppretter bare en ny kontakt når serveren har akseptert forbindelsen, ellers får vi unntaksforbindelse. Når den er opprettet, kan vi få inn- og utgangsstrømmer fra den for å kommunisere med serveren:

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

Inngangsstrømmen til klienten er koblet til utgangsstrømmen til serveren, akkurat som inngangsstrømmen til serveren er koblet til utgangsstrømmen til klienten.

5. Kontinuerlig kommunikasjon

Den nåværende serveren vår blokkerer til en klient kobler seg til den, og blokkerer igjen for å lytte til en melding fra klienten, etter den eneste meldingen stenger den forbindelsen fordi vi ikke har håndtert kontinuitet.

Så det er bare nyttig i ping-forespørsler, men tenk at vi vil implementere en chatserver, kontinuerlig frem og tilbake kommunikasjon mellom server og klient vil definitivt være nødvendig.

Vi må lage en stundsløyfe for kontinuerlig å observere inngangsstrømmen til serveren for innkommende meldinger.

La oss opprette en ny server som heter EchoServer.java hvis eneste formål er å ekko tilbake meldinger den mottar fra klienter:

public class EchoServer {public void start (int port) {serverSocket = new ServerSocket (port); clientSocket = serverSocket.accept (); ut = ny PrintWriter (clientSocket.getOutputStream (), sant); i = ny BufferedReader (ny InputStreamReader (clientSocket.getInputStream ())); Streng inputLine; mens ((inputLine = in.readLine ())! = null) {if (".". tilsvarer (inputLine)) {out.println ("farvel"); gå i stykker; } out.println (inputLine); }}

Legg merke til at vi har lagt til en avslutningsbetingelse der while-sløyfen avsluttes når vi mottar et periodetegn.

Vi begynner EchoServer ved å bruke hovedmetoden akkurat som vi gjorde for GreetServer. Denne gangen starter vi den på en annen port som 4444 for å unngå forvirring.

De EchoClient den er lik Hilsen klient, slik at vi kan duplisere koden. Vi skiller dem for klarhet.

I en annen testklasse skal vi lage en test for å vise at flere forespørsler til EchoServer serveres uten at serveren lukker kontakten. Dette gjelder så lenge vi sender forespørsler fra samme klient.

Å håndtere flere kunder er en annen sak, som vi skal se i en senere seksjon.

La oss lage en oppsett metode for å starte en forbindelse med serveren:

@Før offentlig ugyldig oppsett () {client = new EchoClient (); client.startConnection ("127.0.0.1", 4444); }

Vi vil like lage en rive ned metode for å frigjøre alle ressursene våre, er dette den beste fremgangsmåten for alle tilfeller der vi bruker nettverksressurser:

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

La oss teste ekkoserveren vår med noen få forespørsler:

@Test offentlig ugyldig gittClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hallo"); String resp2 = client.sendMessage ("verden"); Streng resp3 = client.sendMessage ("!"); Streng resp4 = client.sendMessage ("."); assertEquals ("hei", resp1); assertEquals ("verden", resp2); assertEquals ("!", resp3); assertEquals ("farvel", resp4); }

Dette er en forbedring i forhold til det opprinnelige eksemplet, der vi bare kommuniserer en gang før serveren stengte forbindelsen vår; nå sender vi et avslutningssignal for å fortelle serveren når vi er ferdige med økten.

6. Server med flere klienter

Mye som det forrige eksemplet var en forbedring i forhold til det første, er det fortsatt ikke så bra en løsning. En server må ha kapasitet til å betjene mange klienter og mange forespørsler samtidig.

Håndtering av flere kunder er det vi skal dekke i denne delen.

En annen funksjon vi vil se her er at den samme klienten kan koble fra og koble til igjen, uten å få et nektet unntak for tilkobling eller tilbakestille tilkoblingen på serveren. Tidligere klarte vi ikke å gjøre dette.

Dette betyr at serveren vår vil være mer robust og motstandsdyktig på tvers av flere forespørsler fra flere klienter.

Hvordan vi vil gjøre dette er å lage en ny kontakt for hver nye klient og tjeneste som kunden ber om på en annen tråd. Antall klienter som serveres samtidig vil være lik antall tråder som kjører.

Hovedtråden vil kjøre en stund mens den lytter etter nye tilkoblinger.

Nok snakk, la oss lage en annen server som heter EchoMultiServer.java. Inne i den, vil vi lage en handlertrådsklasse for å administrere hver klients kommunikasjon på kontakten:

offentlig klasse EchoMultiServer {privat ServerSocket serverSocket; offentlig ugyldig start (int-port) {serverSocket = ny ServerSocket (port); mens (ekte) nye EchoClientHandler (serverSocket.accept ()). start (); } offentlig ugyldig stopp () {serverSocket.close (); } privat statisk klasse EchoClientHandler utvider tråd {private Socket clientSocket; privat PrintWriter ut; 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 (".". tilsvarer (inputLine)) {out.println ("bye"); gå i stykker; } out.println (inputLine); } in.close (); ut. lukk (); clientSocket.close (); }}

Legg merke til at vi nå ringer aksepterer inne i en samtidig som Løkke. Hver gang samtidig som loop utføres, blokkerer den på aksepterer ring til en ny klient kobles til, deretter handler tråden, EchoClientHandler, er opprettet for denne klienten.

Det som skjer inne i tråden er det vi tidligere gjorde i EchoServer der vi bare håndterte en enkelt klient. Så EchoMultiServer delegerer dette arbeidet til EchoClientHandler slik at den kan fortsette å lytte for flere kunder i samtidig som Løkke.

Vi vil fortsatt bruke EchoClient for å teste serveren, vil vi opprette flere klienter hver gang de sender og mottar flere meldinger fra serveren.

La oss starte serveren vår ved å bruke hovedmetoden på port 5555.

For klarhetens skyld vil vi fortsatt sette tester i en ny suite:

@Test offentlig ugyldig givenClient1_whenServerResponds_thenCorrect () {EchoClient client1 = new EchoClient (); client1.startConnection ("127.0.0.1", 5555); Streng msg1 = client1.sendMessage ("hei"); Streng msg2 = client1.sendMessage ("verden"); String terminate = client1.sendMessage ("."); assertEquals (msg1, "hei"); assertEquals (msg2, "verden"); assertEquals (avslutte, "farvel"); } @Test offentlig ugyldig givenClient2_whenServerResponds_thenCorrect () {EchoClient client2 = new EchoClient (); client2.startConnection ("127.0.0.1", 5555); Streng msg1 = client2.sendMessage ("hei"); Streng msg2 = client2.sendMessage ("verden"); Streng avslutte = client2.sendMessage ("."); assertEquals (msg1, "hei"); assertEquals (msg2, "verden"); assertEquals (avslutte, "farvel"); }

Vi kan lage så mange av disse testtilfellene som vi vil, hver gyter en ny klient, og serveren vil betjene dem alle.

7. Konklusjon

I denne opplæringen har vi fokusert på en introduksjon til sockets programmering over TCP / IP og skrev en enkel klient / server-applikasjon i Java.

Hele kildekoden for artikkelen finner du - som vanlig - i GitHub-prosjektet.