Tutorial: TCP Copiar archivos de Cliente a Servidor con Java



Hola! En este tutorial les presento un ejemplo de como transmitir archivos de un cliente a un servidor usando una conexion TCP. Para este ejemplo usare como base el codigo de un tutorial previo (Aplicacion simple TCP Cliente - Servidor en Java).

El siguiente programa tomara todos los archivos de una carpeta y los copiara en el servidor, el unico requerimiento es tener la libreria "json-simple-1.1.1.jar".  Comencemos!

Para el cliente crearemos la clase "TCPClient" con los siguientes atributos y constructor:

public class TCPClient {
    
    
    private DataInputStream in;
    private DataOutputStream out;
    private InetAddress ip;
    private int port;
    private Socket socket;
   
    public TCPClient(InetAddress ip, int port) {             
            this.ip = ip;
            this.port = port;
       try {
           //Creamos el socket y los streams de input y output
            this.socket = new Socket(ip, port);                             
            this.in = new DataInputStream(socket.getInputStream());
            this.out = new DataOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
}



En la clase TCPClient tenemos lo basico para una conexion la ip, el puerto y el socket que crearemos al crear el objeto, esto lo podrias dejar en una clase independiente, pero a modo de ejemplo pondremos el main aqui mismo:


public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
  // Connect to local socket on port 4444      
  TCPClient tcp = new TCPClient(InetAddress.getByName("localhost"),4444);
  File folder = new File(args[0]);
  //Barremos todo los archivos del directorio
  for (File fileEntry : folder.listFiles()) {
             if (!fileEntry.isDirectory()) {
                 tcp.sendFile(fileEntry);
             } 
         } 
 }
En el main, creamos un objeto tipo TCPClient, y le mandaremos los atributos necesarios, para este ejemplo nos conectaremos al localhost con el puerto 4444. Luego por parametros enviaremos el directorio que queremos copiar al servidor, y recorreremos todo el directorio solo cogiendo archivos y llamamos al metodo "SendFile", que lo detallo a continuacion:


private void sendFile(File file) {         
         try {             
             // Crea un byte array para guardar el archivo
             byte[] sendData = new byte[(int)file.length()];
             
             // Leo el archivo
             FileInputStream fileReader = new FileInputStream(file);
             fileReader.read(sendData);
             //Cierro el File Input String
             fileReader.close();
             //Creo un mensaje Json para enviar atributos del archivo el nombre y el tamano, esto me servira 
             //del lado del servidor
             Map<String, String> jsonObj = new LinkedHashMap<String,String >();
             jsonObj.put("fileLength", String.valueOf(file.length()));
             jsonObj.put("fileName", file.getName());          
             //Envio el mensaje Json al servidor por el output stream
                 this.out.writeUTF(JSONValue.toJSONString(jsonObj));    
                                                    
                 // Envio el arreglo de bytes al servidor
                 this.out.write(sendData, 0, sendData.length);
                 //Me aseguro que se envien inmediatamente las dos cosas anteriores
                 this.out.flush();
                 //Recibo la contestacion del servidor si la copia fue exitosa o fallo
                 System.out.println(this.in.readUTF());
         } catch(IOException e) {
          // TODO Auto-generated catch block
             e.printStackTrace();
         }

     }

En este metodo hacemos el envio del archivo a traves de un arreglo de bytes y un mensaje Json que contiene los atributos del archivo que estamos enviando. Y eso es todo del lado del cliente!

Para el servidor crearemos la clase TCPEchoServer, detallada a continuacion:


public class TCPEchoServer {

 public static void main(String[] args) throws IOException {
  // Abre un server socket para servir a los clientes por el puerto 4444
  try (ServerSocket server = new ServerSocket(4444)) {
   // En un loop infinito se mantiene aceptando clientes
   while(true){
    
    Socket socket = server.accept();
    
    // Comienza una nueva thread para el cliente
    Thread t = new Thread(() -> serveClient(socket));
    //t.setDaemon(true); - If you wish the thread to be daemon
    t.start();
   }
  }
 }

 /**
  * Serves a single client - identified by the socket.
  * @param socket
  */
 private static void serveClient(Socket socket) {
  try (Socket clientSocket = socket) {
   // Get the in/out streams of the sockets
   DataInputStream in = new DataInputStream(clientSocket.getInputStream());
   DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());   
   
   // En loop infinito recibe mensajes de un cliente especifico
   while(true) {
    
    String message = in.readUTF();
    System.out.println(message);
    if (receiveFile(message, in)) { 
    // Bloquea hasta que el mensaje haya sido enviado
    out.writeUTF(String.format("Copia Exitosa: '%s'\n", message));
    }
    else
    {
      out.writeUTF(String.format("Fallo: '%s'\n", message));                     
    }
    out.flush();
   }
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 
  private static boolean receiveFile(String fileHeader, DataInputStream in)  {
         Path filename = null;
         try {
             //Parsea el mensaje recibido para obtener el nombre y la longitud
             JSONParser parserj = new JSONParser();
             JSONObject obj = (JSONObject) parserj.parse(fileHeader);

             //Crea un array de bytes con la longitud recibida para almacenar el archivo
             byte[] receivedData = new byte[Integer.valueOf(obj.get("fileLength").toString())];
             
            //Crea un archivo con el filename recibido            
             filename = Paths.get(obj.get("fileName").toString());
             
             FileOutputStream fos = new FileOutputStream(filename.toFile());

             //Carga la data recibida a traves del InputStream, en el FileOutputStream a traves de un while
             int count=0;           
             long size = Integer.valueOf(obj.get("fileLength").toString());   

             while (size > 0 && (count = in.read(receivedData, 0, (int)Math.min(receivedData.length, size))) != -1)   
             {   
                 fos.write(receivedData, 0, count);   
                 size -= count;   
             }
             
             fos.close();

             System.out.printf("Archivo Recibido '%s'\n", filename.toString());
             return true;

         } catch (IOException | ParseException e) {
             e.printStackTrace();
             return false;
         }
           
         
     }
     
}



El metodo main y ServeClient, han sido explicados en el tutorial previo que he detallado al principio. Para la copia del archivo tenemos la funcion ReceiveFile que es la que recibira primero el mensaje Json y lo parseara para obtener los atributos que enviamos de nombre y tamano. Luego creara un archivo nuevo y leera el array de bytes recibido y lo copiara a un FileOutputStream como ha sido detallado en los comentarios.

Del lado del cliente tendran un resultado como el siguiente:

Tcp copiar archivos cliente servidor
Agregar leyenda


Espero que les sea de utilidad este ejemplo, recuerden que esto es un ejemplo, para archivos muy grandes corren el riesgo que se congestione la conexion, y lo ideal en esos casos seria enviarlos en pedazos. Tambien algo que podria ser agregado aqui seria el uso de una conexion SSL, que seria bastante sencillo de hacer, y si alguien lo requiere por favor sugieralo en los comentarios!!

Otra forma de enviar archivos podria hacer a traves de clases serializables, que lo dejare para otro tutorial!

Q tengan buen dia y hasta la proxima!!!


Comentarios

  1. Muchísimas gracias por este artículo, llevada días sin conseguir que el proceso cliente terminase de leer el archivo recibido. No entiendo por qué pero al parecer el resultado del método read(), jamás valía -1 =S

    ResponderEliminar

Publicar un comentario