Programación de sockets en Java: un tutorial

Este tutorial es una introducción a la programación de sockets en Java, comenzando con un ejemplo simple de cliente-servidor que demuestra las características básicas de Java I / O. Se le presentará tanto el java.io paquete original  como NIO, las java.nioAPI de E / S ( ) sin bloqueo introducidas en Java 1.4. Finalmente, verá un ejemplo que demuestra las redes Java implementadas desde Java 7 en adelante, en NIO.2.

La programación de sockets se reduce a dos sistemas que se comunican entre sí. Generalmente, la comunicación de red se presenta en dos formas: Protocolo de control de transporte (TCP) y Protocolo de datagramas de usuario (UDP). TCP y UDP se utilizan para diferentes propósitos y ambos tienen restricciones únicas:

  • TCP es un protocolo relativamente simple y confiable que permite a un cliente hacer una conexión a un servidor y a los dos sistemas comunicarse. En TCP, cada entidad sabe que se han recibido sus cargas útiles de comunicación.
  • UDP es un protocolo sin conexión y es bueno para escenarios en los que no es necesario que todos los paquetes lleguen a su destino, como la transmisión de medios.

Para apreciar la diferencia entre TCP y UDP, considere lo que sucedería si estuviera transmitiendo video desde su sitio web favorito y perdiera fotogramas. ¿Preferiría que el cliente ralentizara su película para recibir los fotogramas que faltan o preferiría que el video continúe reproduciéndose? Los protocolos de transmisión de video generalmente aprovechan UDP. Debido a que TCP garantiza la entrega, es el protocolo elegido para HTTP, FTP, SMTP, POP3, etc.

En este tutorial, te presento a la programación de sockets en Java. Presento una serie de ejemplos cliente-servidor que demuestran características del marco de E / S Java original, luego avanzo gradualmente al uso de características introducidas en NIO.2.

Sockets Java de la vieja escuela

En implementaciones anteriores a NIO, la java.net.Socketclase maneja el código de socket del cliente TCP de Java . El siguiente código abre una conexión a un servidor:

 Socket socket = nuevo Socket (servidor, puerto); 

Una vez que nuestra socketinstancia está conectada al servidor, podemos comenzar a obtener flujos de entrada y salida al servidor. Los flujos de entrada se utilizan para leer datos del servidor, mientras que los flujos de salida se utilizan para escribir datos en el servidor. Podemos ejecutar los siguientes métodos para obtener flujos de entrada y salida:

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

Debido a que estos son flujos ordinarios, los mismos flujos que usaríamos para leer y escribir en un archivo, podemos convertirlos al formato que mejor se adapte a nuestro caso de uso. Por ejemplo, podríamos envolver el OutputStreamcon un PrintStreampara que podamos escribir texto fácilmente con métodos como println(). Para otro ejemplo, podríamos envolver el InputStreamcon a BufferedReader, a través de un InputStreamReader, para leer fácilmente texto con métodos como readLine().

descargar Descargar el código fuente Código fuente para "Programación de sockets en Java: Un tutorial". Creado por Steven Haines para JavaWorld.

Ejemplo de cliente de socket Java

Trabajemos con un ejemplo corto que ejecuta un HTTP GET contra un servidor HTTP. HTTP es más sofisticado de lo que permite nuestro ejemplo, pero podemos escribir código de cliente para manejar el caso más simple: solicitar un recurso del servidor y el servidor devuelve la respuesta y cierra el flujo. Este caso requiere los siguientes pasos:

  1. Cree un socket para el servidor web que escucha en el puerto 80.
  2. Obtenga un PrintStreamal servidor y envíe la solicitud GET PATH HTTP/1.0, donde PATHestá el recurso solicitado en el servidor. Por ejemplo, si quisiéramos abrir la raíz de un sitio web, la ruta sería /.
  3. Obtenga un mensaje InputStreampara el servidor, envuélvalo con un BufferedReadery lea la respuesta línea por línea.

El Listado 1 muestra el código fuente de este ejemplo.

Listado 1. SimpleSocketClientExample.java

paquete com.geekcap.javaworld.simplesocketclient; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.Socket; public class SimpleSocketClientExample {public static void main (String [] args) {if (args.length <2) {System.out.println ("Uso: SimpleSocketClientExample"); System.exit (0); } String server = args [0]; Ruta de la cadena = args [1]; System.out.println ("Cargando contenido de URL:" + servidor); try {// Conectarse al servidor Socket socket = new Socket (servidor, 80); // Crea flujos de entrada y salida para leer y escribir en el servidor PrintStream out = new PrintStream (socket.getOutputStream ()); BufferedReader in = new BufferedReader (nuevo InputStreamReader (socket.getInputStream ())); // Siga el protocolo HTTP de GET HTTP / 1.0 seguido de una línea vacía out.println ("GET" + ruta + "HTTP / 1.0"); out.println (); // Leer datos del servidor hasta que terminemos de leer el documento String line = in.readLine (); while (línea! = nulo) {System.out.println (línea); línea = in.readLine (); } // Cerrar nuestras transmisiones in.close (); out.close (); socket.close (); } captura (Excepción e) {e.printStackTrace (); }}}

El Listado 1 acepta dos argumentos de línea de comandos: el servidor al que conectarse (asumiendo que nos estamos conectando al servidor en el puerto 80) y el recurso a recuperar. Crea un Socketque apunta al servidor y especifica explícitamente el puerto 80. Luego ejecuta el comando:

OBTENER RUTA HTTP / 1.0 

Por ejemplo:

GET / HTTP / 1.0 

¿Lo que acaba de suceder?

Cuando recupera una página web de un servidor web, por ejemplo www.google.com, el cliente HTTP utiliza servidores DNS para encontrar la dirección del servidor: comienza preguntando al servidor de dominio de nivel superior el comdominio donde el servidor de nombres de dominio autorizado está para el www.google.com. Luego le pide al servidor de nombres de dominio la dirección (o direcciones) IP para www.google.com. A continuación, abre un socket a ese servidor en el puerto 80. (O, si desea definir un puerto diferente, puede hacerlo agregando dos puntos seguidos por el número de puerto, por ejemplo:. :8080) Finalmente, el cliente HTTP ejecuta el método HTTP especificado, tales como GET, POST, PUT, DELETE, HEAD, o OPTI/ONS. Cada método tiene su propia sintaxis. Como se muestra en los recortes de código anteriores, el GETmétodo requiere una ruta seguida deHTTP/version numbery una línea vacía. Si quisiéramos agregar encabezados HTTP, podríamos haberlo hecho antes de ingresar a la nueva línea.

En el Listado 1, recuperamos un OutputStreamy lo envolvimos en un PrintStreampara que pudiéramos ejecutar más fácilmente nuestros comandos basados ​​en texto. Nuestro código obtuvo un InputStream, lo envolvió en un InputStreamReader, que lo convirtió en un Reader, y luego lo envolvió en un BufferedReader. Usamos PrintStreampara ejecutar nuestro GETmétodo y luego usamos BufferedReaderpara leer la respuesta línea por línea hasta que recibimos una nullrespuesta, lo que indica que el socket se había cerrado.

Ahora ejecute esta clase y pásele los siguientes argumentos:

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

Debería ver un resultado similar al siguiente:

Cargando contenido de URL: www.javaworld.com HTTP / 1.1 200 OK Fecha: Dom, 21 de septiembre de 2014 22:20:13 GMT Servidor: 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 Última modificación: Tue, 24 Jan 2012 00:09:09 GMT Etag: "60001b-a8-4b73af4bf3340" Content-Type : texto / html Variar: Aceptar conexión de codificación: cerrar la página de prueba de gasolina

Éxito

Esta salida muestra una página de prueba en el sitio web de JavaWorld. Respondió que habla HTTP versión 1.1 y la respuesta es 200 OK.

Ejemplo de servidor de socket Java

Hemos cubierto el lado del cliente y, afortunadamente, el aspecto de la comunicación del lado del servidor es igual de fácil. Desde una perspectiva simplista, el proceso es el siguiente:

  1. Cree un ServerSocket, especificando un puerto para escuchar.
  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(); } } }