este tutorial es una introducción a la programación de sockets en Java, comenzando con un simple ejemplo cliente-servidor que demuestra las características básicas de Java I/O. Se le presentará tanto el paquete original
java.io
y NIO, la E/S sin bloqueo (java.nio
) API introducidas en Java 1.4. Por último, verá un ejemplo que muestra las redes Java implementadas desde Java 7 forward, En NIO.2.,
La programación de sockets se reduce a dos sistemas que se comunican entre sí. Generalmente, la comunicación de red viene en dos tipos: 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 establecer una conexión con un servidor y que los dos sistemas se comuniquen. 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 necesariamente necesita cada paquete para llegar 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 se cayeran fotogramas. Preferiría que el cliente reduzca su película para recibir los fotogramas que faltan o prefiere usted que el vídeo de seguir jugando? Los protocolos de transmisión de vídeo suelen aprovechar UDP., Debido a que TCP garantiza la entrega, es el protocolo de elección para HTTP, FTP, SMTP, POP3, etc.
en este tutorial, te presento la programación de sockets en Java. Presento una serie de ejemplos cliente-servidor que demuestran características del framework original de E/S de Java, luego avanzo gradualmente a usar características introducidas en NIO.2.
sockets Java de la vieja escuela
en implementaciones anteriores a NIO, el código de socket de Cliente TCP de Java es manejado por la clase java.net.Socket
., El siguiente código abre una conexión a un servidor:
Socket socket = new Socket( server, port );
Una vez que nuestra instancia socket
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 in = 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 a la forma que mejor sirva a nuestro caso de uso. Por ejemplo, se podría ajustar el OutputStream
PrintStream
así que fácilmente podemos escribir texto con métodos como println()
., Para otro ejemplo, se podría ajustar el InputStream
BufferedReader
, a través de un InputStreamReader
, para que sea fácil de leer el texto, con métodos como readLine()
.
Java socket client example
vamos a trabajar a través de 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 la secuencia. Este caso requiere los siguientes pasos:
- Crear un socket para el servidor web que escucha en el puerto 80.
- obtenga un
PrintStream
al servidor y envíe la solicitudGET PATH HTTP/1.0
, dondePATH
es el recurso solicitado en el servidor., Por ejemplo, si queremos abrir la raíz de un sitio web, la ruta sería/
. - obtenga un
InputStream
en el servidor, envuélvalo con unBufferedReader
y lea la respuesta línea por línea.
Listado 1 muestra el código fuente de este ejemplo.
Listado 1. Simplesocketclientejemplo.java
Listing 1 Acepta dos argumentos de línea de comandos: el servidor al que conectarse (suponiendo que nos estamos conectando al servidor en el puerto 80) y el recurso a recuperar., Crea un Socket
que apunta al servidor y especifica explícitamente el puerto 80
. Luego se ejecuta el comando:
GET PATH HTTP/1.0
Por ejemplo:
GET / HTTP/1.0
¿Qué ha sucedido?
cuando recupera una página web de un servidor web, como , el cliente HTTP utiliza servidores DNS para encontrar la dirección del servidor: comienza preguntando al servidor de dominio de nivel superior el dominio
com
donde el servidor de nombres de dominio autoritativo es para el ., Luego le pide al servidor de nombres de dominio la dirección IP (o direcciones) de
. 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 especificado método HTTP, como por ejemplo 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 método GET
requiere una ruta seguida de HTTP/version number
y una línea vacía. Si quisiéramos añadir encabezados HTTP podríamos haberlo hecho antes de introducir la nueva línea.
en el listado 1, recuperamos un OutputStream
y lo envolvimos en un PrintStream
para que pudiéramos ejecutar más fácilmente nuestros comandos basados en texto., Nuestro código obtenido un InputStream
, envuelto que en un InputStreamReader
, lo que convierte a un Reader
, y luego se envuelve de que en un BufferedReader
. Usamos el método PrintStream
para ejecutar nuestro método GET
y luego usamos el método BufferedReader
para leer la respuesta línea por línea hasta que recibimos una respuesta null
, indicando que el socket había sido cerrado.,
Ahora ejecute esta clase y pásele los siguientes argumentos:
java com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExample www.javaworld.com /
debería ver una salida similar a la siguiente:
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
.
Java socket server ejemplo
hemos cubierto el lado del cliente y afortunadamente el aspecto de comunicación del lado del servidor es igual de fácil., Desde una perspectiva simplista, el proceso es el siguiente:
- Crear un
ServerSocket
, especificando un puerto para escuchar. - invoque el método
ServerSocket
‘saccept()
para escuchar en el puerto configurado para una conexión de cliente. - Cuando un cliente se conecta al servidor, el método
accept()
devuelve unSocket
a través del cual el servidor puede comunicarse con el cliente., Esta es la misma claseSocket
que usamos para nuestro cliente, por lo que el proceso es el mismo: obtener unInputStream
para leer desde el cliente y unOutputStream
escribir en el cliente. - Si su servidor necesita ser escalable, querrá pasar el
Socket
a otro hilo para procesar de modo que su servidor pueda continuar escuchando conexiones adicionales. - Llamada
ServerSocket
‘saccept()
método nuevo para escuchar la otra conexión.,
como Pronto verás, el manejo de NIO de este escenario sería un poco diferente., Por ahora, sin embargo, podemos crear directamente un ServerSocket
pasándole un puerto para escuchar (más sobre ServerSocketFactory
s en la siguiente sección):
ServerSocket serverSocket = new ServerSocket( port );
Y ahora podemos aceptar conexiones entrantes a través del 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., El servidor que se muestra es un servidor de eco, lo que significa que se hace eco de cualquier mensaje que recibe.
si bien el ejemplo de Listing 2 no es complicado, anticipa algo de lo que vendrá en la siguiente sección Sobre NIO. Preste especial atención a la cantidad de código de subproceso que tenemos que escribir para construir un servidor que pueda manejar múltiples solicitudes simultáneas.
Listing 2. SimpleSocketServer.java
en el listado 2 creamos una nueva instancia SimpleSocketServer
e iniciamos el servidor., Esto es necesario porque el SimpleSocketServer
amplía Thread
para crear un nuevo hilo para manejar el bloqueo accept()
llamada que se ven en el read()
método. El método run()
se encuentra en un bucle aceptando solicitudes de clientes y creando subprocesos RequestHandler
para procesar la solicitud. Una vez más, Este es un código relativamente simple, pero también implica una buena cantidad de programación enhebrada.,
tenga en cuenta también que RequestHandler
maneja la comunicación del cliente al igual que el código en el listado 1: envuelve el OutputStream
con un PrintStream
para facilitar las Escrituras fáciles y, de manera similar, envuelve el InputStream
with a BufferedReader
for easy reads. En lo que respecta a un servidor, lee las líneas del cliente y las devuelve al cliente. Si el cliente envía una línea vacía, la conversación termina y elRequestHandler
cierra el socket.
NIO y NIO.,2
para muchas aplicaciones, el modelo de programación de sockets Java base que acabamos de explorar es suficiente. Para aplicaciones que impliquen e/s más intensivas o entrada/salida asíncrona, querrá estar familiarizado con las API sin bloqueo introducidas en Java NIO y NIO.2.
el paquete NIO JDK 1.4 ofrece las siguientes características clave:
- Los canales están diseñados para admitir transferencias masivas de un búfer NIO a otro.
- Los Buffers representan un bloque de memoria contiguo interconectado por un simple conjunto de operaciones.,
- Non-Blocking Input/Output es un conjunto de clases que exponen canales a fuentes de E/s comunes como archivos y sockets.
al programar con NIO, abres un canal a tu destino y luego lees los datos en un búfer desde el destino, escribes los datos en un búfer y los envías a tu destino., Nos sumergiremos en la configuración de un socket y la obtención de un canal en breve, pero primero vamos a revisar el proceso de uso de un búfer:
- Escribir datos en un búfer
- llamar al buffer
flip()
método para prepararlo para leer - Leer datos del búfer
- llamar al buffer
clear()
orcompact()
método para prepararlo para recibir más datos
Cuando los datos se escriben en el búfer, el búfer conoce la cantidad de datos escritos en él., Mantiene tres propiedades, cuyos significados difieren si el búfer está en modo de lectura o modo de escritura:
- posición: en modo de escritura, la posición inicial es 0 y mantiene la posición actual en la que se escribe en el búfer; después de voltear un búfer para ponerlo en modo de lectura, restablece la posición a 0 y mantiene la posición actual en el búfer desde el que se lee,
- Capacidad: El tamaño fijo del búfer
- límite: en modo de escritura, el límite define cuántos datos se pueden escribir en modo de lectura, el límite define cuántos datos se pueden leer desde el búfer.,
Java I/O demo: servidor Echo con NIO.2
NIO.2, que se introdujo en JDK 7, extiende las bibliotecas de E/S sin bloqueo de Java para agregar soporte para tareas del sistema de archivos, como el paquete java.nio.file
y la clase java.nio.file.Path
y expone una nueva API del sistema de archivos. Con ese fondo en mente, vamos a escribir un nuevo servidor Echo usando NIO.2’s AsynchronousServerSocketChannel
.
el AsynchronousServerSocketChannel
proporciona un canal asíncrono sin bloqueo para tomas de escucha orientadas a la transmisión., Para usarlo, primero ejecutamos su método estático open()
y luego bind()
a un puerto específico. A continuación, ejecutaremos su método accept()
, pasándole una clase que implementa la interfaz CompletionHandler
. Muy a menudo, encontrará que el controlador creado como una clase interna anónima.
Listing 3 muestra el código fuente para nuestro nuevo servidor asincrónico Echo.
Listing 3. SimpleSocketServer.,java
en el listado 3 primero creamos un nuevo AsynchronousServerSocketChannel
y luego lo vinculamos al puerto 5000:
final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(5000));
desde este AsynchronousServerSocketChannel
, invocamos accept()
para decirle que comience a escuchar conexiones, pasándole una instancia personalizada CompletionHandler
. Cuando invocamos accept()
, retorna inmediatamente., Tenga en cuenta que este ejemplo es diferente de la clase ServerSocket
en el listado 1; mientras que el método accept()
bloqueado hasta que un cliente se conecte a él, el método AsynchronousServerSocketChannel
accept()
lo maneja por nosotros.
El controlador de finalización
Nuestro próximo responsabilidad es crear un CompletionHandler
clase y proporcionar una implementación de la etiqueta completed()
y failed()
métodos., El completed()
método es llamado cuando el AsynchronousServerSocketChannel
recibe una conexión de un cliente y que incluye un AsynchronousSocketChannel
para el cliente. El método completed()
primero acepta la conexión desde AsynchronousServerSocketChannel
y luego comienza a comunicarse con el cliente. Lo primero que hace es escribir un mensaje de «Hola»: construye una cadena, la convierte en una matriz de bytes, y luego la pasa a ByteBuffer.wrap()
para construir un ByteBuffer
., El ByteBuffer
puede pasar AsynchronousSocketChannel
‘s write()
método.
Para leer desde el cliente, creamos un nuevo ByteBuffer
invocando su allocate(4096)
(que crea un 4K de búfer), para luego invocar el AsynchronousSocketChannel
‘s read()
método. El read()
devuelve un Future<Integer>
en el que podemos invocar get()
para recuperar el número de bytes leídos desde el cliente., En este ejemplo, pasamos get()
un valor de tiempo de espera de 20 segundos: si no obtenemos una respuesta en 20 segundos, el método get()
arrojará un TimeoutException
. Nuestra regla para este servidor echo es que si observamos 20 segundos de silencio, entonces terminamos la conversación.
a continuación comprobamos la posición del buffer, que será la ubicación del último byte recibido del cliente. Si el cliente envía una línea vacía, entonces recibimos dos bytes: un retorno de carro y un avance de línea., La comprobación asegura que si el cliente envía una línea en blanco que lo tomamos como un indicador de que el cliente ha terminado con la conversación. Si tenemos datos significativos, llamamos al método ByteBuffer
‘s flip()
para prepararlo para la lectura. Creamos una matriz de bytes temporal para contener el número de bytes leídos desde el cliente y luego invocamos el ByteBuffer
‘s get()
para cargar datos en esa matriz de bytes. Finalmente, convertimos la matriz de bytes en una cadena creando una nueva instancia String
., Hacemos eco de la línea al cliente convirtiendo la cadena en una matriz de bytes, pasando eso al método ByteBuffer.wrap()
e invocando el método AsynchronousSocketChannel
‘s write()
. Ahora nos clear()
ByteBuffer
, que recuerdan significa que reposiciona el position
a cero y pone la etiqueta ByteBuffer
en el modo de escritura y, a continuación, podemos leer la siguiente línea del cliente.,
lo único que hay que tener en cuenta es que el método main()
, que crea el servidor, también configura un temporizador de 60 segundos para mantener la aplicación en ejecución. Debido a que el método AsynchronousSocketChannel
‘s accept()
regresa inmediatamente, si no tenemos el Thread.sleep()
entonces nuestra aplicación se detendrá inmediatamente.,
para probar esto, inicie el servidor y conéctese a él usando un cliente telnet:
telnet localhost 5000
envíe algunas cadenas al servidor, observe que se le hacen eco y luego envíe una línea vacía para terminar la conversación.
en conclusión
en este artículo he presentado dos enfoques para la programación de sockets con Java: El enfoque tradicional introducido con Java 1.0 y los más nuevos, NIO y NIO sin bloqueo.2 enfoques introducidos en Java 1.4 y Java 7, respectivamente., Ha visto varias iteraciones de un cliente de socket Java y un ejemplo de servidor de socket Java, demostrando tanto la utilidad de la E/S básica de Java como algunos escenarios donde la E/S sin bloqueo mejora el modelo de programación de socket Java. Usando E/S sin bloqueo, puede programar aplicaciones en red Java para manejar múltiples conexiones simultáneas sin tener que administrar múltiples colecciones de subprocesos. También puede aprovechar la nueva escalabilidad del servidor que está integrada en NIO y NIO.2.