Een gids voor Java-sockets

1. Overzicht

De voorwaarde stopcontact programmeren verwijst naar het schrijven van programma's die worden uitgevoerd op meerdere computers waarin de apparaten allemaal met elkaar zijn verbonden via een netwerk.

Er zijn twee communicatieprotocollen die u kunt gebruiken voor het programmeren van stopcontacten: User Datagram Protocol (UDP) en Transfer Control Protocol (TCP).

Het belangrijkste verschil tussen de twee is dat UDP geen verbinding heeft, wat betekent dat er geen sessie is tussen de client en de server, terwijl TCP verbindingsgeoriënteerd is, wat betekent dat er eerst een exclusieve verbinding tot stand moet worden gebracht tussen de client en de server om communicatie te laten plaatsvinden.

Deze tutorial presenteert een inleiding tot het programmeren van sockets via TCP / IP netwerken en laat zien hoe u client / servertoepassingen in Java schrijft. UDP is geen standaardprotocol en wordt daarom niet vaak aangetroffen.

2. Projectconfiguratie

Java biedt een verzameling klassen en interfaces die zorgen voor communicatiedetails op laag niveau tussen de client en de server.

Deze zijn meestal opgenomen in het java.net pakket, dus we moeten de volgende import maken:

importeer java.net. *;

We hebben ook de java.io pakket dat ons invoer- en uitvoerstromen geeft om naar te schrijven en van te lezen tijdens het communiceren:

importeer java.io. *;

Eenvoudigheidshalve draaien we onze client- en serverprogramma's op dezelfde computer. Als we ze op verschillende netwerkcomputers zouden uitvoeren, is het enige dat zou veranderen het IP-adres, in dit geval gebruiken we localhost Aan 127.0.0.1.

3. Eenvoudig voorbeeld

Laten we onze handen vuil maken met de meeste basisvoorbeelden van een client en een server. Het wordt een tweerichtingscommunicatietoepassing waarbij de client de server begroet en de server reageert.

Laten we de servertoepassing maken in een klasse met de naam GreetServer.java met de volgende code.

We nemen de hoofd methode en de globale variabelen om de aandacht te vestigen op hoe we alle servers in dit artikel zullen draaien. In de rest van de voorbeelden in de artikelen laten we dit soort meer repetitieve code weg:

openbare klasse GreetServer {privé ServerSocket serverSocket; privé Socket clientSocket; privé PrintWriter uit; privé BufferedReader in; openbare ongeldige start (int poort) {serverSocket = nieuwe ServerSocket (poort); clientSocket = serverSocket.accept (); out = nieuwe PrintWriter (clientSocket.getOutputStream (), true); in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ())); Tekenreeksgroet = in.readLine (); if ("hallo server" .equals (groet)) {out.println ("hallo client"); } else {out.println ("niet-herkende begroeting"); }} public void stop () {in.close (); out.close (); clientSocket.close (); serverSocket.close (); } public static void main (String [] args) {GreetServer server = nieuwe GreetServer (); server.start (6666); }}

Laten we ook een klant maken met de naam GreetClient.java met deze code:

openbare klasse GreetClient {privé Socket clientSocket; privé PrintWriter uit; privé BufferedReader in; public void startConnection (String ip, int port) {clientSocket = new Socket (ip, port); out = nieuwe PrintWriter (clientSocket.getOutputStream (), true); in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ())); } public String sendMessage (String msg) {out.println (msg); String resp = in.readLine (); terugkeer resp; } openbare ongeldige stopConnection () {in.close (); out.close (); clientSocket.close (); }}

Laten we de server starten; in uw IDE doet u dit door het simpelweg uit te voeren als een Java-applicatie.

En laten we nu een begroeting naar de server sturen met behulp van een unit-test, die bevestigt dat de server daadwerkelijk een begroeting stuurt als reactie:

@Test openbare ongeldig gegevenGreetingClient_whenServerRespondsWhenStarted_thenCorrect () {GreetClient client = nieuwe GreetClient (); client.startConnection ("127.0.0.1", 6666); String response = client.sendMessage ("hallo server"); assertEquals ("hallo klant", antwoord); }

Maak je geen zorgen als je niet helemaal begrijpt wat er hier gebeurt, want dit voorbeeld is bedoeld om ons een idee te geven van wat we later in het artikel kunnen verwachten.

In de volgende secties zullen we ontleden socket communicatie gebruik dit eenvoudige voorbeeld en duik dieper in de details met meer voorbeelden.

4. Hoe stopcontacten werken

We zullen het bovenstaande voorbeeld gebruiken om verschillende delen van deze sectie te doorlopen.

Per definitie is een stopcontact is een eindpunt van een tweewegcommunicatieverbinding tussen twee programma's die op verschillende computers in een netwerk worden uitgevoerd. Een socket is gebonden aan een poortnummer, zodat de transportlaag de applicatie kan identificeren waarnaar de gegevens zijn bestemd om te worden verzonden.

4.1. De server

Gewoonlijk draait een server op een specifieke computer in het netwerk en heeft deze een socket die is gebonden aan een specifiek poortnummer. In ons geval gebruiken we dezelfde computer als de client en startten we de server op poort 6666:

ServerSocket serverSocket = nieuwe ServerSocket (6666);

De server wacht gewoon, luisterend naar de socket voor een client om een ​​verbindingsverzoek te doen. Dit gebeurt in de volgende stap:

Socket clientSocket = serverSocket.accept ();

Wanneer de servercode de aanvaarden methode, het blokkeert totdat een client er een verbindingsverzoek naar doet.

Als alles goed gaat, is de server accepteert de verbinding. Bij acceptatie krijgt de server een nieuwe socket, clientSocket, gebonden aan dezelfde lokale poort, 6666, en heeft ook het externe eindpunt ingesteld op het adres en de poort van de client.

Op dit punt is het nieuwe Stopcontact object plaatst de server in directe verbinding met de client, we hebben dan toegang tot de output- en inputstreams om respectievelijk berichten naar en van de client te schrijven en te ontvangen:

PrintWriter uit = nieuwe PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ()));

Vanaf hier is de server in staat om eindeloos berichten uit te wisselen met de client totdat de socket met zijn streams wordt gesloten.

In ons voorbeeld kan de server echter alleen een begroetingsreactie sturen voordat de verbinding wordt verbroken. Dit betekent dat als we onze test opnieuw zouden uitvoeren, de verbinding zou worden geweigerd.

Om continuïteit in de communicatie mogelijk te maken, zullen we moeten lezen uit de invoerstroom in een terwijl loop en pas af wanneer de klant een beëindigingsverzoek verzendt, we zullen dit in de volgende sectie in actie zien.

Voor elke nieuwe client heeft de server een nieuwe socket nodig die wordt geretourneerd door de aanvaarden bellen. De serverSocket wordt gebruikt om te blijven luisteren naar verbindingsverzoeken terwijl wordt voldaan aan de behoeften van de verbonden clients. In ons eerste voorbeeld hebben we dit nog niet toegestaan.

4.2. De cliënt

De client moet de hostnaam of het IP-adres kennen van de machine waarop de server draait en het poortnummer waarop de server luistert.

Om een ​​verbindingsverzoek te doen, probeert de client contact te maken met de server op de computer en poort van de server:

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

De client moet zichzelf ook identificeren bij de server, zodat hij zich bindt aan een lokaal poortnummer, toegewezen door het systeem, dat hij zal gebruiken tijdens deze verbinding. Wij lossen dit zelf niet op.

De bovenstaande constructor maakt alleen een nieuwe socket als de server dat heeft geaccepteerd de verbinding, anders krijgen we een uitzondering voor verbinding geweigerd. Als het met succes is gemaakt, kunnen we er invoer- en uitvoerstromen van verkrijgen om te communiceren met de server:

PrintWriter uit = nieuwe PrintWriter (clientSocket.getOutputStream (), true); BufferedReader in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ()));

De input-stream van de client is verbonden met de output-stream van de server, net zoals de input-stream van de server is verbonden met de output-stream van de client.

5. Continue communicatie

Onze huidige server blokkeert totdat een client er verbinding mee maakt en blokkeert vervolgens opnieuw om naar een bericht van de client te luisteren, na het enkele bericht wordt de verbinding verbroken omdat we niet met continuïteit hebben omgegaan.

Het is dus alleen nuttig bij ping-verzoeken, maar stel je voor dat we een chatserver zouden willen implementeren, dan zou continue heen en weer communicatie tussen server en client zeker nodig zijn.

We zullen een while-lus moeten maken om continu de invoerstroom van de server voor inkomende berichten te observeren.

Laten we een nieuwe server maken met de naam EchoServer.java waarvan het enige doel is om alle berichten die het van klanten ontvangt te echoën:

openbare klasse EchoServer {openbare ongeldige start (int poort) {serverSocket = nieuwe ServerSocket (poort); clientSocket = serverSocket.accept (); out = nieuwe PrintWriter (clientSocket.getOutputStream (), true); in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". equals (inputLine)) {out.println ("tot ziens"); breken; } out.println (inputLine); }}

Merk op dat we een beëindigingsvoorwaarde hebben toegevoegd waarbij de while-lus wordt afgesloten wanneer we een punt-teken ontvangen.

We zullen beginnen EchoServer met behulp van de hoofdmethode, net zoals we deden voor de GreetServer. Deze keer starten we het op een andere poort, zoals 4444 om verwarring te voorkomen.

De EchoClient is gelijkaardig aan GreetClient, zodat we de code kunnen dupliceren. We scheiden ze voor de duidelijkheid.

In een andere testklasse zullen we een test maken om aan te tonen dat meerdere verzoeken aan de EchoServer wordt bediend zonder dat de server de socket sluit. Dit is waar zolang we verzoeken van dezelfde klant verzenden.

Omgaan met meerdere klanten is een ander geval, dat we in een volgende sectie zullen zien.

Laten we een opstelling methode om een ​​verbinding met de server tot stand te brengen:

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

We zullen ook een scheuren methode om al onze bronnen vrij te geven, dit is de beste praktijk voor elk geval waarin we netwerkbronnen gebruiken:

@After openbare leegte tearDown () {client.stopConnection (); }

Laten we dan onze echoserver testen met een paar verzoeken:

@Test openbare ongeldig gegevenClient_whenServerEchosMessage_thenCorrect () {String resp1 = client.sendMessage ("hallo"); String resp2 = client.sendMessage ("wereld"); String resp3 = client.sendMessage ("!"); String resp4 = client.sendMessage ("."); assertEquals ("hallo", resp1); assertEquals ("wereld", resp2); assertEquals ("!", resp3); assertEquals ("tot ziens", resp4); }

Dit is een verbetering ten opzichte van het oorspronkelijke voorbeeld, waarbij we slechts één keer zouden communiceren voordat de server onze verbinding verbrak; nu sturen we een beëindigingssignaal om de server te vertellen wanneer we klaar zijn met de sessie.

6. Server met meerdere clients

Hoewel het vorige voorbeeld een verbetering was ten opzichte van het eerste, is het nog steeds niet zo'n geweldige oplossing. Een server moet de capaciteit hebben om veel klanten en veel verzoeken tegelijkertijd te bedienen.

Omgaan met meerdere klanten is wat we in deze sectie gaan behandelen.

Een ander kenmerk dat we hier zullen zien, is dat dezelfde client de verbinding kan verbreken en opnieuw kan verbinden, zonder een uitzondering voor een geweigerde verbinding of een reset van de verbinding op de server. Eerder konden we dit niet doen.

Dit betekent dat onze server robuuster en veerkrachtiger zal zijn voor meerdere verzoeken van meerdere klanten.

Hoe we dit zullen doen, is om een ​​nieuwe socket te maken voor elke nieuwe klant en service die de klant vraagt ​​op een andere thread. Het aantal clients dat tegelijkertijd wordt bediend, is gelijk aan het aantal actieve threads.

De hoofdthread loopt een while-lus terwijl deze naar nieuwe verbindingen luistert.

Genoeg gepraat, laten we een andere server maken met de naam EchoMultiServer.java. Binnenin zullen we een handlerthread-klasse maken om de communicatie van elke klant op zijn socket te beheren:

openbare klasse EchoMultiServer {privé ServerSocket serverSocket; openbare ongeldige start (int poort) {serverSocket = nieuwe ServerSocket (poort); while (true) nieuwe EchoClientHandler (serverSocket.accept ()). start (); } public void stop () {serverSocket.close (); } private statische klasse EchoClientHandler breidt Thread uit {private Socket clientSocket; privé PrintWriter uit; privé BufferedReader in; openbare EchoClientHandler (socket socket) {this.clientSocket = socket; } public void run () {out = nieuwe PrintWriter (clientSocket.getOutputStream (), true); in = nieuwe BufferedReader (nieuwe InputStreamReader (clientSocket.getInputStream ())); String inputLine; while ((inputLine = in.readLine ())! = null) {if (".". equals (inputLine)) {out.println ("bye"); breken; } out.println (inputLine); } in.close (); out.close (); clientSocket.close (); }}

Merk op dat we nu bellen aanvaarden binnen een terwijl lus. Elke keer dat de terwijl lus wordt uitgevoerd, het blokkeert op de aanvaarden bellen totdat een nieuwe client verbinding maakt, vervolgens de handler-thread, EchoClientHandler, is gemaakt voor deze klant.

Wat er in de thread gebeurt, is wat we eerder deden in de EchoServer waar we slechts één klant behandelden. Dus de EchoMultiServer delegeert dit werk aan EchoClientHandler zodat het kan blijven luisteren naar meer klanten in het terwijl lus.

We zullen nog steeds gebruiken EchoClient om de server te testen, zullen we deze keer meerdere clients maken die elk meerdere berichten van de server verzenden en ontvangen.

Laten we onze server starten met de belangrijkste methode op poort 5555.

Voor alle duidelijkheid: we zullen nog steeds tests in een nieuwe suite plaatsen:

@Test openbare ongeldig gegevenClient1_whenServerResponds_thenCorrect () {EchoClient client1 = nieuwe EchoClient (); client1.startConnection ("127.0.0.1", 5555); String msg1 = client1.sendMessage ("hallo"); String msg2 = client1.sendMessage ("wereld"); String terminate = client1.sendMessage ("."); assertEquals (msg1, "hallo"); assertEquals (msg2, "wereld"); assertEquals (beëindigen, "doei"); } @Test openbare ongeldig gegevenClient2_whenServerResponds_thenCorrect () {EchoClient client2 = nieuwe EchoClient (); client2.startConnection ("127.0.0.1", 5555); String msg1 = client2.sendMessage ("hallo"); String msg2 = client2.sendMessage ("wereld"); String terminate = client2.sendMessage ("."); assertEquals (msg1, "hallo"); assertEquals (msg2, "wereld"); assertEquals (beëindigen, "doei"); }

We zouden zoveel van deze testcases kunnen maken als we willen, waarbij elk een nieuwe client voortbrengt en de server ze allemaal zal bedienen.

7. Conclusie

In deze tutorial hebben we ons gericht op een inleiding tot het programmeren van sockets via TCP / IP en schreef een eenvoudige Client / Server-applicatie in Java.

De volledige broncode voor het artikel is - zoals gewoonlijk - te vinden in het GitHub-project.


$config[zx-auto] not found$config[zx-overlay] not found