====== Comunicación en red: Sockets ======
Las aplicaciones que se comunican a través de una red, utilizan la insfraestructura de protocolos con las que se rigen las redes. En nuestro caso tan solo nos debemos preocupar de cómo programar el funcionamiento de esa comunicación.
El primer concepto que debemos tratar es que toda aplicación en red utiliza una arquitectura cliente-servidor. Una parte del programa establecerá una conexión con otra parte del programa, y esta segunda parte está pendiente de atender conexiones:
* La parte de un programa que inicia una conexión hacia un host destino se conoce como **cliente**.
* La parte de un programa que atiende conexiones de clientes se conoce como **servidor**.
* Un programa puede ejercer de cliente y de servidor al mismo tiempo, pero para iniciar una comunicación, una parte debe realizar la conexión primero.
* Es habitual que diferentes clientes se comuniquen entre sí mediante el uso de un servidor, el cual canalizará los mensajes desde y hacia los diferentes cliente.
Por último, a la hora de crear una aplicación en red, en muchos casos queremos permitir que varios clientes se conecten al servidor al mismo tiempo, por lo que también debemos utilizar los conceptos de la **programación multihilo** para poder atender todas las solicitudes de los clientes de forma simultánea.
===== Arquitectura cliente-servidor =====
A la hora de desarrollar aplicaciones en red, debemos entender que están compuesta de dos programas que se ejecutan de forma independiente, y que debo iniciar por separado.
Algunos aspectos que debemos tener en cuenta en una aplicación //cliente-servidor// son:
* En una aplicación que siga el modelo de la arquitectura cliente-servidor existe una aplicación //servidor// que ofrece sus servicios a través de la red (LAN, Internet, . . .) a múltiples //hosts// o //clientes// que pueden conectarse con ella
* Protocolos como la Web, FTP, correo electrónico, mensajería instantánea y otros se apoyan en esta arquitectura para funcionar
* Es una arquitectura centralizada, es el servidor quién da el servicio de forma concurrente a todos los clientes que se conectan a él. Si éste cae, todo el servicio se detiene
* El servidor tendrá que diseñarse como multihilo, puesto que tiene que dar servicio a muchos clientes al mismo tiempo. Hoy en día no tendría sentido pensar en una aplicación servidor que sólo pudiera //atender// a un cliente al mismo tiempo
* Las conexiones realizadas necesitan conocer //IP// y //puerto// para establecerse, aunque el puerto a veces no se indique si se conoce de antemano por la aplicación cliente (//well known ports//) como ocurre, por ejemplo, cuando solicitamos una //URL// desde el navegador (no hace falta indicar el puerto de conexión)
===== Sockets =====
Un //socket// es la conexión que se establece entre dos aplicaciones en dos hosts diferentes, una aplicación cliente en un host y otra aplicación servidor en otro (siguiendo la arquitectura cliente-servidor) a través de una red (LAN, WAN, . . .)
==== Sockets en Java ====
El //package// [[https://docs.oracle.com/javase/8/docs/api/java/net/package-summary.html|java.net]] dispone de toda una API para trabajar con //sockets// y todo lo necesario para desarrollar aplicaciones cliente-servidor. Además, como ya se ha visto en el tema anterior, también disponemos de una API completa para desarrollar aplicaciones multihilo.
Para trabajar con sockets en Java disponemos de las clases [[https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html|Socket]] y [[https://docs.oracle.com/javase/8/docs/api/java/net/ServerSocket.html|ServerSocket]]. Nos permiten realizar conexiones desde un cliente o para recibir y establecer la conexión desde un servidor, respectivamente.
==== Clase Socket ====
Para que una aplicación Java pueda realizar una conexión de red mediante un socket cliente, necesitaremos dos parámetros: la dirección IP del host al que nos queremos conectar y el puerto donde "escucha" la aplicación servidor a la que nos queremos conectar. A continuación, es esencial establecer los flujos de comunicación que permitirán comunicarnos hacia el servidor (flujo de salida) y recibir los mensaje que éste nos envíe (flujo de entrada).
^Método^Función^
|Socket(String host, int port)| Consctructor de la clase, se conecta a la dirección indicada|
|close()|Cierra el socket|
|getInputStream()|Obtiene el //Stream// de lectura|
|getOutputStream()|Obtiene el //Stream// de escritura|
|isClose()|Indica si el socket está cerrado|
|isConnected()|Indica si el socket está conectado|
|getLocalSocketAddress()|Devuelve la dirección local del socket|
|getRemoteSocketAddress()|Devuelve la direccion del servidor|
Los pasos a realizar para establecer una conexión desde un socket cliente son:
- Crear el socket indicando IP y Puerto del servidor
- Abrir los flujos de lectura y escritura
- Intercambiar datos con el servidor
- Cerrar los flujos de datos
- Cerrar la conexion
. . .
// Realiza la conexión con el host remoto
Socket socketCliente = new Socket("10.10.10.10", 55555);
// Establece los flujos de comunicación de entrada y salida
PrintWriter salida = new PrintWriter(socket.getOutputStream(), true);
BufferedReader entrada = new BufferedReader(new InputStreamReader(socket.getInputStream()));
. . .
//Realiza comunicación
salida.println("mensaje...");
String mensaje = entrada.readLine();
. . .
salida.close();
entrada.close();
socketCliente.close()
==== Clase ServerSocket ====
La clase''ServerSocket'' permite que aplicaciones Java se ejecuten en una máquina y definir un puerto, que será al que los Sockets cliente dirigirán sus intentos de conexión.
Para crear un ServerSocket sólo es necesario indicar el puerto en el que la aplicación quedará "escuchando" las conexiones de los clientes.
Una vez que tenemos definido el puerto de escucha, ''ServerSocket'' dispone del método ''accept()'' el cual bloquea la ejecución de la aplicación hasta que se recibe la conexión de un //Socket// cliente. En el momento en que se recibe una conexión, el método ''accept()'' nos ofrece un objeto ''Socket'', para poder comunicarnos con el Socket del cliente. Mediante ese //socket// podremos abrir los canales de lectura y escritura de datos.
Hay que tener en cuenta, según se puede observar en el gráfico anterior, que el canal de entrada del //socket cliente// corresponde con el de salida del //socket// del servidor y viceversa.
Pasos para crear un servidor:
- Crear el ServerSocket indicando el puerto de escucha
- Esperar a recibir una conexión (accept())
- Obtener el socket con el que establezco una conexión con el cliente
- Abrir flujos de lectura/escritura
- Intercambiar datos con el cliente
- Cerrar los flujos de lectura/escritura
- Cerrar la conexión
- Si no se esperan más conexiones de clientes, cerrar el ServerSocket
// Construyo un servidor que admite conexiones en el puerto 55555
ServerSocket socketServidor = new ServerSocket(55555);
// El servidor se queda esperando la conexión de un cliente
Socket socketCliente = socketServidor.accept();
// Una vez recibida la conexión establece los flujos de comunicación con ese cliente
PrintWriter salida = new PrintWriter(socketCliente.getOutputStream(), true);
BufferedReader entrada = new BufferedReader(new InputStreamReader(socketCliente.getInputStream()));
. . .
//Realiza la comunicación
entrada.readLine();
salida.println("mensaje...");
. . .
entrada.close();
salida.close();
socketCliente.close();
socketServidor.close();
{{ vimeo>791344923?medium }}
> Funcionamiento y explicación de Socket y ServerSocket
=== Recibir varias conexiones ===
El método ''accept()'' se encarga de recibir la conexión de un cliente. Podemos hacer que nuestro programa pueda recibir más conexiones invocándo ese método varias veces. Pero mientras el servidor no utilice hilos no podrá atenderlos simultaneamente, sino por turnos:
// Espero la conexión de un cliente
Socket cliente1 = socketServidor.accept();
PrintWriter salida1 = new PrintWriter(cliente1.getOutputStream(), true);
BufferedReader entrada1 = new BufferedReader(new InputStreamReader(cliente1.getInputStream()));
// Espero la conexión de otro cliente
Socket cliente2 = socketServidor.accept();
PrintWriter salida2 = new PrintWriter(cliente2.getOutputStream(), true);
BufferedReader entrada2 = new BufferedReader(new InputStreamReader(cliente2.getInputStream()));
// Realizo la comunicación con el cliente1
entrada1.readLine();
salida1.println("mensaje...");
// Realizo la comunicación con el cliente2
entrada2.readLine();
salida2.println("mensaje...");
==== Servidor multihilo ====
El ejemplo de servidor que hemos visto solo podrá atender a un cliente, ya que cuando recibe una conexión de un cliente (''accept()'') no sigue escuchando nuevas peticiones. Si queremos que nuestro servidor pueda aceptar una cualquier cantidad de conexiones necesitamos el uso de la programación multihilo (''Threads'').
A continuación se muestra una forma sencilla de hacer que nuestra aplicación servidor, al recibir una conexión, cree un hilo que se encargue de ejecutar el código para atenderla, sin afectar al hilo principal de la aplicación. De esta forma es capaz de volver a escuchar nuevas conexiones mientras está atendiendo las ya recibidas.
. . .
// El servidor comienza a conexiones escuchar
ServerSocket socketServidor = new ServerSocket(55555);
. . .
// Recibe la conexión de un cliente
while (conectado) {
Socket socketCliente = socketServidor.accept();
// Crea y lanza un hilo para atender a ese cliente
ConexionCliente conexionCliente = new ConexionCliente(socketCliente);
conexionCliente.start();
}
. . .
Clase para el hilo que gestiona las comunicaciones de un cliente:
public class ConexionCliente extends Thread {
private Socket socket;
private PrintWriter salida;
private BufferedReader entrada;
public ConexionCliente(Socket socket){
this.socket = socket;
crearCanalesIO();
}
public void crearCanalesIO(){
// Establece los flujos de comunicación con ese cliente
salida = new PrintWriter(socket.getOutputStream(), true);
entrada = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
@Override
public void run() {
...
// Método que ejecutará el código para la comunicacion con el cliente
// Leemos o escribimos a través de los flujos entrada y salida
salida.println("Bienvenido");
String mensajeRecibido = entrada.readLine();
}
public void desconectar(){
entrada.close();
salida.close();
socket.close();
}
}
==== Clases útiles ====
Además de las clases para trabajar con Sockets, a continuación se indican algunas clases interesantes del paquete ''java.net'' para programación en red, y también otras clases del paquete ''java.io'' para manejar los flujos de lectura/escritura:
Paquete ''[[https://docs.oracle.com/javase/8/docs/api/java/net/package-summary.html|java.net]]'':
^Clase^Función^
|[[https://docs.oracle.com/javase/8/docs/api/java/net/InetSocketAddress.html|InetSocketAddress]]|Representa la dirección de un socket mediante IP y puerto|
|[[https://docs.oracle.com/javase/8/docs/api/java/net/InetAddress.html|InetAddress]]|Representa una dirección de red|
|[[https://docs.oracle.com/javase/8/docs/api/java/net/Inet4Address.html|Inet4Address]]|Representa dirección IPv4, mediante 4 bytes|
Paquete ''[[https://docs.oracle.com/javase/8/docs/api/java/io/package-summary.html|java.io]]'':
^Tipo de datos^Clase^Función^
|bytes|[[https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html|In(Out)putStream]]|Son las clases bases para leer flujos de bytes desde fichero o desde la red|
|bytes|[[https://docs.oracle.com/javase/8/docs/api/java/io/ObjectInputStream.html|ObjectIn(Out)putStream]]|Flujo de bytes para datos primitivos y objetos serializados|
|bytes|[[https://docs.oracle.com/javase/8/docs/api/java/io/BufferedInputStream.html|BufferedIn(Out)putStream]]|Flujo de bytes que usa un buffer para mejorar su rendimiento|
|texto|[[https://docs.oracle.com/javase/8/docs/api/java/io/InputStreamReader.html|InputStreamReader]]|Hace de puente entre un flujo de bytes y un flujo de caracteres|
|texto|[[https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html|BufferedReader]] | Permite usar un buffer sobre un flujo de caracteres|
|texto|[[https://docs.oracle.com/javase/8/docs/api/java/io/PrintWriter.html|PrintWriter]]|Escribe flujos de bytes como cadenas de caracteres de forma sencilla. Tiene un constructor que activa //autoflush//|
Hay muchas otras clases en el paquete ''[[https://docs.oracle.com/javase/8/docs/api/java/io/package-summary.html|java.io]]'', pero con las anteriores me basta para transmitir todo tipo de datos a través de los canales de lectura y escritura de un socket. Para usarlas debo atender a las siguientes consideraciones:
==== Consideraciones sobre flujos de entrada y salida ====
Las siguientes cuestiones se deben tener en cuenta cuando utilizo clases de lectura/escritura (Streams) para transferir información por los canales de un socket.
- Los flujos de entrada y salida del socket **solo deben ser cerrados cuando no vayamos a usar más ese socket** para transmitir información durante la ejecución de mi programa.
- Cada socket tiene dos canales: uno de entrada (clase ''[[https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html|InputStream]]''), obtenido con el método ''getInputStream()'' y uno de salida (clase ''[[https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html|OutputStream]]'') obtenido con el método ''getOutputStream()''.
- Puedo utilizar objetos de otras clases (''PrintWriter'', ''BufferedReader'', ''ObjectOutputStream'', etc) para trabajar con estos dos canales de entrada y salida.
- Pero si creo un objeto para trabajar con un canal de E/S, **no debo volver a crear otro objeto que use el mismo canal**; debo usar el objeto de E/S elegido durante todo el uso del socket.
- Si en mi programa tengo diferentes clases que usan el mismo socket para transferir, debo pasar por parámetro a esas clases el socket, o los objetos de entrada y salida del socket, ya que no puedo crear nuevos objetos.
- Hay algunas clases que me permiten escribir texto, otras tipos primitivos, otras objetos, etc. Es por esto, que debo estudiar qué clases me interesa utilizar para el tipo de aplicación que estoy programando.
- Si un socket debe transmitir información de tipo texto, me basta con las clases ''PrintWriter'' y ''BufferedReader''.
- Las clases ''ObjectIn/OutputStream'' me permite transmitir bytes, tipos primitivos y objetos.
En StackOverFlow hay respuestas a problemas originados de incumplir estas cuestiones. [[https://stackoverflow.com/questions/2393179/streamcorruptedexception-invalid-type-code-ac|Aquí lo comentan en la primera respuesta]].
=====Transferencia de texto=====
A continuación vemos un ejemplo práctica de aplicación que usas clases para transferir datos de texto. Utilizamos las clases de los ejemplos anteriores : ''PrintWriter'' para el flujo de salida y ''BufferedReader'' para el de entrada.
==== Cliente/Servidor echo ====
//echo// es un servicio que simplemente repite el mensaje que el cliente le envía a través de un socket. Es un servicio muy sencillo (y poco útil) pero que nos permitirá comprobar la conectividad y la funcionalidad de los sockets para un primer instante.
=== Cliente echo ===
. . .
// Nombre y puerto del servidor
String hostname = "10.10.10.10";
int puerto = 44444;
try {
Socket socket = new Socket(hostname , puerto);
PrintWriter salida = new PrintWriter(socket.getOutputStream(), true);
BufferedReader entrada = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// Captura el teclado del usuario
BufferedReader teclado = new BufferedReader(new InputStreamReader(System.in) );
String cadena = null;
// Envia lo que el usuario escribe por teclado al servidor y lee la respuesta
while ((cadena = teclado.readLine()) != null) {
salida.println(cadena);
System.out.println(entrada.readLine());
}
} catch (UnknownHostException uhe) {
. . .
} catch (IOException ioe) {
. . .
}
. . .
=== Servidor echo ===
. . .
int puerto = 44444;
try {
ServerSocket socketServidor = new ServerSocket(puerto);
// Espera la conexion con un cliente
Socket socketCliente = socketServidor.accept();
// Establece los flujos de salida y entrada (hacia y desde el cliente, respectivamente)
PrintWriter salida = new PrintWriter(socketCliente.getOutputStream(), true);
BufferedReader entrada = new BufferedReader(new InputStreamReader(socketCliente.getInputStream()));
// Envia algunos mensajes al cliente en cuanto este se conecta
salida.println("Solo sé repetir lo que me escribas");
salida.println("Cuando escribas ’.’, se terminara la conexión");
String linea = null;
while ((linea = entrada.readLine()) != null) {
if (linea.equals(".")) {
socketCliente.close();
socketServidor.close();
break;
}
}
} catch (IOException ioe) {
. . .
}
. . .
==== Servidor echo multihilo ====
En este caso, aprovechando las características de los hilos de Java, implementaremos una versión del servidor de //echo// capaz de atender múltiples conexiones simultáneas.
=== Clase Servidor ===
En este caso, para el servidor multihilo, tendremos una clase ''Servidor'', que se corresponde con el siguiente código, y que lanza un objeto ''ConexionCliente'' para atender a cada uno de los clientes que se conectan al servidor. De esa manera, puesto que el objeto ''ConexionCliente'' es un hilo, puede atender una petición mientras espera otra y asi sucesivamente.
. . .
ServerSocket servidor = null;
ConexionCliente conexionCliente = null;
int puerto = 44444;
try {
servidor = new ServerSocket(puerto);
while (!servidor.isClosed()) {
conexionCliente = new ConexionCliente(servidor.accept());
conexionCliente.start();
}
} catch (IOException ioe) {
. . .
}
. . .
=== Clase ConexionCliente: Hilo ===
En esta clase ''ConexionCliente'' habrá que implementar lo necesario para atender una sola petición de cliente. Hay que tener en cuenta que habrá tanto objetos de esta clase como clientes conectados en un momento determinado. Al tratarse de un hilo se ejecutará en segundo plano y podrán atenderse múltiples de ellas al mismo tiempo.
public class ConexionCliente extends Thread {
private Socket socket;
private PrintWriter salida;
private BufferedReader entrada;
public ConexionCliente(Socket socket) throws IOException {
this.socket = socket;
salida = new PrintWriter(socket.getOutputStream(), true);
entrada = new BufferedReader(new InputStreamReader(socket.getInputStream()));
}
@Override
public void run() {
salida.println("Solo sé repetir lo que me escribas");
salida.println("Cuando escribas ’.’, se terminara la conexión");
try {
String mensaje = null;
while ((mensaje = entrada.readLine()) != null) {
if (mensaje.equals(".")) {
socket.close();
break;
}
salida.println(mensaje);
}
} catch (IOException ioe) {
. . .
}
}
{{ vimeo>775401845?medium }}
> Servicio multihilo de mensajería
===== Transmitir otros tipos de datos =====
Los ejemplos planteados en este bloque se centran en transferir texto a través de los sockets. Pero nos puede interesar transferir otro tipo de información: bytes de un fichero completo, objetos creados por nosotros, etc. El funcionamiento de los sockets no cambia ya que están diseñados para transmitir bytes, pero necesitaremos utilizar otras clases para //envolver// los streams de entrada y salida del socket.
Las clases más completas son ''ObjectInputStream'' y ''ObjectOutputStream'', ya que permiten escribir tanto bytes, como tipos primitivos u objetos.
==== Objetos ====
Para transmitir objetos, las clases ''ObjectInputStream'' y ''ObjectOutputStream'' tienen métodos para escribir y leer directamente objetos completos.
El único requisito es que toda clase del objeto que deseo transmitir, o las clases de los objetos de las que se componen, implementen la interface ''[[https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html|Serializable]]''.
Socket cliente;
ArrayList personas;
ObjectOutputStream flujoSalida = new ObjectOutputStream(cliente.getOutputStream());
ObjectInputStream flujoEntrada = new ObjectInputStream(cliente.getInputStream());
. . .
public void enviarLista(){
flujoSalida.writeObject(personas);
}
. . .
public void recibirLista(){
ArrayList listaRecibida = (ArrayList)flujoEntrada.readObject();
}
* **En el //lado Servidor// debo crear siempre primero el ObjectOutputStream**, antes que el ObjectInputStream. Si no, corremos riesgo de bloqueo.
=== Método flush() ===
En los casos en los que realice una comunicación bidireccional (sockets escribiendo y esperando a leer desde el mismo extremo), puedo tener la necesidad de que sea una comunicación **síncrona**: un extremo del socket se queda esperando a recibir datos del otro extremo, para poder una respuesta.
Si recordamos, cuando utilizamos la clase ''PrintWriter'', indicamos en su constructor el valor ''true'' como segund0 parámetro, activando la opción //autoFlush//. Si no usamos ese contructor, también debemos llamar al método ''flush()''.
// Activamos autoflush en el constructor en el segundo parámetro (true)
PrintWriter salida = new PrintWriter(socket.getOutputStream(), true);
// De este modo me ahorro tener que llamar al método flush()
Sin embargo, la clase ''ObjectOutputStream'' no tiene ese constructor. Para evitar que queden datos sin enviar en el //buffer// de un flujo de salida (output), debo llamar explícitamente al método ''flush()'':
ObjectOutputStream salida = new ObjectOutputStream(socket.getOutputStream());
. . .
entrada.readInt();
salida.writeObject(datos);
salida.flush();
=== Método reset() ===
Cuando envío el mismo objeto repetidas veces a través del mismo extremo del socket, la clase ''ObjectOutputStream'' almacena en caché una copia de ese objeto, para que el envío sea más rápido la próxima vez.
Esto genera el problema de que si yo modifico propiedades de ese objeto, el método ''writeObject()'' no puede reconocer si el objeto se ha modificado o es el mismo de antes, y envía la copia que tiene ya en caché de ese objeto:
ObjectOutputStream salida = . . .;
persona.setNombre("Fernando");
salida.writeObject(persona); //Almacena en cache una copia de persona
persona.setNombre("Juan")
salida.writeObject(persona); //Envía la copia almacenada en cache (nombre->Fernando)
persona.setNombre("Maria");
salida.writeObject(persona); //Envía la copia almacenada en cache (nombre->Fernando)
Cuando tengo una situación en la que debo enviar el mismo objeto modificado varias veces por un mismo canal, debo asegurarme de //resetear// la caché del flujo de salida (''salida.reset()'') después de enviar el objeto.
Esto puede ser algo necesario en una aplicación en la que envío varias veces un mismo objeto ''List'' con una lista de objetos que se ha ido actualizando añadiendo o eliminando elementos.
==== Ficheros ====
En este caso, aparte de las clases para transmitir a través del socket, necesito leer desde un objeto //File// para enviarlo por el socket, y escribirlo en disco al recibirlo desde el otro lado del socket: Por lo tanto, necesito también las clases ''FileIn(Out)putStream''.
* Lado **emisor**: abre y lee un fichero de origen y lo envía por el Socket.
private Socket socket;
. . .
FileInputStream lectorFichero = new FileInputStream(fichero);
ObjectOutputStream salida = new ObjectOutputStream(socket.getOutputStream());
//Envío al receptor el tamaño total del fichero
salida.writeLong(fichero.length());
byte[] buffer = new byte[1024];
int bytesLeidos = 0;
while( (bytesLeidos = lectorFichero.read(buffer)) > 0){
//Envio datos a través del socket
salida.write(buffer, 0, bytesLeidos);
//Me aseguro de que se escriben todos lo bytes del buffer
salida.flush();
}
lectorFichero.close();
* Lado **receptor**: abre un fichero de destino y escribe en él lo que lee desde el Socket.
private Socket socket;
. . .
FileOutputStream escritorFichero = new FileOutputStream(ficheroDestino);
ObjectInputStream entrada = new ObjectInputStream(socket.getInputStream());
//Leo el tamaño del fichero que me ha enviado el emisor
long fileSize = entrada.readLong()
byte[] buffer = new byte[1024];
int bytesLeidos;
long totalLeido = 0;
//El bucle debe terminar cuando ha recibido la totalidad de bytes
// Sin esa condición, el bucle se va a quedar indefinidamente esperando a leer
while( totalLeido < fileSize && (bytesLeidos = entrada.read(buffer)) > 0 ){
escritorFichero.write(buffer, 0, bytesLeidos);
totalLeido += bytesLeidos; //Llevo la cuenta de los bytes leidos
}
escritorFichero.close();
Como acabamos de ver, para enviar un fichero, la parte del programa que lo envía a través del socket necesita enviarle previamente el tamaño de fichero para que el receptor sepa cuántos bytes debe leer. Si no enviamos el tamaño del fichero al receptor, el receptor nunca va a saber cuándo ha recibido la totalidad del fichero, y se quedará esperando a leer desde el socket indefinidamente.
Esto no ocurre con el flujo de lectura desde fichero, solamente con los flujos de lectura desde el socket. Cuando leemos desde un flujo de fichero (''FileInputStream''), hay un momento en que los bytes de ese fichero se acaban y el bucle se termina. Sin embargo al leer de un socket, no puedo saber cuándo se acaban los bytes ya que el socket seguirá abierto durante la ejecución de todo el programa, y puedo seguirlo usando para recibir otros ficheros más tarde.
==== Diferentes tipos de datos ====
Enviar objetos es útil, y lo podemos aplicar a la transferencia de ficheros. El cliente podría indicarle al servidor qué fichero desea descargar de una lista que previamente le envía el servidor.
* Lado servidor:
//Lista Strings que contiene las rutas de los ficheros disponibles
List listaFicheros;
//Se la envía al cliente cuando se conecta
salida.writeObject(listaFicheros);
//Espera la selección del cliente
String seleccion = (String)entrada.readObject();
//Crea el fichero para enviar su tamaño y disponerse a enviarlo a través del socket
File fichero = new File(seleccion);
salida.writeLong(fichero.length());
//Procede a leer el fichero y enviarlo a través del socket como en el ejemplo anterior
* Lado cliente:
//El cliente recibe la lista de ficheros disponibles al conectarse
List listaFicheros = (List)entrada.readObject();
. . .
//Envía al servidor el String con la selección del usuario
salida.writeObject(ficheroElegido);
//Espera a recibir el tamaño del fichero para proceder la lectura
long fileSize = entrada.readLong();
//Procede a leer desde el socket como en el ejemplo anterior y escribirlo en fichero
{{ vimeo>790252929?medium }}
> Video: Transferir objetos, bytes y tipos de datos primitivos a través de sockets
----
===== Proyectos de ejemplo =====
Diferentes proyectos de este tema, junto con los expuestos en los videos, se pueden encontrar en el [[ https://bitbucket.org/fvaldeon/psp-red|repositorio de red]] de Bitbucket.
Los proyectos de los ejercicios que se vayan haciendo en clase estarán disponibles en el [[https://bitbucket.org/fvaldeon/psp-ejercicios22-23|repositorio psp-ejercicios de Bitbucket]]
----
===== Prácticas =====
* **Práctica 2.1** Creación de una aplicación cliente-servidor
----
(c) {{date> %Y}} Santiago Faci y Fernando Valdeón