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); } } }
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:
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!!!
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
ResponderEliminarDe nada! que bueno que te haya servido!!
Eliminar