Socket-Programmierung in Java: Ein Tutorial

Dieses Tutorial ist eine Einführung in die Socket-Programmierung in Java, beginnend mit einem einfachen Client-Server-Beispiel, das die grundlegenden Funktionen von Java I/O demonstriert Sie werden sowohl in das ursprüngliche java.io – Paket als auch in NIO eingeführt, die nicht blockierenden E/A-APIs (java.nio), die in Java 1.4 eingeführt wurden. Schließlich sehen Sie ein Beispiel, das Java Networking demonstriert, wie es von Java 7 forward in NIO implementiert wurde.2.,

Die Socket-Programmierung läuft darauf hinaus, dass zwei Systeme miteinander kommunizieren. Im Allgemeinen gibt es die Netzwerkkommunikation in zwei Varianten: Transport Control Protocol (TCP) und User Datagram Protocol (UDP). TCP und UDP werden für verschiedene Zwecke verwendet und beide haben 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 kann. In TCP weiß jede Entität, dass ihre Kommunikationsnutzlasten empfangen wurden.,
  • UDP ist ein verbindungsloses Protokoll und eignet sich für Szenarien, in denen Sie nicht unbedingt jedes Paket benötigen, um am Ziel anzukommen, z. B. Medienstreaming.

Um den Unterschied zwischen TCP und UDP zu erkennen, überlegen Sie, was passieren würde, wenn Sie Videos von Ihrer bevorzugten Website streamen und Frames löschen würden. Möchten Sie es vorziehen, dass der Client Ihren Film verlangsamt, um die fehlenden Frames zu erhalten, 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 aus dem ursprünglichen Java-E/A-Framework demonstrieren und dann schrittweise zur Verwendung der in NIO eingeführten Funktionen übergehen.2.

Java-Sockets der alten Schule

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

Socket socket = new Socket( server, port );

Sobald unsere socket – Instanz mit dem Server verbunden ist, können wir mit dem Abrufen von Ein-und Ausgabeströmen an den Server beginnen. Eingabeströme werden zum Lesen von Daten vom Server verwendet, während Ausgabeströme zum Schreiben von Daten auf den Server verwendet werden., Wir können die folgenden Methoden ausführen, um Eingabe-und Ausgabeströme zu erhalten:

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

Da es sich um normale Streams handelt, dieselben Streams, aus denen wir lesen und schreiben würden In eine Datei können wir sie in die Form konvertieren, die unserem Anwendungsfall am besten entspricht. Zum Beispiel könnten wir die OutputStream mit einer PrintStream, so dass wir leicht Text mit Methoden wie println()., In einem anderen Beispiel könnten wir die InputStream mit einer BufferedReader über eine InputStreamReader, um Text mit Methoden wie readLine()einfach zu lesen.

download

Source-code für die „Socket-Programmierung in Java: A tutorial.“Erstellt von Steven Haines für JavaWorld.

Java socket client example

Lassen Sie uns ein kurzes Beispiel durcharbeiten, das einen HTTP GET gegen einen HTTP Server ausführt., HTTP ist anspruchsvoller als unser Beispiel zulässt, aber wir können Client-Code 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 abhört.
  2. Erhalte eine PrintStream an den Server und sende die Anfrage GET PATH HTTP/1.0, wobei PATH die angeforderte Ressource auf dem Server ist., Wenn wir beispielsweise das Stammverzeichnis einer Website öffnen möchten, lautet der Pfad /.
  3. Rufen Sie eine InputStream auf den Server ab, wickeln Sie sie mit einer BufferedReader ein und lesen Sie die Antwort zeilenweise.

Listing 1 zeigt den Quellcode für dieses Beispiel.

Auflistung 1. SimpleSocketClientExample.java

Listing 1 akzeptiert zwei Befehlszeilenargumente: den Server, mit dem eine Verbindung hergestellt werden soll (vorausgesetzt, wir stellen eine Verbindung zum Server an Port 80 her) und die abzurufende Ressource., Es wird eine Socket erstellt, die auf den Server verweist und explizit port 80angibt. Es führt dann den Befehl aus:

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 der HTTP-Client DNS-Server, um die Adresse des Servers zu ermitteln: Zunächst wird der Domänenserver der obersten Ebene nach der com – Domäne gefragt, in der sich der autorisierende Domänennamenserver für die ., Dann fragt es diesen Domain-Name-Server nach der IP-Adresse (oder Adressen) für . Als nächstes öffnet es einen Socket für diesen Server an Port 80. (Oder, wenn Sie einen anderen Port definieren möchten, können Sie dies tun, indem Sie einen Doppelpunkt gefolgt von der Portnummer hinzufügen, zum Beispiel: :8080.) Schließlich führt der HTTP-Client die angegebene HTTP-Methode aus, z. B. GET, POST, PUT, DELETE, HEAD oder OPTI/ONS. Jede Methode hat Ihre eigene syntax., Wie in den obigen Code-Snips gezeigt, erfordert die Methode GET einen Pfad gefolgt von HTTP/version number und einer leeren Zeile. Wenn wir HTTP-Header hinzufügen wollten, hätten wir dies vor dem Betreten der neuen Zeile tun können.

In Listing 1 haben wir eine OutputStream abgerufen und in eine PrintStream, damit wir unsere textbasierten Befehle einfacher ausführen können., Unser Code erhielt eine InputStream, die in eine InputStreamReader, die sie in eine Reader und dann in eine BufferedReader. Wir haben die PrintStream verwendet, um unsere GET-Methode auszuführen, und dann die BufferedReader verwendet, um die Antwort Zeile für Zeile zu lesen, bis wir eine null-Antwort erhalten haben, die anzeigt, dass der Socket geschlossen wurde.,

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

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

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

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

Java socket server example

Wir haben die Clientseite abgedeckt und zum Glück ist der Kommunikationsaspekt der Serverseite genauso einfach., Aus einer vereinfachten Perspektive ist der Prozess wie folgt:

  1. Erstellen Sie eine ServerSocket und geben Sie einen Port zum Abhören an.
  2. Rufen Sie die ServerSocket – Methode accept() auf, um den konfigurierten Port für eine Clientverbindung abzuhören.
  3. Wenn ein Client eine Verbindung zum Server herstellt, gibt die accept() – Methode eine Socket zurück, über die der Server mit dem Client kommunizieren kann., Dies ist dieselbeSocket – Klasse, die wir für unseren Client verwendet haben, daher ist der Prozess derselbe: Erhalten Sie eine InputStream zum Lesen vom Client und eine OutputStream Schreiben Sie an den Client.
  4. Wenn Ihr Server skalierbar sein muss, möchten Sie die Socket an einen anderen zu verarbeitenden Thread übergeben, damit Ihr Server weiterhin auf zusätzliche Verbindungen warten kann.
  5. Rufen Sie die ServerSocket – Methode accept() erneut auf, um auf eine andere Verbindung zu warten.,

Wie Sie bald sehen werden, wäre NIOS Umgang mit diesem Szenario etwas anders., Im Moment können wir jedoch direkt eine ServerSocket erstellen, indem wir einen Port zum Abhören übergeben (mehr über ServerSocketFactorys im nächsten Abschnitt):

ServerSocket serverSocket = new ServerSocket( port );

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

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

Multithread-Programmierung mit Java-Sockets

Liste 2, unten, setzt den gesamten Servercode so weit zusammen in ein etwas robusteres Beispiel, das Threads verwendet, um mehrere Anfragen zu behandeln., Der angezeigte Server ist ein Echo-Server, dh er gibt jede empfangene Nachricht zurück.

Während das Beispiel in Listing 2 nicht kompliziert ist, erwartet es etwas von dem, was im nächsten Abschnitt über NIO kommt. Achten Sie besonders auf die Menge an Threading-Code, den wir schreiben müssen, um einen Server zu erstellen, der mehrere gleichzeitige Anforderungen verarbeiten kann.

Auflistung 2. SimpleSocketServer.java

In Listing 2 erstellen wir eine neueSimpleSocketServer Instanz und starten den Server., Dies ist erforderlich, da die SimpleSocketServer Thread erweitert, um einen neuen Thread zu erstellen, der die Blockierung behandelt accept() Aufruf, den Sie in der read() – Methode sehen. Dierun() – Methode befindet sich in einer Schleife, die Clientanforderungen akzeptiert undRequestHandler – Threads erstellt, um die Anforderung zu verarbeiten. Auch dies ist relativ einfacher Code, beinhaltet aber auch eine Menge Thread-Programmierung.,

Beachten Sie auch, dass die RequestHandler die Clientkommunikation ähnlich behandelt wie der Code in Listing 1: Er umschließt die OutputStream mit einer PrintStream um einfaches Schreiben zu erleichtern und umschließt in ähnlicher Weise die InputStream mit einer BufferedReader für einfache liest. Soweit ein Server geht, liest er Zeilen vom Client und gibt sie an den Client zurück. Wenn der Client eine leere Zeile sendet, ist die Konversation beendet und die RequestHandler schließt den Socket.

NIO und NIO.,2

Für viele Anwendungen reicht das Socket-Basisprogrammiermodell Java aus, das wir gerade untersucht haben. Für Anwendungen mit intensiverer E/A oder asynchroner Eingabe / Ausgabe möchten Sie mit den nicht blockierenden APIs vertraut sein, die in Java NIO und NIO eingeführt wurden.2.

Das JDK 1.4 NIO-Paket bietet die folgenden Hauptfunktionen:

  • – Kanäle unterstützen Massenübertragungen von einem NIO-Puffer zu einem anderen.
  • Puffer stellen einen zusammenhängenden Speicherblock dar, der durch einen einfachen Satz von Operationen verbunden ist.,
  • Nicht blockierende Eingabe/Ausgabe ist eine Reihe von Klassen, die Kanäle für gängige E / A-Quellen wie Dateien und Sockets verfügbar machen.

Beim Programmieren mit NIO öffnen Sie einen Kanal zu Ihrem Ziel und lesen dann Daten aus dem Ziel in einen Puffer, schreiben die Daten in einen Puffer und senden diese an Ihr Ziel., Wir werden uns in Kürze mit dem Einrichten eines Sockets und dem Abrufen eines Kanals befassen, aber lassen Sie uns zunächst den Vorgang der Verwendung eines Puffers überprüfen:

  1. Schreiben Sie Daten in einen Puffer
  2. Rufen Sie die flip() – Methode des Puffers auf, um sie für das Lesen vorzubereiten
  3. Lesen Sie Daten aus dem Puffer
  4. Rufen Sie die clear()Methode, um es vorzubereiten, um mehr Daten zu empfangen

Wenn Daten in den Puffer geschrieben werden, kennt der Puffer die Menge der darin geschriebenen Daten., Es behält drei Eigenschaften bei, deren Bedeutungen sich unterscheiden, wenn sich der Puffer im Lesemodus oder Schreibmodus befindet:

  • Position: Im Schreibmodus ist die Ausgangsposition 0 und es hält die aktuelle Position, in die geschrieben wird in den Puffer; Nachdem Sie einen Puffer umgedreht haben, um ihn in den Lesemodus zu versetzen, setzt es die Position auf 0 zurück und hält die aktuelle Position im Puffer, aus dem gelesen wird,
  • Kapazität: Die feste Größe des Puffers
  • Limit: Im Schreibmodus definiert das Limit, wie viele Daten in den Puffer geschrieben werden können; in lesemodus, das Limit definiert, wie viele Daten aus dem Puffer gelesen werden können.,

Java I/O demo: Echo-server mit NIO.2

– NIO.2, das in JDK 7 eingeführt wurde, erweitert die nicht blockierenden E/A-Bibliotheken von Java, um Unterstützung für Dateisystemaufgaben hinzuzufügen, z. B. das Paket java.nio.file und java.nio.file.Path und macht eine neue Dateisystem-API verfügbar. Vor diesem Hintergrund schreiben wir mit NIO einen neuen Echo-Server.2 AsynchronousServerSocketChannel.

Die AsynchronousServerSocketChannel stellt einen nicht blockierenden asynchronen Kanal für streamorientierte Listening-Sockets bereit., Um es zu verwenden, führen wir zuerst seine statische open() – Methode und dann bind() zu einem bestimmten Port aus. Als nächstes führen wir die accept() – Methode aus und übergeben eine Klasse, die die CompletionHandler – Schnittstelle implementiert. Meistens finden Sie diesen Handler, der als anonyme innere Klasse erstellt wurde.

Listing 3 zeigt den Quellcode für unseren neuen asynchronen Echo Server.

Auflistung 3. SimpleSocketServer.,java

In Listing 3 erstellen wir zuerst eine neue AsynchronousServerSocketChannel und binden sie dann an Port 5000:

 final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));

Von dieser AsynchronousServerSocketChannel rufen wir accept() auf, um ihm mitzuteilen, dass er Verbindungen abhören soll, und übergeben ihm eine benutzerdefinierte CompletionHandler Instanz. Wenn wir accept(), wird sofort zurückgegeben., Beachten Sie, dass sich dieses Beispiel von der ServerSocket – Klasse in Listing 1 unterscheidet.während die accept() – Methode blockiert, bis ein Client eine Verbindung herstellt, behandelt die AsynchronousServerSocketChannel accept() – Methode sie für uns.

Der Completion-Handler

Unsere nächste Aufgabe besteht darin, eine CompletionHandler – Klasse zu erstellen und eine Implementierung der completed() und failed() – Methoden bereitzustellen., Diecompleted() – Methode wird aufgerufen, wenn dieAsynchronousServerSocketChannel eine Verbindung von einem Client empfängt und eineAsynchronousSocketChannel zum Client enthält. Diecompleted() – Methode akzeptiert zuerst die Verbindung von derAsynchronousServerSocketChannel und beginnt dann mit der Kommunikation mit dem Client. Das erste, was es tut, ist, eine „Hallo“ – Nachricht auszuschreiben: Es erstellt eine Zeichenfolge, konvertiert sie in ein Byte-Array und übergibt sie dann an ByteBuffer.wrap(), um eine ByteBuffer., ByteBuffer können übergeben werden: AsynchronousSocketChannel’s write() Methode.

Um vom Client zu lesen, erstellen wir eine neue ByteBuffer durch Aufrufen der allocate(4096) (wodurch ein 4K-Puffer erstellt wird), dann rufen wir die AsynchronousSocketChannel’s read() – Methode auf. Die read() gibt eine Future<Integer> zurück, auf der wir get() aufrufen können, um die Anzahl der vom Client gelesenen Bytes abzurufen., In diesem Beispiel übergeben wir get() einen Timeout-Wert von 20 Sekunden: Wenn wir in 20 Sekunden keine Antwort erhalten, wird die get() – Methode eine TimeoutException. Unsere Regel für diesen Echo-Server lautet: Wenn wir 20 Sekunden Stille beobachten, beenden wir die Konversation.

Als nächstes überprüfen wir die Position des Puffers, die die Position des letzten vom Client empfangenen Bytes ist. Wenn der Client eine leere Zeile sendet, erhalten wir zwei Bytes: einen Wagenrücklauf und einen Zeilenvorschub., Die Prüfung stellt sicher, dass, wenn der Client eine Leerzeile sendet, dass wir es als Indikator nehmen, dass der Client mit dem Gespräch fertig ist. Wenn wir aussagekräftige Daten haben, rufen wir dieByteBuffer’sflip() auf, um sie zum Lesen vorzubereiten. Wir erstellen ein temporäres Byte-Array, um die Anzahl der vom Client gelesenen Bytes zu speichern, und rufen dann die ByteBuffer’s get() auf, um Daten in dieses Byte-Array zu laden. Schließlich konvertieren wir das Byte-Array in eine Zeichenfolge, indem wir eine neue String Instanz erstellen., Wir geben die Zeile an den Client zurück, indem wir die Zeichenfolge in ein Byte-Array konvertieren, diese an die ByteBuffer.wrap() – Methode übergeben und die AsynchronousSocketChannel – Methode write() aufrufen. Jetzt haben wir clear() die ByteBuffer, was bedeutet, dass sie die auf Null setzt und die ByteBuffer in den Schreibmodus versetzt und dann die nächste Zeile vom Client liest.,

Beachten Sie nur, dass die main() – Methode, mit der der Server erstellt wird, auch einen Timer von 60 Sekunden einrichtet, um die Anwendung am Laufen zu halten. Da dieAsynchronousSocketChannel’saccept() Methode sofort zurückgibt, wenn wir nicht dieThread.sleep() dann wird unsere Anwendung sofort beendet.,

Um dies zu testen, starten Sie den Server und stellen Sie eine Verbindung mit einem Telnet-Client her:

telnet localhost 5000

Senden Sie einige Zeichenfolgen an den Server, beachten Sie, dass sie an Sie zurückgesendet werden, und senden Sie dann eine leere Zeile, um die Konversation zu beenden.

Abschließend

In diesem Artikel habe ich zwei Ansätze zur Socket-Programmierung mit Java vorgestellt: den traditionellen Ansatz, der mit Java 1.0 eingeführt wurde, und den neueren, nicht blockierenden NIO und NIO.2 in Java 1.4 bzw. Java 7 eingeführte Ansätze., Sie haben mehrere Iterationen eines Java-Socket-Clients und eines Java-Socket-Servers gesehen, die sowohl den Nutzen grundlegender Java-E/A als auch einige Szenarien demonstrieren, in denen nicht blockierende E/A das Java-Socket-Programmiermodell verbessern. Mit nicht blockierenden E / A können Sie Java-Netzwerkanwendungen so programmieren, dass mehrere gleichzeitige Verbindungen verarbeitet werden, ohne mehrere Thread-Sammlungen verwalten zu müssen. Sie können auch die neue Serverskalierbarkeit nutzen, die in NIO und NIO integriert ist.2.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.