Qualche tempo fa mi e’ capitato di riprendere questo discorso leggendo e rispondendo ad un post sul newsgroup it.comp.java cosi ho deciso di scrivere questa micro guida che spiega molto velocemente come fare a trasferire un file da un pc ad un altro con il semplice utilizzo delle socket.
Ci sono molti altri modi per fare questo tipo di operazione oltre a tutti gli accorgimenti relativi alla sicurezza che qui non trattero’, ma, per chi si trova in questa situazione per la prima volta, potrebbe essere una buona traccia per iniziare.

Livello:
Medio, Facile

Per chi:
Chiunque sia alle prime armi con l’utilizzo di thread e socket

Scenario:
Trasferire file via socket client/server

Iniziamo con lo sviluppo del server che rimarra’ in attesa di nuove connessioni e file da ricevere, prima di tutto creiamo l’oggetto che si occupa, per ogni connessione, di ricevere e salvare il file, questo oggetto implemeta l’interfaccia Runnable cosi da poter gestire con i thread l’invio simultaneo di piu’ file e di conseguenza potrli salvare contemporaneamente senza dover aspettare che il primo arrivato finisca

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
 
public class ReceiverManager implements Runnable {
 
  private final String SAVE_DIR = "/home/crx/uploads";
 
  private Socket socket;
 
  public ReceiverManager(Socket socket) {
      this.socket = socket;
  }
 
  public void run() {
      try {
          System.out.println("presa in carico nuova connessione da " + socket);
 
          // intercetto il file in arrivo
          ObjectInputStream oin = new ObjectInputStream(socket.getInputStream());
 
          // eseguo un cast dell' oggetto come file
          File inFile = (File) oin.readObject();
 
          // imposto il nuovo file che dovro' salvare
          // prendendone il nome originale
          File saveFile = new File(SAVE_DIR + "/" + inFile.getName());
 
          // salvo il file
          save(inFile, saveFile);
 
      } catch (Exception e) {
          e.printStackTrace();
      } finally {
          try {
              socket.close();
          } catch (IOException e) { }
      }
  }
 
  /**
   * Esegue il salvattaggio 
   *
   * @param in
   * @param out
   * @throws IOException
   */
  private void save(File in, File out) throws IOException {
      System.out.println(" --ricezione file " + in.getName());
      System.out.println(" --dimensione file " + in.length());
 
      // apro uno stream sul file che e' stato inviato
      FileInputStream fis  = new FileInputStream(in);
      // scrivo uno stram per il salvataccio del nuovo file
      FileOutputStream fos = new FileOutputStream(out);
 
      byte[] buf = new byte[1024];
      int i = 0;
      // riga per riga leggo il file originale per 
      // scriverlo nello stram del file destinazione
      while((i=fis.read(buf))!=-1) {
          fos.write(buf, 0, i);
      }
      // chiudo gli strams
      fis.close();
      fos.close();
 
      System.out.println(" --ricezione completata");
  }
}

Ora creiamo il server vero e proprio che rimane in attesa di connessioni e, nel caso in cui qualche client si collegasse, provvedera’ ad instanziare ed eseguire in un thread un nuovo ReceiverManager per ogni nuova connessione.

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 
 
public class Server {
 
  private final int SERVER_PORT = 9977;
 
  public static void main(String[] args) {
      Server s = new Server();
      try {
          s.listen();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
 
  private void listen() throws IOException {
      ServerSocket ss = new ServerSocket(SERVER_PORT);
      System.out.println("Sono sulla " + ss);
 
      // ciclo infinito per accettare per sempre connessioni
      for (;;) {
          // prendo la connessione in ingresso
          Socket s = ss.accept();
          System.out.println("Conessione da " + s);
 
          // creo ed eseguo il thread per questa connessione 
          // cosi il ciclo continua e rimane in attesa di 
          // nuove connessioni
          new ReceiverManager(s).run();
 
          // faccio respirare un po il ciclo
          try {
              Thread.sleep(100);
          } catch (InterruptedException e) { }
      }
  }
}

Perfetto, ora abbiamo il server pronto a ricevere e salvare file provenienti anche da piu’ connessioni contemporanee, non rimane che creare il client che si occupera dell’invio vero e proprio di un file.

import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class Client {
 
  private final String SERVER_HOST = "127.0.0.1";
  private final int SERVER_PORT = 9977;
 
  public static void main(String[] args) {
      Client c = new Client();
      try {
          c.sendFile(new File(args[0]));
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
 
  private void sendFile(File file) throws IOException {
      Socket socket = null;
      try {
          socket = new Socket(SERVER_HOST, SERVER_PORT);
      } catch (UnknownHostException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
 
      ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
      oos.reset();
      oos.writeObject(file);
      oos.flush();
      oos.close();
  }
}

Semplice vero?
Ora non ci resta che provare il tutto per verificare il corretto funzionamento, partiamo con l’eseguire il server:

$ java Server
Sono sulla ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=9977]

e successivamente il client passando compe parametro il file che vogliamo inviare

java Client /home/crx/wireless.log

ed ecco sull’output del server cosa avremo:

$ java sc/Server
Sono sulla ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=9977]
Conessione da Socket[addr=/127.0.0.1,port=33113,localport=9977]
presa in carico nuova connessione da Socket[addr=/127.0.0.1,port=33113,localport=9977]
 --ricezione file wireless.log
 --dimensione file 50223
 --ricezione completata

Ora analizziamo velocemente alcuni degli oggetti principali utilizzati in questo esempio:

  • Socket e’ il punto finale della comunicazione tra due pc
  • ServerSocket e’ l’oggetto che rimane in attesa di nuove connessioni, le gestisce e quando possibile restituisce il risultato al client
  • ObjectInputStream e’ un oggetto utilizzato per deserializzare oggetti precedentemente serializzati siano essi tipi primitivi o oggetti
  • ObjectOutputStream al contrario di ObjectInputStream si occupa di serializzare oggetti e tipi primitivi, solo oggetti che implementano l’interfaccia java.io.Serializable possono essere utilizzati in questo oggettoUna volta scritto l’oggetto nel metodo writeObject() e’ necessario eseguire il metodo flush() per assicurarsi che possa poi essere letto correttamente, senza rimaner bloccato, dal “ricevente”

Al solito spero di essere stato d’aiuto.

Ciao e alla prossima,
Cristian.