En este ejemplo vamos a abrir un servidor y un cliente. El cliente le pedirá al servidor un fichero por su nombre, y este se lo enviará. No voy a entrar en detalles de cómo se abren los sockets en java. Como tampoco quiero liar el código, el cliente pedirá un fichero fijo, siempre el mismo, que sabemos que existe y el servidor se lo enviará. La idea es, por tanto, ver cómo enviamos un fichero por un socket, sin liar la forma de pedir el fichero, ni hacer hilos para atender muchos clientes y muchas peticiones ni nada de eso. Solo leer el fichero y enviarlo por el socket.
Una vez arrancados el cliente y el servidor, la mensajería entre ellos puede ser parecido a lo siguiente: El cliente le pide un fichero al servidor por medio de un MensajeDameFichero que contenga el nombre del fichero que quiere. El Servidor le contestará con uno o más mensajes MensajeTomaFichero. Este mensaje contendrá básicamente un array de bytes con el contenido del fichero, más otros campos que detallaremos luego.
¿Por qué uno o varios MensajeTomaFichero?. Se puede hacer perfectamente con un sólo mensaje. Se lee TODO el fichero, se mete en el array de bytes del mensaje y se envía. Esto no parece que sea muy adecuado si el fichero es grande. Es mejor definir un tamaño suficientemente grande, como 4000 bytes o así) y enviar el fichero en trozos. En el ejemplo y para poder probar cómodamente, he puesto 10 bytes como tamaño máximo, pero este valor no es eficiente, ya que manda muchos mensajes para un fichero pequeño. Puedes cambiar fácilmente el valor en el ejemplo.
Necesitamos entonces, dos mensajes. Un MensajeDameFichero del cliente al servidor y otro MensajeTomaFichero del servidor al cliente. Aprovechando las características de java, usamos dos clases con estos nombres que implementen la interface Serializable. De esta forma se puede enviar de forma muy sencilla el mensaje, pero perdemos compatibilidad con otros lenguajes, como C++. Haciéndolo así, no podemos programar por ejemplo, el servidor en java y el cliente en C++. Si quieres usar distintos lenguajes, échale un ojo a este tutorial.
El MensajeDameFichero únicamente necesita un atributo String con el nombre del fichero.
El MensajeTomaFichero necesita más cosas:
Aquí tienes ambas clases:
Vamos con el código del servidor. Los pasos que va a dar son los siguitentes:
Abrir el socket y establecer comunicación
ServerSocket
socketServidor =
new ServerSocket(puerto);
Socket cliente = socketServidor.accept();
cliente.setSoLinger(true, 10);
La llamada setSoLinger() hace que al cerrar el socket, se espere hasta un máximo de 10 segundos a que el cliente termine de leer los datos. Si no lo hacemos así, si enviamos un dato y cerramos inmediatamente detrás el socket, es posible que el cliente no tenga tiempo de leerlo y ese dato se pierda.
Esperar el mensaje MensajeDameFichero del cliente
ObjectInputStream ois =
new
ObjectInputStream(cliente.getInputStream());
Object mensaje = ois.readObject();
Para leer el mensaje, creamos un ObjectInputStream con el socket y leemos de él.
Comprobamos que es el mensaje de petición de fichero. Si es así, abrimos el fichero y empezamos a enviarlo.
if (mensaje instanceof
MensajeDameFichero)
{
enviaFichero(
((MensajeDameFichero)
mensaje).nombreFichero,
new
ObjectOutputStream(cliente.getOutputStream()));
}
Lo hemos hecho llamando a otro método de la clase Servidor, al que le pasamos el nombre del fichero y el ObjectOutpuStream, que creamos sobre la marcha, por el que tiene que enviar el fichero.
Dentro del método, abrimos el fichero y en un bucle vamos leyendo del fichero y enviando distintos mensajes MensajeTomaFichero.
// Una variable auxiliar
para marcar cuando se envía el último mensaje
boolean enviadoUltimo=false;
// Se abre el fichero.
FileInputStream fis = new FileInputStream(fichero);
// Se instancia y rellena un mensaje
de envio de fichero
MensajeTomaFichero mensaje = new MensajeTomaFichero();
mensaje.nombreFichero = fichero;
// Se leen los primeros bytes del fichero
en un campo del mensaje
int leidos = fis.read(mensaje.contenidoFichero);
// Bucle mientras se vayan leyendo datos
del fichero
while (leidos > -1)
{
// Se rellena el
número de bytes leidos
mensaje.bytesValidos = leidos;
// Si no se han leido
el máximo de bytes, es porque el fichero
// se ha acabado y este es el último mensaje
if (leidos < MensajeTomaFichero.LONGITUD_MAXIMA)
{
//
Se marca que este es el último mensaje
mensaje.ultimoMensaje = true;
enviadoUltimo=true;
}
else
mensaje.ultimoMensaje =
false;
// Se envía
por el socket
oos.writeObject(mensaje);
// Si es el último
mensaje, salimos del bucle.
if (mensaje.ultimoMensaje)
break;
// Se crea un nuevo
mensaje
mensaje = new MensajeTomaFichero();
mensaje.nombreFichero = fichero;
// y se leen sus
bytes.
leidos = fis.read(mensaje.contenidoFichero);
}
// En caso de que el fichero tenga justo
un múltiplo de bytes de MensajeTomaFichero.LONGITUD_MAXIMA,
// no se habrá enviado el mensaje marcado como último.
Lo hacemos ahora.
if (enviadoUltimo==false)
{
mensaje.ultimoMensaje=true;
mensaje.bytesValidos=0;
oos.writeObject(mensaje);
}
// Se cierra el ObjectOutputStream
oos.close();
En la parte del cliente, simplemente se abre el socket, se pide el mensaje y se escribe en un fichero todo lo que venga.
Se abre el socket
Socket socket = new Socket(servidor, puerto);
Se crea un ObjectOutputStream y se envía un MensajeDameFichero
ObjectOutputStream
oos = new
ObjectOutputStream(socket.getOutputStream());
MensajeDameFichero mensaje = new MensajeDameFichero();
mensaje.nombreFichero = fichero;
oos.writeObject(mensaje);
Abrimos el fichero en el que vamos a ir guardando lo que nos llegue y creamos un ObjectInputStream del socket para leer los mensajes
// Puesto que servidor
y cliente van a correr en local, para evitar machacer el fichero original,
// se le añade el apellido "_copia".
FileOutputStream fos = new FileOutputStream(mensaje.nombreFichero+
"_copia");
// Se crea un ObjectInputStream del
socket para leer los mensajes que contienen el fichero.
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
En un bucle hasta recibir el mensaje marcado como último, leemos mensajes y los escribimos en fichero
do
{
// Se lee el mensaje
en una variabla auxiliar
Object mensajeAux = ois.readObject();
// Si es del tipo
esperado, se trata
if (mensajeAux instanceof MensajeTomaFichero)
{
mensajeRecibido =
(MensajeTomaFichero) mensajeAux;
//
Se escribe en pantalla y en el fichero
System.out.print(new String(
mensajeRecibido.contenidoFichero, 0,
mensajeRecibido.bytesValidos));
fos.write(mensajeRecibido.contenidoFichero, 0,
mensajeRecibido.bytesValidos);
}
else
{
// Si no es del tipo esperado, se marca
error y se termina
// el bucle
System.err.println("Mensaje no esperado "
+ mensajeAux.getClass().getName());
break;
}
} while (!mensajeRecibido.ultimoMensaje);
Esto es todo. En fuentes.zip tienes los fuentes completos. Solo tienes que desempaquetarlos, compilarlos y ejecutar los main() de las clases ServidorFichero para el servidor y ClienteFichero para el cliente. Si no tocas las fuentes, transmitirán un fichero "d:\hola.txt", El cliente lo recibirá y lo guardará en "d:\hola.txt_copia". Puedes cambiarlo para que sea el fichero que quieras. Si vas a hacerlo con un fichero binario, quita del ClienteFichero.java las líneas que sacan el fichero por pantalla.