Socket-Programmierung in Java: Ein Tutorial

Dieses Tutorial ist eine Einführung in die Socket-Programmierung in Java und beginnt mit einem einfachen Client-Server-Beispiel, das die grundlegenden Funktionen von Java I / O demonstriert. Sie werden sowohl mit dem Originalpaket  java.io als auch mit NIO vertraut gemacht, den nicht blockierenden I / O ( java.nio) - APIs, die in Java 1.4 eingeführt wurden. Schließlich sehen Sie ein Beispiel, das die Java-Vernetzung demonstriert, wie sie ab Java 7 in NIO.2 implementiert ist.

Die Socket-Programmierung besteht aus zwei miteinander kommunizierenden Systemen. Im Allgemeinen gibt es zwei Arten der Netzwerkkommunikation: TCP (Transport Control Protocol) und UDP (User Datagram Protocol). TCP und UDP werden für unterschiedliche Zwecke verwendet und haben beide eindeutige Einschränkungen:

  • TCP ist ein relativ einfaches und zuverlässiges Protokoll, mit dem ein Client eine Verbindung zu einem Server herstellen und die beiden Systeme kommunizieren können. In TCP weiß jede Entität, dass ihre Kommunikationsnutzdaten empfangen wurden.
  • UDP ist ein verbindungsloses Protokoll und eignet sich für Szenarien, in denen nicht unbedingt jedes Paket am Ziel ankommen muss, z. B. Medien-Streaming.

Um den Unterschied zwischen TCP und UDP zu erkennen, sollten Sie überlegen, was passieren würde, wenn Sie Videos von Ihrer Lieblingswebsite streamen und Frames löschen würden. Möchten Sie, dass der Client Ihren Film verlangsamt, um die fehlenden Bilder zu empfangen, oder möchten Sie, dass das Video weiter abgespielt wird? Video-Streaming-Protokolle nutzen normalerweise UDP. Da TCP die Zustellung garantiert, ist es das Protokoll der Wahl für HTTP, FTP, SMTP, POP3 usw.

In diesem Tutorial stelle ich Ihnen die Socket-Programmierung in Java vor. Ich präsentiere eine Reihe von Client-Server-Beispielen, die Funktionen des ursprünglichen Java-E / A-Frameworks demonstrieren, und gehe dann schrittweise zur Verwendung der in NIO.2 eingeführten Funktionen über.

Java-Sockets der alten Schule

In Implementierungen vor NIO wird Java TCP-Client-Socket-Code von der java.net.SocketKlasse verarbeitet. Der folgende Code öffnet eine Verbindung zu einem Server:

 Socket Socket = neuer Socket (Server, Port); 

Sobald unsere socketInstanz mit dem Server verbunden ist, können wir Eingabe- und Ausgabestreams an den Server abrufen. Eingabestreams werden zum Lesen von Daten vom Server verwendet, während Ausgabestreams zum Schreiben von Daten auf den Server verwendet werden. Wir können die folgenden Methoden ausführen, um Eingabe- und Ausgabestreams zu erhalten:

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

Da es sich um normale Streams handelt, dieselben Streams, die wir zum Lesen und Schreiben in eine Datei verwenden würden, können wir sie in die Form konvertieren, die unserem Anwendungsfall am besten entspricht. Zum Beispiel könnten wir das OutputStreammit einem PrintStreamumschließen, damit wir einfach Text mit Methoden wie schreiben können println(). Für ein anderes Beispiel könnten wir das InputStreammit einem BufferedReaderüber ein InputStreamReaderumbrechen, um Text mit Methoden wie einfach zu lesen readLine().

download Laden Sie den Quellcode herunter Quellcode für "Socket-Programmierung in Java: Ein Tutorial". Erstellt von Steven Haines für JavaWorld.

Beispiel für einen Java-Socket-Client

Lassen Sie uns ein kurzes Beispiel durcharbeiten, das ein HTTP-GET für einen HTTP-Server ausführt. HTTP ist komplexer als es unser Beispiel zulässt, aber wir können Clientcode schreiben, um den einfachsten Fall zu behandeln: Fordern Sie eine Ressource vom Server an, und der Server gibt die Antwort zurück und schließt den Stream. Dieser Fall erfordert die folgenden Schritte:

  1. Erstellen Sie einen Socket für den Webserver, der Port 80 überwacht.
  2. Beziehen Sie eine PrintStreaman den Server und senden Sie die Anfrage GET PATH HTTP/1.0, wo PATHsich die angeforderte Ressource auf dem Server befindet. Wenn wir beispielsweise das Stammverzeichnis einer Website öffnen möchten, lautet der Pfad /.
  3. Beziehen Sie eine InputStreaman den Server, wickeln Sie sie mit a ein BufferedReaderund lesen Sie die Antwort Zeile für Zeile.

Listing 1 zeigt den Quellcode für dieses Beispiel.

Listing 1. SimpleSocketClientExample.java

Paket com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; öffentliche Klasse SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Verwendung: SimpleSocketClientExample"); System.exit (0); } String server = args [0]; String path = args [1]; System.out.println ("Inhalt der URL wird geladen:" + Server); try {// Verbindung zum Server herstellen Socket socket = new Socket (Server, 80); // Eingabe- und Ausgabestreams zum Lesen und Schreiben auf den Server erstellen PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = neuer BufferedReader (neuer InputStreamReader (socket.getInputStream ())); // Folgen Sie dem HTTP-Protokoll von GET HTTP / 1.0 gefolgt von einer leeren Zeile out.println ("GET" + Pfad + "HTTP / 1.0"); out.println (); // Daten vom Server lesen, bis wir das Dokument gelesen haben String line = in.readLine (); while (Zeile! = null) {System.out.println (Zeile); line = in.readLine (); } // Schließe unsere Streams in.close (); out.close (); socket.close (); } catch (Ausnahme e) {e.printStackTrace (); }}}

Listing 1 akzeptiert zwei Befehlszeilenargumente: den Server, zu dem eine Verbindung hergestellt werden soll (vorausgesetzt, wir stellen über Port 80 eine Verbindung zum Server her), und die Ressource, die abgerufen werden soll. Es wird ein Verzeichnis erstellt Socket, das auf den Server verweist und den Port explizit angibt 80. Anschließend wird der folgende Befehl ausgeführt:

GET PATH HTTP / 1.0 

Zum Beispiel:

GET / HTTP / 1.0 

Was ist gerade passiert?

Wenn Sie eine Webseite von einem Webserver abrufen, z. B. verwendet www.google.comder HTTP-Client DNS-Server, um die Serveradresse zu ermitteln: Zunächst wird der Domänenserver der obersten Ebene nach der comDomäne gefragt, für die sich der autorisierende Domänennamenserver befindet www.google.com. Dann fragt es diesen Domain-Name-Server nach der IP-Adresse (oder den Adressen) für www.google.com. Als Nächstes wird ein Socket für diesen Server an Port 80 geöffnet. (Wenn Sie einen anderen Port definieren möchten, können Sie dazu einen Doppelpunkt gefolgt von der Portnummer hinzufügen, z. B. :8080:) Schließlich wird der HTTP-Client ausgeführt die angegebene HTTP - Methode, wie zum Beispiel GET, POST, PUT, DELETE, HEAD, oder OPTI/ONS. Jede Methode hat ihre eigene Syntax. Wie in den obigen Codeausschnitten gezeigt, GETerfordert die Methode einen Pfad, dem gefolgt wirdHTTP/version numberund eine leere Zeile. Wenn wir HTTP-Header hinzufügen wollten, hätten wir dies tun können, bevor wir die neue Zeile eingegeben haben.

In Listing 1 haben wir eine abgerufen OutputStreamund in eine verpackt, PrintStreamdamit wir unsere textbasierten Befehle einfacher ausführen können. Unser Code erhielt ein InputStream, wickelte das in ein InputStreamReader, konvertierte es in ein Reader, und wickelte das dann in ein BufferedReader. Wir haben das verwendet PrintStream, um unsere GETMethode auszuführen , und dann das BufferedReader, um die Antwort Zeile für Zeile zu lesen, bis wir eine nullAntwort erhalten haben, die angibt, dass der Socket geschlossen wurde.

Führen Sie nun diese Klasse aus und übergeben Sie ihr die folgenden Argumente:

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

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

Laden des Inhalts der URL: www.javaworld.com HTTP / 1.1 200 OK Datum: So, 21. September 2014, 22:20:13 GMT Server: Apache X-Gas_TTL: 10 Cache-Kontrolle: maximales Alter = 10 X-GasHost: gas2 .usw X-Cooking-With: Benzin-Lokal X-Benzin-Alter: 8 Inhaltslänge: 168 Zuletzt geändert: Di, 24. Januar 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Inhaltstyp : text / html Variieren: Accept-Encoding Connection: Benzintestseite schließen

Erfolg

Diese Ausgabe zeigt eine Testseite auf der JavaWorld-Website. Es antwortete zurück, dass es HTTP Version 1.1 spricht und die Antwort ist 200 OK.

Beispiel für einen Java-Socket-Server

Wir haben die Client-Seite behandelt und glücklicherweise ist der Kommunikationsaspekt der Server-Seite genauso einfach. Aus einer vereinfachenden Perspektive ist der Prozess wie folgt:

  1. Erstellen Sie einen ServerSocketPort und geben Sie einen Port an, den Sie abhören möchten.
  2. Rufen Sie die Methode ServerSocket's accept()auf, um den konfigurierten Port auf eine Clientverbindung zu überwachen.
  3. Wenn ein Client eine Verbindung zum Server herstellt, gibt die accept()Methode eine zurück, Socketüber die der Server mit dem Client kommunizieren kann. Dies ist dieselbe SocketKlasse, die wir für unseren Client verwendet haben, daher ist der Prozess der gleiche: Erhalten Sie eine InputStreamvom Client zu lesende und eine OutputStreamauf den Client zu schreibende.
  4. Wenn Ihr Server skalierbar sein muss, möchten Sie ihn Socketzur Verarbeitung an einen anderen Thread übergeben, damit Ihr Server weiterhin auf zusätzliche Verbindungen warten kann.
  5. Rufen Sie die ServerSockets‘ accept()Methode erneut für eine andere Verbindung zu hören.

Wie Sie gleich sehen werden, wäre die Behandlung dieses Szenarios durch NIO etwas anders. Im Moment können wir jedoch direkt ServerSocketeinen Port erstellen, indem wir ihm einen Port zum Abhören übergeben (mehr über ServerSocketFactorys im nächsten Abschnitt):

 ServerSocket serverSocket = neuer ServerSocket (Port); 

Und jetzt können wir eingehende Verbindungen über die accept()Methode akzeptieren :

Socket socket = serverSocket.accept (); // Verbindung herstellen ...

Multithread-Programmierung mit Java-Sockets

In Listing 2 unten wird der gesamte bisherige Servercode zu einem etwas robusteren Beispiel zusammengefasst, bei dem Threads zur Verarbeitung mehrerer Anforderungen verwendet werden. Der angezeigte Server ist ein Echoserver , dh er gibt alle empfangenen Nachrichten zurück.

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(); } } }