Socket-programmering i Java: En handledning

Denna handledning är en introduktion till sockelprogrammering i Java, som börjar med ett enkelt klientserverexempel som visar de grundläggande funktionerna i Java I / O. Du kommer att presenteras för både originalpaketet  java.io och NIO, de icke-blockerande I / O ( java.nio) API: erna som introducerades 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 beror på att två system kommunicerar med varandra. Generellt finns 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 att kommunicera. I TCP vet varje enhet att dess kommunikationsnyttolast har mottagits.
  • UDP är ett anslutningsfritt 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, t.ex. mediastreaming.

För att uppskatta skillnaden mellan TCP och UDP, överväg vad som skulle hända om du strömmade video från din favoritwebbplats och den tappade ramar. Föredrar du att klienten saktar ner din film för att ta emot de saknade bildrutorna eller föredrar du att videon fortsätter att spelas? Protokoll för videostreaming utnyttjar vanligtvis UDP. Eftersom TCP garanterar leverans är det det valbara protokollet för HTTP, FTP, SMTP, POP3 och så vidare.

I den här guiden presenterar jag dig för sockelprogrammering i Java. Jag presenterar en serie klientserverexempel som visar funktioner från det ursprungliga Java I / O-ramverket och sedan gradvis går vidare till att använda funktioner som introducerats i NIO.2.

Old-school Java-uttag

I implementeringar före NIO hanteras Java TCP-klientuttagskod av java.net.Socketklassen. Följande kod öppnar en anslutning till en server:

 Sockeluttag = nytt uttag (server, port); 

När vår socketinstans är ansluten till servern kan vi börja få in- och utmatningsströmmar till servern. Ingångsströmmar används för att läsa data från servern medan utgångsströmmar används för att skriva data till servern. Vi kan utföra följande metoder för att få in- och utmatningsströmmar:

InputStream in = socket.getInputStream (); OutputStream out = socket.getOutputStream ();

Eftersom detta ä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 den form som bäst tjänar vårt användningsfall. Vi kan till exempel slå in den OutputStreammed en PrintStreamså att vi enkelt kan skriva text med metoder som println(). För ett annat exempel kan vi slå in den InputStreammed en BufferedReader, via en InputStreamReader, för att enkelt läsa text med metoder som readLine().

ladda ner Ladda ner källkoden Källkod för "Socket-programmering i Java: En handledning." Skapad av Steven Haines för JavaWorld.

Exempel på Java-sockelklient

Låt oss arbeta igenom ett kort exempel som kör en HTTP GET mot en HTTP-server. HTTP är mer sofistikerad än vad vårt exempel tillåter, men vi kan skriva klientkod för att hantera det enklaste fallet: begär en resurs från servern och servern returnerar svaret och stänger strömmen. I det här fallet krävs följande steg:

  1. Skapa ett uttag till webbservern som lyssnar på port 80.
  2. Skaffa a PrintStreamtill servern och skicka begäran GET PATH HTTP/1.0, var PATHär den begärda resursen på servern. Till exempel, om vi ville öppna roten till en webbplats så skulle sökvägen vara /.
  3. Skaffa en InputStreamtill servern, linda den med a BufferedReaderoch läs svaret rad för rad.

Listning 1 visar källkoden för detta exempel.

Listing 1. SimpleSocketClientExample.java

paketet com.geekcap.javaworld.simplesocketclient; importera java.io.BufferedReader; importera java.io.InputStreamReader; importera java.io.PrintStream; importera java.net.Socket; public class SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } Strängserver = args [0]; Strängbana = args [1]; System.out.println ("Läser in innehåll i URL:" + server); prova {// Anslut till servern Socket socket = new Socket (server, 80); // Skapa in- och utmatningsströmmar för att läsa från och skriva till servern PrintStream ut = ny PrintStream (socket.getOutputStream ()); BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); // Följ HTTP-protokollet för GET HTTP / 1.0 följt av en tom rad ut.println ("GET" + sökväg + "HTTP / 1.0"); out.println (); // Läs data från servern tills vi är klara med att läsa dokumentet String line = in.readLine (); while (line! = null) {System.out.println (line); line = in.readLine (); } // Stäng våra strömmar in.close (); out.close (); socket.close (); } fånga (Undantag e) {e.printStackTrace (); }}}

Listning 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 en Socketsom pekar på servern och specifikt specificerar port 80. Det kör sedan kommandot:

GET PATH HTTP / 1.0 

Till exempel:

GET / HTTP / 1.0 

Vad hände nyss?

När du hämtar en webbsida från en webbserver, till exempel www.google.com, använder HTTP-klienten DNS-servrar för att hitta serverns adress: det börjar med att fråga toppdomänservern för den comdomän där den auktoritativa domännamnsservern är för www.google.com. Sedan frågar den att domännamnsservern för IP-adressen (eller adresserna) för www.google.com. Därefter öppnas 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 kör HTTP-klienten den specificerade HTTP metod, såsom GET, POST, PUT, DELETE, HEAD, eller OPTI/ONS. Varje metod har sin egen syntax. Som visas i ovanstående kodklipp GETkräver metoden en sökväg följt avHTTP/version numberoch en tom rad. Om vi ​​ville lägga till HTTP-rubriker kunde vi ha gjort det innan vi gick in i den nya raden.

I listan 1 hämtade vi en OutputStreamoch slog in den i en PrintStreamså att vi lättare kunde utföra våra textbaserade kommandon. Vår kod erhöll en InputStream, insvept den i en InputStreamReader, som konverterade den till a Readeroch sedan förpackade den i en BufferedReader. Vi använde den för PrintStreamatt utföra vår GETmetod och använde sedan för BufferedReaderatt läsa svaret rad för rad tills vi fick nullsvar, vilket indikerar att uttaget hade stängts.

Kör nu den här klassen och skicka följande argument:

java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExempel www.javaworld.com / 

Du bör se utdata som liknar vad som är nedan:

Laddar innehåll på URL: www.javaworld.com HTTP / 1.1 200 OK Datum: Sun, 21 Sep 2014 22:20:13 GMT Server: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Bensin-Local X-Bensin-ålder: 8 Innehållslängd: 168 Senast ändrad: Tis, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Innehållstyp : text / html Variera: Accept-kodningsanslutning: stäng Bensintestsida

Framgång

Denna utdata visar en testsida på JavaWorlds webbplats. Det svarade tillbaka att det talar HTTP version 1.1 och svaret är 200 OK.

Exempel på Java-sockelserver

Vi har täckt klientsidan och lyckligtvis är kommunikationsaspekten på serversidan lika enkel. Ur ett förenklat perspektiv är processen följande:

  1. Skapa en ServerSocket, ange en port att lyssna på.
  2. Invoke den ServerSockets accept()metod för att lyssna på den konfigurerade port för en klientanslutning.
  3. När en klient ansluter till servern accept()returnerar metoden en Socketgenom vilken servern kan kommunicera med klienten. Det här är samma Socketklass som vi använde för vår klient, så processen är densamma: skaffa en InputStreamatt läsa från klienten och OutputStreamskriva till klienten.
  4. Om din server behöver vara skalbar, vill du skicka den Sockettill en annan tråd för att bearbeta så att din server kan fortsätta lyssna efter ytterligare anslutningar.
  5. Ring ServerSocket's accept()metod igen för att lyssna efter ett annat sammanhang.

As you'll soon see, NIO's handling of this scenario would be a bit different. For now, though, we can directly create a ServerSocket by passing it a port to listen on (more about ServerSocketFactorys in the next section):

 ServerSocket serverSocket = new ServerSocket( port ); 

And now we can accept incoming connections via the accept() method:

 Socket socket = serverSocket.accept(); // Handle the connection ... 

Multithreaded programming with Java sockets

Listing 2, below, puts all of the server code so far together into a slightly more robust example that uses threads to handle multiple requests. The server shown is an echo server, meaning that it echoes back any message it receives.

While the example in Listing 2 isn't complicated it does anticipate some of what's coming up in the next section on NIO. Pay special attention to the amount of threading code we have to write in order to build a server that can handle multiple simultaneous requests.

Listing 2. SimpleSocketServer.java

package com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.I/OException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SimpleSocketServer extends Thread { private ServerSocket serverSocket; private int port; private boolean running = false; public SimpleSocketServer( int port ) { this.port = port; } public void startServer() { try { serverSocket = new ServerSocket( port ); this.start(); } catch (I/OException e) { e.printStackTrace(); } } public void stopServer() { running = false; this.interrupt(); } @Override public void run() { running = true; while( running ) { try { System.out.println( "Listening for a connection" ); // Call accept() to receive the next connection Socket socket = serverSocket.accept(); // Pass the socket to the RequestHandler thread for processing RequestHandler requestHandler = new RequestHandler( socket ); requestHandler.start(); } catch (I/OException e) { e.printStackTrace(); } } } public static void main( String[] args ) { if( args.length == 0 ) { System.out.println( "Usage: SimpleSocketServer " ); System.exit( 0 ); } int port = Integer.parseInt( args[ 0 ] ); System.out.println( "Start server on port: " + port ); SimpleSocketServer server = new SimpleSocketServer( port ); server.startServer(); // Automatically shutdown in 1 minute try { Thread.sleep( 60000 ); } catch( Exception e ) { e.printStackTrace(); } server.stopServer(); } } class RequestHandler extends Thread { private Socket socket; RequestHandler( Socket socket ) { this.socket = socket; } @Override public void run() { try { System.out.println( "Received a connection" ); // Get input and output streams BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); PrintWriter out = new PrintWriter( socket.getOutputStream() ); // Write out our header to the client out.println( "Echo Server 1.0" ); out.flush(); // Echo lines back to the client until the client closes the connection or we receive an empty line String line = in.readLine(); while( line != null && line.length() > 0 ) { out.println( "Echo: " + line ); out.flush(); line = in.readLine(); } // Close our connection in.close(); out.close(); socket.close(); System.out.println( "Connection closed" ); } catch( Exception e ) { e.printStackTrace(); } } }