denna handledning är en introduktion till socket programmering i Java, som börjar med en enkel klient-server exempel visar de grundläggande funktionerna i Java I/O. du kommer att introduceras till både den ursprungliga
java.io
paket och NIO, den icke-blockerande I / O (java.nio
) API: er infördes i Java 1.4. Slutligen ser du ett exempel som visar Java-nätverk som implementerats från Java 7 framåt, i NIO.2.,
Socket programmering kokar ner till två system som kommunicerar med varandra. Generellt kommer nätverkskommunikation i två smaker: Transport Control Protocol (TCP) och User Datagram Protocol (UDP). TCP och UDP används för olika ändamål och båda har unika begränsningar:
- TCP är relativt enkelt och pålitligt protokoll som gör det möjligt för en klient att ansluta till en server och de två systemen för att kommunicera. I TCP vet varje enhet att dess kommunikationsbetalningar har mottagits.,
- UDP är ett anslutningslöst protokoll och är bra för scenarier där du inte nödvändigtvis behöver varje paket för att komma fram till sin destination, till exempel media streaming.
för att uppskatta skillnaden mellan TCP och UDP, överväga vad som skulle hända om du streamade video från din favoritwebbplats och det tappade ramar. Skulle du föredra att klienten saktar ner din film för att få de saknade ramarna eller föredrar du att videon fortsätter att spela? Video streaming protokoll vanligtvis utnyttja UDP., Eftersom TCP garanterar leverans, det är protokollet val för HTTP, FTP, SMTP, POP3, och så vidare.
i den här handledningen introducerar jag dig till socket-programmering i Java. Jag presenterar en serie klientserver exempel som visar funktioner från den ursprungliga Java I / O ramverket, sedan gradvis gå vidare till att använda funktioner som införts i NIO.2.
Java-uttag i gamla skolan
i implementeringar före NIO hanteras Java TCP client socket code av klassenjava.net.Socket
., Följande kod öppnar en anslutning till en server:
Socket socket = new Socket( server, port );
När vårsocket
– instans är ansluten till servern kan vi börja få in-och utdataströmmar till Severn. Inmatningsströmmar används för att läsa data från servern medan utmatningsströmmar används för att skriva data till servern., Vi kan utföra följande metoder för att få in-och utdataströmmar:
InputStream in = socket.getInputStream();OutputStream out = socket.getOutputStream();
eftersom det här är vanliga strömmar, samma strömmar som vi skulle använda för att läsa från och skriva till en fil, kan vi konvertera dem till formuläret som bäst tjänar vårt användningsfall. Till exempel kan vi slå in OutputStream
med en PrintStream
så att vi enkelt kan skriva text med metoder som println()
., För ett annat exempel kan vi slå in InputStream
med en BufferedReader
, via en InputStreamReader
, för att enkelt läsa text med metoder som readLine()
.
Java socket client example
låt oss arbeta igenom ett kort exempel som utför en HTTP GET mot en HTTP-server., HTTP är mer sofistikerad än vårt exempel tillåter, men vi kan skriva klientkod för att hantera det enklaste fallet: begära en resurs från servern och servern returnerar svaret och stänger strömmen. Det här fallet kräver följande steg:
- skapa ett uttag till webbservern som lyssnar på port 80.
- hämta en
PrintStream
till servern och skicka begäranGET PATH HTTP/1.0
, därPATH
är den begärda resursen på servern., Om vi till exempel ville öppna roten till en webbplats skulle sökvägen vara/
. - hämta en
InputStream
till servern, linda den med enBufferedReader
och läs svarslinjen för rad.
Notering 1 visar källkoden för detta exempel.
Lista 1. Helt enkelt.java
Lista 1 accepterar två kommandoradsargument: servern att ansluta till (förutsatt att vi ansluter till servern på port 80) och resursen att hämta., Det skapar enSocket
som pekar på servern och uttryckligen anger port80
. Det utför sedan kommandot:
GET PATH HTTP/1.0
till exempel:
GET / HTTP/1.0
vad hände just?
När du hämtar en webbsida från en webbserver, till exempel, använder HTTP-klienten DNS-servrar för att hitta serverns adress: Den börjar med att fråga toppdomänservern för domänen
com
där den auktoritativa domännamnsservern är för., Då frågar den domännamnsservern för IP-adressen (eller adresserna) för
. Därefter öppnar det ett uttag till den servern på port 80. (Eller, om du vill definiera en annan port, kan du göra det genom att lägga till ett kolon följt av portnumret, till exempel:
:8080
.) Slutligen utför HTTP-klienten den angivna HTTP-metoden, till exempel GET
, POST
, PUT
, DELETE
, HEAD
eller OPTI/ONS
. Varje metod har sin egen syntax., Som visas i ovanstående kod snips kräver GET
– metoden en sökväg följt av HTTP/version number
och en tom linje. Om vi ville lägga till HTTP-rubriker kunde vi ha gjort det innan vi gick in i den nya raden.
i Lista 1, vi hämtade en OutputStream
och lindade den i en PrintStream
så att vi lättare kunde utföra våra textbaserade kommandon., Vår kod erhöll en InputStream
, insvept som i en InputStreamReader
, som konverterade den till en Reader
, och sedan insvept som i en BufferedReader
. Vi använde PrintStream
för att utföra vår GET
-metod och använde sedan BufferedReader
för att läsa svarslinjen för rad tills vi fick ett null
-svar, vilket indikerar att uttaget hade stängts.,
kör nu den här klassen och skicka följande argument:
java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /
Du bör se utdata som liknar vad som finns nedan:
denna utdata visar en testsida på Javaworlds webbplats. Det svarade tillbaka att det talar HTTP version 1.1 och svaret är 200 OK
.
Java socket server example
Vi har täckt klientsidan och lyckligtvis är kommunikationsaspekten på serversidan lika enkel., Ur ett förenklat perspektiv är processen följande:
- skapa en
ServerSocket
, som anger en port att lyssna på. - anropa metoden
ServerSocket
’saccept()
för att lyssna på den konfigurerade porten för en klientanslutning. - när en klient ansluter till servern returnerar metoden
accept()
enSocket
genom vilken servern kan kommunicera med klienten., Detta är sammaSocket
klass som vi använde för vår klient, så processen är densamma: få enInputStream
att läsa från klienten och enOutputStream
skriva till klienten. - om servern behöver skalbar, kommer du att vilja skicka
Socket
till en annan tråd för att bearbeta så att din server kan fortsätta lyssna efter ytterligare anslutningar. - anropa
ServerSocket
’saccept()
– metoden igen för att lyssna efter en annan anslutning.,
som du snart kommer att se skulle NIO: s hantering av detta scenario vara lite annorlunda., För tillfället kan vi dock direkt skapa en ServerSocket
genom att skicka den till en port för att lyssna på (mer om ServerSocketFactory
s i nästa avsnitt):
ServerSocket serverSocket = new ServerSocket( port );
och nu kan vi acceptera inkommande anslutningar via metoden accept()
:
Socket socket = serverSocket.accept();// Handle the connection ...
flertrådig programmering med Java-uttag
lista 2, nedan, sätter all serverkod hittills tillsammans i ett något mer robust exempel som använder trådar för att hantera flera förfrågningar., Servern som visas är en echo-server, vilket innebär att den ekar tillbaka alla meddelanden som den tar emot.
medan exemplet i Listning 2 inte är komplicerat förutser det några av vad som kommer upp i nästa avsnitt om NIO. Var särskilt uppmärksam på hur mycket trådkod vi måste skriva för att bygga en server som kan hantera flera samtidiga förfrågningar.
Notering 2. SimpleSocketServer.java
i Lista 2 skapar vi en ny SimpleSocketServer
– instans och startar servern., Detta krävs eftersomSimpleSocketServer
utökarThread
för att skapa en ny tråd för att hantera blockeringenaccept()
samtal som du ser iread()
– metoden. Metoden run()
sitter i en slinga som accepterar klientbegäranden och skapar RequestHandler
– trådar för att behandla begäran. Återigen är detta relativt enkel kod, men innebär också en hel del gängad programmering.,
Observera också att RequestHandler
hanterar klientkommunikationen ungefär som koden i Lista 1 gjorde: det sveper OutputStream
med en PrintStream
för att underlätta enkla skrivningar och på samma sätt sveper InputStream
med en BufferedReader
för lätt läser. Så långt som en server går, läser den rader från klienten och ekar dem tillbaka till klienten. Om klienten skickar en tom linje är konversationen över och RequestHandler
stänger uttaget.
NIO och NIO.,2
För många applikationer är programmeringsmodellen för Java base socket som vi just har utforskat tillräcklig. För applikationer som involverar mer intensiv I/O eller asynkron ingång / utgång vill du vara bekant med de icke-blockerande API: er som introduceras i Java NIO och NIO.2.
JDK 1.4 NIO paketet erbjuder följande viktiga funktioner:
- kanaler är utformade för att stödja massöverföringar från en NIO buffert till en annan.
- buffertar representerar ett sammanhängande minnesblock som är sammankopplat med en enkel uppsättning operationer.,
- non-Blocking Input/Output är en uppsättning klasser som exponerar kanaler för vanliga i / O-källor som filer och uttag.
När du programmerar med NIO öppnar du en kanal till din destination och läser sedan data till en buffert från destinationen, skriver data till en buffert och skickar den till din destination., Vi dyker in i att skapa ett uttag och få en kanal till det inom kort, men låt oss först granska processen med att använda en buffert:
- skriv data till en buffert
- Ring buffertens
flip()
metod för att förbereda den för att läsa - Läs data från bufferten
- Ring buffertens
clear()
ellerclear()
metod för att förbereda den för att ta emot mer data
När data skrivs in i bufferten vet bufferten hur mycket data som skrivs in i den., Den upprätthåller tre egenskaper, vars betydelser skiljer sig åt om bufferten är i läsläge eller skrivläge:
- Position: i skrivläge är utgångsläget 0 och det håller den aktuella positionen som skrivs till i bufferten; när du vrider en buffert för att sätta den i läsläge återställer den positionen till 0 och håller den aktuella positionen i bufferten som läses från,
- kapacitet: buffertens fasta storlek
- gräns: i skrivläge definierar gränsen hur mycket data som kan skrivas in i bufferten; i skrivläge Läs Läge, gränsen definierar hur mycket data kan läsas från bufferten.,
Java i / o demo: Echo server med NIO.2
NIO.2, som introducerades i JDK 7, utökar Javas icke-blockerande I / O-bibliotek för att lägga till stöd för filsystemuppgifter, till exempeljava.nio.file
– paketet ochjava.nio.file.Path
– klassen och exponerar ett nytt filsystems API. Med den bakgrunden i åtanke, låt oss skriva en ny Echo-Server med NIO.2: AsynchronousServerSocketChannel
.
AsynchronousServerSocketChannel
tillhandahåller en icke-blockerande asynkron kanal för strömorienterade lyssningsuttag., För att kunna använda den utför vi först dess statiska open()
– metod och sedan bind()
den till en viss port. Därefter kör vi dessaccept()
– metod och skickar till en klass som implementerarCompletionHandler
– gränssnittet. Oftast hittar du den hanteraren skapad som en anonym inre klass.
notering 3 visar källkoden för vår nya asynkrona Echo-Server.
Lista 3. SimpleSocketServer.,i Lista 3 skapar vi först en nyAsynchronousServerSocketChannel
och binder sedan den till port 5000:
final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));
från dettaAsynchronousServerSocketChannel
anropar vi accept()
för att berätta för den att börja lyssna på anslutningar, och skickar till det en anpassad AsynchronousServerSocketChannel
. = ”351b770bbb” > instans. När vi anropar accept()
returnerar den omedelbart., Observera att det här exemplet skiljer sig frånServerSocket
– klassen i Lista 1; medanaccept()
– metoden blockeras tills en klient är ansluten till den, hanterarAsynchronousServerSocketChannel
accept()
den för oss.
kompletteringshanteraren
vårt nästa ansvar är att skapa enCompletionHandler
klass och tillhandahålla en implementering avcompleted()
ochfailed()
metoder., Metodencompleted()
anropas närAsynchronousServerSocketChannel
tar emot en anslutning från en klient och innehåller enAsynchronousSocketChannel
till klienten. Metodencompleted()
accepterar först anslutningen från AsynchronousServerSocketChannel
och börjar sedan kommunicera med klienten. Det första som det gör är att skriva ut ett” Hej ” – meddelande: Det bygger en sträng, konverterar den till en byte-array och skickar den sedan till ByteBuffer.wrap()
för att konstruera en ByteBuffer
., MetodenByteBuffer
kan sedan skickasAsynchronousSocketChannel
’swrite()
.
för att läsa från klienten skapar vi en nyByteBuffer
genom att åberopa dessallocate(4096)
(vilket skapar en 4K-buffert), då åberopar viAsynchronousSocketChannel
’sread()
– metoden. read()
returnerar ettFuture<Integer>
där vi kan anropaget()
för att hämta antalet byte som läses från klienten., I det här exemplet passerar vi get()
ett tidsgräns på 20 sekunder: om vi inte får ett svar på 20 sekunder kommer get()
– metoden att kasta en TimeoutException
. Vår regel för denna echo-server är att om vi observerar 20 sekunder av tystnad så avslutar vi samtalet.
därefter kontrollerar vi buffertens position, vilket kommer att vara platsen för den sista byte som tas emot från klienten. Om klienten skickar en tom rad får vi två byte: en vagnretur och en radmatning., Kontrollen säkerställer att om kunden skickar en tom linje som vi tar det som en indikator på att klienten är klar med konversationen. Om vi har meningsfulla data kallar viByteBuffer
’sflip()
– metoden för att förbereda den för läsning. Vi skapar en tillfällig byte-array för att hålla antalet byte som läses från klienten och sedan åberopaByteBuffer
’sget()
för att ladda data i den byte-arrayen. Slutligen konverterar vi byte-matrisen till en sträng genom att skapa en ny String
– instans., Vi echo linjen tillbaka till klienten genom att konvertera strängen till en byte array, passerar det till ByteBuffer.wrap()
metod och åberopar AsynchronousSocketChannel
’s write()
metod. Nu clear()
ByteBuffer
, vilket minns innebär att det repositions position
till noll och sätter ByteBuffer
I skrivläge, och sedan läser vi nästa rad från klienten.,
det enda du behöver vara medveten om är attmain()
– metoden, som skapar servern, också ställer in en 60 sekunders timer för att hålla programmet igång. EftersomAsynchronousSocketChannel
’saccept()
– metoden returnerar omedelbart, om vi inte har Thread.sleep()
kommer vår ansökan att sluta omedelbart.,
för att testa detta, starta servern och anslut den med en telnet-klient:
telnet localhost 5000
skicka några strängar till servern, Observera att de är echoed tillbaka till dig och skicka sedan en tom linje för att avsluta konversationen.
Sammanfattningsvis
i den här artikeln har jag presenterat två metoder för socket programmering med Java: den traditionella metoden infördes med Java 1.0 och den nyare, icke-blockerande NIO och NIO.2 tillvägagångssätt infördes i Java 1.4 och Java 7, respektive., Du har sett flera iterationer av en Java socket-klient och ett Java socket server-exempel, vilket visar både nyttan av grundläggande Java i/O och vissa scenarier där icke-blockerande I/O förbättrar programmeringsmodellen Java socket. Med icke-blockerande I / O kan du programmera Java-nätverksapplikationer för att hantera flera samtidiga anslutningar utan att behöva hantera flera trådsamlingar. Du kan också dra nytta av den nya server skalbarhet som är inbyggd i NIO och NIO.2.