deze tutorial is een introductie tot socket programmeren in Java, beginnend met een eenvoudig client-server voorbeeld dat de basiseigenschappen van Java I/O demonstreert.u zult kennismaken met zowel het originele
java.io
pakket en NIO, de niet-blokkerende I/O (java.nio
) API ‘ s geà ntroduceerd in Java 1.4. Tot slot zie je een voorbeeld dat Java networking demonstreert zoals geà mplementeerd vanuit Java 7, in NIO.2.,
Socket programmeren komt neer op twee systemen die met elkaar communiceren. Over het algemeen, netwerk communicatie komt in twee smaken: Transport Control Protocol (TCP) en User Datagram Protocol (UDP). TCP en UDP worden gebruikt voor verschillende doeleinden en beide hebben unieke beperkingen:
- TCP is een relatief eenvoudig en betrouwbaar protocol dat een client in staat stelt om een verbinding te maken met een server en de twee systemen om te communiceren. In TCP, elke entiteit weet dat de communicatie payloads zijn ontvangen.,
- UDP is een verbindingsloos protocol en is goed voor scenario ‘ s waarbij je niet per se elk pakket nodig hebt om op zijn bestemming aan te komen, zoals het streamen van media.
om het verschil tussen TCP en UDP te waarderen, overweeg dan wat er zou gebeuren als je video streamt vanaf je favoriete website en het frames zou laten vallen. Wilt u dat de client uw film vertraagt om de ontbrekende frames te ontvangen of wilt u dat de video verder wordt afgespeeld? Video streaming protocollen meestal gebruik maken van UDP., Omdat TCP de levering garandeert, is het het protocol van keuze voor HTTP, FTP, SMTP, POP3, enzovoort.
in deze tutorial introduceer ik socket programming in Java. Ik presenteer een reeks client-server voorbeelden die functies van de oorspronkelijke Java I/O framework demonstreren, dan geleidelijk overgaan tot het gebruik van functies geà ntroduceerd in NIO.2.
Old-school Java sockets
in implementaties voorafgaand aan NIO wordt Java TCP client socket code afgehandeld door dejava.net.Socket
klasse., De volgende code opent een verbinding met een server:
Socket socket = new Socket( server, port );
zodra onze socket
instantie is verbonden met de server kunnen we beginnen met het verkrijgen van input en output streams naar de sever. Invoerstromen worden gebruikt om gegevens van de server te lezen, terwijl uitvoerstromen worden gebruikt om gegevens naar de server te schrijven., We kunnen de volgende methoden uitvoeren om input en output streams te verkrijgen:
InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();
omdat dit gewone streams zijn, dezelfde streams die we zouden gebruiken om van een bestand te lezen en naar een bestand te schrijven, kunnen we ze converteren naar de vorm die het beste onze use case dient. We kunnen bijvoorbeeld de OutputStream
omwikkelen met een PrintStream
zodat we gemakkelijk tekst kunnen schrijven met methoden als println()
., Een ander voorbeeld: we kunnen de InputStream
omwikkelen met een BufferedReader
, via een InputStreamReader
, om tekst gemakkelijk te lezen met methoden als readLine()
.
Java socket client voorbeeld
laten we een kort voorbeeld doornemen dat een HTTP GET uitvoert tegen een HTTP server., HTTP is geavanceerder dan ons voorbeeld toestaat, maar we kunnen client code schrijven om de eenvoudigste zaak te behandelen: vraag een resource aan van de server en de server geeft het antwoord terug en sluit de stream. Dit geval vereist de volgende stappen:
- Maak een socket aan voor de webserver die luistert op poort 80.
- Verkrijg een
PrintStream
naar de server en verzend het verzoekGET PATH HTTP/1.0
, waarbijPATH
de gevraagde bron op de server is., Als we bijvoorbeeld de root van een website willen openen, dan zou het pad/
zijn. - Verkrijg een
InputStream
naar de server, wikkel het met eenBufferedReader
en lees het antwoord regel voor regel.
Listing 1 toont de broncode voor dit voorbeeld.
Lijst 1. SimpleSocketClientExample.java
Listing 1 accepteert twee commandoregelargumenten: de server waarmee verbinding moet worden gemaakt (ervan uitgaande dat we verbinding maken met de server op poort 80) en de bron die moet worden opgehaald., Het maakt een Socket
aan die naar de server wijst En expliciet poort 80
specificeert. Het voert vervolgens het commando uit:
GET PATH HTTP/1.0
bijvoorbeeld:
GET / HTTP/1.0
Wat is er zojuist gebeurd?
wanneer u een webpagina van een webserver opzoekt, zoals , gebruikt de HTTP-client DNS-servers om het adres van de server te vinden: het begint met het vragen aan de top-level domain server voor het
com
domein waarbij de gezaghebbende domain-name server is voor de ., Vervolgens vraagt het die domain-name server om het IP adres (of adressen) voor
. Vervolgens opent het een socket naar die server op poort 80. (Of, als u een andere poort wilt definiëren, kunt u dit doen door een dubbele punt toe te voegen gevolgd door het poortnummer, bijvoorbeeld:
:8080
.) Ten slotte voert de HTTP-client de opgegeven HTTP-methode uit, zoals GET
, POST
, PUT
, DELETE
, HEAD
, of OPTI/ONS
. Elke methode heeft zijn eigen syntaxis., Zoals getoond in de bovenstaande codesnips, vereist de methode GET
een pad gevolgd door HTTP/version number
en een lege regel. Als we HTTP headers wilden toevoegen, hadden we dat kunnen doen voordat we de nieuwe regel invoerden.
In Lijst 1 hebben we een OutputStream
opgehaald en verpakt in een PrintStream
zodat we onze op tekst gebaseerde commando ‘ s gemakkelijker konden uitvoeren., Onze code verkreeg een InputStream
, verpakt in een InputStreamReader
, die geconverteerd naar een Reader
, en vervolgens verpakt in een BufferedReader
. We gebruikten dePrintStream
om onzeGET
methode uit te voeren en vervolgens deBufferedReader
om de respons regel voor regel te lezen totdat we eennull
antwoord kregen, wat aangeeft dat de socket gesloten was.,
Voer nu deze klasse uit en geef het de volgende argumenten door:
java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /
u zou uitvoer moeten zien zoals hieronder:
deze uitvoer toont een testpagina op de website van JavaWorld. Het antwoordde terug dat het HTTP versie 1.1 spreekt en het antwoord is 200 OK
.
Java socket server voorbeeld
we hebben de client kant behandeld en gelukkig is het communicatie aspect van de server kant net zo eenvoudig., Vanuit een simplistisch perspectief is het proces als volgt:
- Creëer een
ServerSocket
, met een poort om op te luisteren. - roep de
ServerSocket
‘saccept()
methode aan om te luisteren op de geconfigureerde poort voor een clientverbinding. - wanneer een client verbinding maakt met de server, geeft de methode
accept()
eenSocket
waardoor de server met de client kan communiceren., Dit is dezelfdeSocket
klasse die we voor onze client hebben gebruikt, dus het proces is hetzelfde: verkrijg eenInputStream
om van de client te lezen en eenOutputStream
schrijf naar de client. - als uw server schaalbaar moet zijn, wilt u de
Socket
doorgeven aan een andere thread om te verwerken, zodat uw server kan blijven luisteren naar extra verbindingen. - roep de
ServerSocket
‘saccept()
methode opnieuw aan om te luisteren naar een andere verbinding.,
zoals je snel zult zien, zou NIO ‘ s behandeling van dit scenario een beetje anders zijn., Voor nu, hoewel, we kunnen direct maken van een ServerSocket
door een poort te luisteren (meer over ServerSocketFactory
‘ s in de volgende paragraaf):
ServerSocket serverSocket = new ServerSocket( port );
En nu kunnen we het accepteren van binnenkomende verbindingen via de accept()
methode:
Socket socket = serverSocket.accept();// Handle the connection ...
Multithreaded programmeren met Java sockets
Listing 2, hieronder zet alle van de server code zo ver bij elkaar in een wat meer robuuste voorbeeld dat gebruik maakt van draden voor het verwerken van meerdere aanvragen., De getoonde server is een echo server, wat betekent dat het echo ‘ s terug elk bericht dat het ontvangt.
hoewel het voorbeeld in lijst 2 niet ingewikkeld is, anticipeert het wel op wat er in de volgende sectie over NIO komt. Besteed speciale aandacht aan de hoeveelheid threading code die we moeten schrijven om een server te bouwen die meerdere gelijktijdige verzoeken kan verwerken.
Lijst 2. SimpleSocketServer.java
in Listing 2 maken we een nieuwe SimpleSocketServer
instantie aan en starten de server., Dit is vereist omdat deSimpleSocketServer
strektThread
om een nieuwe thread aan te maken om de blokkering te verwerkenaccept()
aanroep die u ziet in deread()
methode. Derun()
methode zit in een lus en accepteert clientaanvragen en maaktRequestHandler
threads om het verzoek te verwerken. Nogmaals, dit is relatief eenvoudige code, maar omvat ook een behoorlijke hoeveelheid threaded programmeren.,
merk ook op dat de RequestHandler
de clientcommunicatie verwerkt zoals de code in Lijst 1 deed: het wikkelt de OutputStream
met een PrintStream
om eenvoudig schrijven te vergemakkelijken en, op dezelfde manier, wikkelt de InputStream
met een BufferedReader
voor eenvoudig lezen. Voor zover een server gaat, leest het regels van de client en echoot ze terug naar de client. Als de client een lege regel verstuurt dan is het gesprek voorbij en sluit de RequestHandler
de socket.
NIO en NIO.,2
voor veel toepassingen is het basis Java socket programmeermodel dat we zojuist hebben onderzocht voldoende. Voor toepassingen met intensievere I / O of asynchrone input / output wilt u vertrouwd zijn met de niet-blokkerende API ‘ s geà ntroduceerd in Java NIO en NIO.2.
het JDK 1.4 NIO pakket biedt de volgende belangrijke functies:
- kanalen zijn ontworpen om bulk transfers van de ene NIO buffer naar de andere te ondersteunen.
- Buffers vertegenwoordigen een aaneengesloten geheugenblok gekoppeld door een eenvoudige set operaties.,
- non-Blocking Input / Output is een reeks klassen die kanalen blootstellen aan gangbare I/O-bronnen zoals bestanden en sockets.
wanneer je programmeert met NIO, open je een kanaal naar je bestemming en lees je gegevens in een buffer van de bestemming, schrijf je de gegevens naar een buffer en verzend je die naar je bestemming., We duiken in het opzetten van een aansluiting en het verkrijgen van een kanaal te kort, maar eerst laten we het proces van het gebruik van een buffer:
- Schrijven van gegevens in een buffer
- Oproep in de buffer ‘ s
flip()
methode voor te bereiden voor het lezen - Lees de data uit de buffer
- Oproep in de buffer ‘ s
clear()
ofcompact()
methode voor te bereiden om meer gegevens te ontvangen
het schrijven van data in de buffer de buffer kent het bedrag van de gegevens die worden geschreven in het., Hij behoudt zijn drie eigenschappen, waarvan de betekenis verschillen als de buffer is in de modus lezen of schrijven-modus:
- Positie: In de modus schrijven, de eerste positie is 0 en het houdt de huidige positie wordt geschreven in de buffer; na je draai een buffer om het in te lezen-modus, het zet de stand op 0 en houdt de huidige positie in de buffer gelezen,
- Capaciteit: De grootte van de buffer
- Limiet: schrijven-modus, de limiet bepaalt hoeveel gegevens kunnen worden geschreven in de buffer; in te lezen-modus, de limiet bepaalt hoeveel gegevens kunnen worden gelezen uit de buffer.,
Java I / O demo: Echo server met NIO.2
NIO.2, dat werd geïntroduceerd in JDK 7, breidt Java ‘ s niet-blokkerende I / O-bibliotheken uit om ondersteuning toe te voegen voor bestandssysteemtaken, zoals de java.nio.file
pakket en java.nio.file.Path
klasse en onthult een nieuwe bestandssysteem API. Met die achtergrond in het achterhoofd, laten we een nieuwe Echo Server te schrijven met behulp van NIO.2 ’s AsynchronousServerSocketChannel
.
de AsynchronousServerSocketChannel
biedt een niet-blokkerend asynchrone kanaal voor stream-georiënteerde luistersockets., Om het te gebruiken, voeren we eerst zijn statische open()
methode uit en dan bind()
het naar een specifieke poort. Vervolgens zullen we zijn accept()
methode uitvoeren, waarbij we een klasse doorgeven die de CompletionHandler
interface implementeert. Meestal vind je die handler gemaakt als een anonieme innerlijke klasse.
Listing 3 toont de broncode voor onze nieuwe asynchrone Echo Server.
Lijst 3. SimpleSocketServer.,java
in Lijst 3 maken we eerst een nieuwe AsynchronousServerSocketChannel
en binden het dan aan poort 5000:
final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));
van deze AsynchronousServerSocketChannel
, roepen we accept()
aan om het te vertellen om te beginnen met luisteren naar verbindingen, waarbij een aangepaste CompletionHandler
instance wordt doorgegeven. Wanneer we accept()
aanroepen, keert het onmiddellijk terug., Merk op dat dit voorbeeld verschilt van de ServerSocket
klasse in Lijst 1; terwijl de accept()
methode geblokkeerd totdat een client ermee verbonden is, behandelt de AsynchronousServerSocketChannel
accept()
methode het voor ons.
de handler voor voltooiing
onze volgende verantwoordelijkheid is om een CompletionHandler
klasse aan te maken en een implementatie van de completed()
en failed()
methoden te bieden., Decompleted()
methode wordt aangeroepen wanneer deAsynchronousServerSocketChannel
een verbinding van een client ontvangt en het bevat eenAsynchronousSocketChannel
naar de client. De methode completed()
accepteert eerst de verbinding van de methode AsynchronousServerSocketChannel
en begint dan te communiceren met de client. Het eerste wat het doet is een “Hallo” bericht schrijven: het bouwt een string, converteert het naar een byte array, en geeft het dan door aan ByteBuffer.wrap()
om een ByteBuffer
te construeren., DeByteBuffer
kan dan worden doorgegevenAsynchronousSocketChannel
‘swrite()
methode.
om van de client te lezen, maken we een nieuwe ByteBuffer
door zijn allocate(4096)
aan te roepen (die een 4K-buffer maakt), waarna we de AsynchronousSocketChannel
’s read()
aanroepen. De read()
geeft een Future<Integer>
waarop we get()
kunnen aanroepen om het aantal bytes op te halen dat van de client wordt gelezen., In dit voorbeeld geven we get()
een time-out waarde van 20 seconden: als we geen antwoord krijgen in 20 seconden dan zal de get()
methode een TimeoutException
gooien. Onze regel voor deze echo server is dat als we 20 seconden stilte in acht nemen, we het gesprek beëindigen.
vervolgens controleren we de positie van de buffer, die de locatie zal zijn van de laatste byte ontvangen van de client. Als de client een lege regel verstuurt dan ontvangen we twee bytes: een carriage return en een line feed., De controle zorgt ervoor dat als de client een lege regel stuurt dat we het als een indicator nemen dat de client klaar is met het gesprek. Als we zinvolle gegevens hebben dan noemen we de ByteBuffer
’s flip()
methode om het voor te bereiden voor het lezen. We maken een tijdelijke byte-array om het aantal bytes te bevatten dat van de client wordt gelezen en roepen dan de ByteBuffer
’s get()
aan om gegevens in die byte-array te laden. Tenslotte converteren we de byte array naar een string door een nieuwe String
instantie aan te maken., We echo de regel terug naar de client door de string te converteren naar een byte array, die door te geven aan de ByteBuffer.wrap()
methode en het aanroepen van de AsynchronousSocketChannel
’s write()
methode. Nu hebben we clear()
de ByteBuffer
, wat recall betekent dat het de position
naar nul plaatst en de ByteBuffer
in schrijfmodus plaatst, en dan lezen we de volgende regel van de client.,
het enige waar je op moet letten is dat de main()
methode, die de server aanmaakt, ook een 60 seconden timer instelt om de toepassing draaiende te houden. Omdat de methode AsynchronousSocketChannel
’s accept()
onmiddellijk terugkeert, zal onze toepassing onmiddellijk stoppen als we de Thread.sleep()
niet hebben.,
om dit uit te testen, start u de server en maakt u verbinding met de server met behulp van een telnet-client:
telnet localhost 5000
stuur een paar tekenreeksen naar de server, merk op dat ze naar u worden echo ‘ s, en stuur dan een lege regel om het gesprek te beëindigen.
concluderend
In dit artikel heb ik twee benaderingen van socket programmeren met Java gepresenteerd: de traditionele aanpak die met Java 1.0 is geïntroduceerd en de nieuwere, niet-blokkerende NIO en NIO.2 benaderingen geà ntroduceerd in Java 1.4 en Java 7, respectievelijk., Je hebt gezien verschillende iteraties van een Java socket client en een Java socket server voorbeeld, waaruit zowel het nut van de basis Java I/O en een aantal scenario ‘ s waar niet-blokkerende I/O verbetert de Java socket programmeermodel. Met behulp van niet-blokkerende I/O kunt u Java-netwerkapplicaties programmeren om meerdere gelijktijdige verbindingen af te handelen zonder dat u meerdere threadcollecties hoeft te beheren. U kunt ook profiteren van de nieuwe server schaalbaarheid die is ingebouwd in NIO en NIO.2.