Programmation de socket en Java: un tutoriel

Ce didacticiel est une introduction à la programmation de sockets en Java, en commençant par un simple exemple client-serveur démontrant les fonctionnalités de base d'E / S Java. Vous serez présenté à la fois au java.io package d' origine  et à NIO, les java.nioAPI d' E / S non bloquantes ( ) introduites dans Java 1.4. Enfin, vous verrez un exemple illustrant la mise en réseau Java implémentée à partir de Java 7, dans NIO.2.

La programmation des sockets se résume à deux systèmes communiquant entre eux. En règle générale, la communication réseau se décline en deux versions: le protocole TCP (Transport Control Protocol) et le protocole UDP (User Datagram Protocol). TCP et UDP sont utilisés à des fins différentes et ont tous deux des contraintes uniques:

  • TCP est un protocole relativement simple et fiable qui permet à un client de se connecter à un serveur et aux deux systèmes de communiquer. Dans TCP, chaque entité sait que ses charges utiles de communication ont été reçues.
  • UDP est un protocole sans connexion et convient aux scénarios dans lesquels vous n'avez pas nécessairement besoin de chaque paquet pour arriver à sa destination, comme le streaming multimédia.

Pour apprécier la différence entre TCP et UDP, pensez à ce qui se passerait si vous diffusiez de la vidéo à partir de votre site Web préféré et qu'il perdait des images. Préférez-vous que le client ralentisse votre film pour recevoir les images manquantes ou préférez-vous que la lecture de la vidéo se poursuive? Les protocoles de streaming vidéo exploitent généralement UDP. Parce que TCP garantit la livraison, c'est le protocole de choix pour HTTP, FTP, SMTP, POP3, etc.

Dans ce tutoriel, je vous présente la programmation de socket en Java. Je présente une série d'exemples client-serveur qui démontrent les fonctionnalités du framework Java I / O d'origine, puis je passe progressivement à l'utilisation des fonctionnalités introduites dans NIO.2.

Prises Java old-school

Dans les implémentations antérieures à NIO, le code de socket client TCP Java est géré par la java.net.Socketclasse. Le code suivant ouvre une connexion à un serveur:

 Socket socket = nouveau Socket (serveur, port); 

Une fois notre socketinstance connectée au serveur, nous pouvons commencer à obtenir des flux d'entrée et de sortie vers le serveur. Les flux d'entrée sont utilisés pour lire les données du serveur tandis que les flux de sortie sont utilisés pour écrire des données sur le serveur. Nous pouvons exécuter les méthodes suivantes pour obtenir des flux d'entrée et de sortie:

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

Comme ce sont des flux ordinaires, les mêmes flux que nous utiliserions pour lire et écrire dans un fichier, nous pouvons les convertir dans la forme qui correspond le mieux à notre cas d'utilisation. Par exemple, nous pourrions envelopper le OutputStreamavec un PrintStreamafin de pouvoir facilement écrire du texte avec des méthodes comme println(). Pour un autre exemple, nous pourrions envelopper le InputStreamavec un BufferedReader, via un InputStreamReader, afin de lire facilement du texte avec des méthodes comme readLine().

download Téléchargez le code source Code source pour «Programmation socket en Java: un tutoriel». Créé par Steven Haines pour JavaWorld.

Exemple de client de socket Java

Explorons un court exemple qui exécute un HTTP GET sur un serveur HTTP. HTTP est plus sophistiqué que ne le permet notre exemple, mais nous pouvons écrire du code client pour gérer le cas le plus simple: demander une ressource au serveur et le serveur renvoie la réponse et ferme le flux. Ce cas nécessite les étapes suivantes:

  1. Créez une socket sur le serveur Web en écoute sur le port 80
  2. Obtenez un PrintStreamau serveur et envoyez la demande GET PATH HTTP/1.0, où PATHest la ressource demandée sur le serveur. Par exemple, si nous voulions ouvrir la racine d'un site Web, le chemin serait /.
  3. Obtenez un InputStreamau serveur, enveloppez-le avec un BufferedReaderet lisez la réponse ligne par ligne.

Le listing 1 montre le code source de cet exemple.

Liste 1. SimpleSocketClientExample.java

package com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; classe publique SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Usage: SimpleSocketClientExample"); System.exit (0); } String server = args [0]; Chemin de chaîne = args [1]; System.out.println ("Chargement du contenu de l'URL:" + serveur); try {// Se connecter au serveur Socket socket = new Socket (server, 80); // Créer des flux d'entrée et de sortie pour lire et écrire sur le serveur PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream ())); // Suivez le protocole HTTP de GET HTTP / 1.0 suivi d'une ligne vide out.println ("GET" + chemin + "HTTP / 1.0"); out.println (); // Lire les données du serveur jusqu'à la fin de la lecture du document String line = in.readLine (); while (ligne! = null) {System.out.println (ligne); ligne = in.readLine (); } // Ferme nos flux in.close (); out.close (); socket.close (); } catch (Exception e) {e.printStackTrace (); }}}

Le listing 1 accepte deux arguments de ligne de commande: le serveur auquel se connecter (en supposant que nous nous connectons au serveur sur le port 80) et la ressource à récupérer. Il crée un Socketqui pointe vers le serveur et spécifie explicitement le port 80. Il exécute ensuite la commande:

OBTENIR LE CHEMIN HTTP / 1.0 

Par exemple:

GET / HTTP / 1.0 

Qu'est-ce qui vient juste de se passer?

Lorsque vous récupérez une page Web à partir d'un serveur Web, tel que www.google.com, le client HTTP utilise des serveurs DNS pour trouver l'adresse du serveur: il commence par demander au serveur de domaine de niveau supérieur le comdomaine où se trouve le serveur de noms de domaine faisant autorité pour le www.google.com. Ensuite, il demande à ce serveur de nom de domaine l'adresse IP (ou les adresses) pour www.google.com. Ensuite, il ouvre une socket à ce serveur sur le port 80. (Ou, si vous voulez définir un port différent, vous pouvez le faire en ajoutant un deux-points suivi du numéro de port, par exemple:. :8080) Enfin, le client HTTP s'exécute la méthode HTTP spécifiée, telle que GET, POST, PUT, DELETE, HEAD, ou OPTI/ONS. Chaque méthode a sa propre syntaxe. Comme indiqué dans les extraits de code ci-dessus, la GETméthode nécessite un chemin suivi deHTTP/version numberet une ligne vide. Si nous voulions ajouter des en-têtes HTTP, nous aurions pu le faire avant d'entrer la nouvelle ligne.

Dans la liste 1, nous avons récupéré un OutputStreamet l' avons encapsulé dans un PrintStreamafin de pouvoir exécuter plus facilement nos commandes textuelles. Notre code a obtenu un InputStream, l'a enveloppé dans un InputStreamReader, qui l'a converti en a Reader, puis l'a enveloppé dans un BufferedReader. Nous avons utilisé le PrintStreampour exécuter notre GETméthode, puis utilisé BufferedReaderpour lire la réponse ligne par ligne jusqu'à ce que nous recevions une nullréponse, indiquant que le socket avait été fermé.

Exécutez maintenant cette classe et transmettez-lui les arguments suivants:

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

Vous devriez voir une sortie similaire à celle ci-dessous:

Chargement du contenu de l'URL: www.javaworld.com HTTP / 1.1 200 OK Date: Sun, 21 Sep 2014 22:20:13 GMT Serveur: Apache X-Gas_TTL: 10 Cache-Control: max-age = 10 X-GasHost: gas2 .usw X-Cooking-With: Gasoline-Local X-Gasoline-Age: 8 Content-Length: 168 Dernière modification: Tue, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Content-Type : text / html Vary: Accept-Encoding Connection: fermez la page de test d'essence

Succès

Cette sortie affiche une page de test sur le site Web de JavaWorld. Il a répondu qu'il parle de la version 1.1 de HTTP et que la réponse est 200 OK.

Exemple de serveur socket Java

Nous avons couvert le côté client et, heureusement, l'aspect communication du côté serveur est tout aussi simple. Dans une perspective simpliste, le processus est le suivant:

  1. Créez un ServerSocket, en spécifiant un port sur lequel écouter.
  2. Invoke the ServerSocket's accept() method to listen on the configured port for a client connection.
  3. When a client connects to the server, the accept() method returns a Socket through which the server can communicate with the client. This is the same Socket class that we used for our client, so the process is the same: obtain an InputStream to read from the client and an OutputStream write to the client.
  4. If you server needs to be scalable, you will want to pass the Socket to another thread to process so that your server can continue listening for additional connections.
  5. Call the ServerSocket's accept() method again to listen for another connection.

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