Newsgroup: it.comp.java
Titolo: La fine dello stream non arriva mai!!!

Ho spedito una richiesta di aiuto praticamente identica qualche tempo
fa.
Il problema si è ripresentato, e non riesco né ad aggirarlo né a
risolverlo.
Si tratta di un programma client/server (un po' di codice è alla fine
del messaggio): il client si collega al server e richiede un file
tramite una stringa. Il file si trova in una directory accessibile al
server, che lo serializza su uno stream e lo invia al client (vedi
metodo spedisci() del codice lato server). Il problema è che il client
si blocca sulla for del
metodo ricevi(String richiesta), perché la variabile di controllo (i)
non diventa mai uguale a -1! Eppure BufferedInputStream.read(byte[]
buffer) dovrebbe restituire -1 se lo stream è finito, e dal lato
server è finito, visto che non arriva più niente, o meglio, continuano
ad arrivare zeri! Dall'altra parte però il codice va avanti; infatti
il metodo spedisci() di Server termina normalmente, e restituisce il
controllo al main, che essendo in un ciclo
infinito, richiama un'altra volta spedisci() per accettare richieste
di un altro file: siccome però Client sta ancora aspettando che i
diventi uguale a -1, il server si blocca su br.readLine().
Comunque il primo file della serie viene scritto correttamento sul
disco dal lato client.
Mi sono accorto che se dopo la for del metodo spedisci() sul server,
chiudo lo stream di output con il metodo close(), il client si
sblocca. Però, come dice lo stesso javadoc, close() rilascia tutte le
risorse associate allo stream, quindi anche il socket, e dato che sto
usando un socket SSL fornito da un´ altra classe, non posso
scollegarmi e ricollegarmi ogni volta!
Le ho provate tutte, compresi altri tipi di stream, ma il risultato
migliore cho sono riuscito ad ottenere è la copia del primo file e il
ciclo infinito nella for del client.
Ora non so veramente più che fare. Qualcuno saprebbe aiutarmi?
Il codice allegato è simile nelle parti principali a quello che sto
scrivendo.
Grazie per l'attenzione, ciao!
GePs
//Lato client:
public class Client() {
  //...
  public void apriCollegamento() {
    // apertura Socket...
    Socket s...;
    inputBuffer = new BufferedInputStream(s.getInputStream());
    pw = new PrintWriter(new
BufferedOutputStream(s.getOutputStream()));
    //...
  }
  public void ricevi(String richiesta) {
    pw.println(richiesta);
    pw.flush();
    FileOutputStream fos = new FileOutputStream(richiesta);
    byte[] buffer = new byte[512];
    for (int i = inputBuffer.read(buffer);
             i != -1;
             i = inputBuffer.read(buffer)) {
      os.write(buffer, 0, i);
    }
    // QUI NON CI ARRIVO MAI!
    fos.close();
    System.out.println("ricevuto");
  }
  public static void main(String args[0]) {
    apriCollegamento();
    ricevi("nomePrimoFile");
    ricevi("nomeSecondoFile");
    ricevi("nomeTerzoFile");
  }
}
//Lato server:
public class Server() {
  //...
  public void apriServer() {
    //il Socket lo ottengo da un´altra classe
    Socket s = miaClasse.getSocket();
    outputBuffer = new BufferedOutputStream(sock.getOutputStream());
    br = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
  }
  public void spedisci() {
    String nomeFileRichiesto = br.readLine();
    FileInputStream fis = new FileInputStream(nomeFileRichiesto);
    byte[] buffer = new byte[512];
    for (int i = fis.read(buffer);
             i != -1;
             i = fis.read(buffer)) {
      outputBuffer.write(buffer, 0, i);
    }
    outputBuffer.flush();
    /*
      Se qui faccio outputBuffer.close() il Client si sblocca, ma
chiudo anche il socket dal
      lato del server, e quindi le chiamate successive del metodo
ricevi del client NON
      funzioneranno, perchè non posso riconnettermi!!!
     */
    fis.close();
    System.out.println("File " + nomeFileRichiesto + " spedito");
  }
  public static void main(String args[0]) {
    apriServer();
    while (true) {
      spedisci();
    }
  }
}

Ciao, GePs!
Il giorno 01/03/2004 alle ore 10:18 hai scritto:
 G> metodo ricevi(String richiesta), perché la variabile di controllo (i)
 G> non diventa mai uguale a -1! Eppure BufferedInputStream.read(byte[]
 G> buffer) dovrebbe restituire -1 se lo stream è finito, e dal lato
 G> server è finito, visto che non arriva più niente, o meglio, continuano
 G> ad arrivare zeri!
Usando un socket non c'e' modo di sapere se il file dall'altra parte e'
finito o no, se non implementi una segnaletica opportuna. Se dal server non
arrivano bytes puo' essere perche' effettivamente il file e' finito oppure
perche' c'e' una congestione, temporanea, nella rete.
Un modo per superare questo problema e' inserire il numero di bytes da
leggere all'inizio del trasferimento, cosi' saprai per certo quanti byte
trasferire e una volta raggiunto questo numero il file sara' sicuramente
terminato.
 G> Però, come dice lo stesso javadoc, close() rilascia tutte le risorse
 G> associate allo stream, quindi anche il socket, e dato che sto usando un
 G> socket SSL fornito da un´ altra classe, non posso scollegarmi e
 G> ricollegarmi ogni volta!
A parte che puoi ricollegarti quante volte vuoi (i browser web in fondo
funzionano proprio cosi') a maggior ragione, se uno stesso socket deve
gestire piu' richieste, devi per forza inserire un sistema di controllo,
altrimenti come fai a discriminare tra una richiesta e l'altra ?
Bye
-- 
Marco Maccaferri
MaccaSoft - 
http://www.maccasoft.com

On 1 Mar 2004 01:18:37 -0800, GePs wrote:
[CUT]
Dato che non puoi chiudere lo stream, e non puoi nemmeno utilizzare un
particolare byte come "tag" di fine file, perchè non provi a trasmettere al
client la dimensione del file prima del file stesso?
Proviamo a riordinare... mancano però ancora un sacco di cose: dovresti
controllare che il file esista davvero sul server, e dovresti anche prendere
in considerazione il fatto che la rete può cadere, o comunque che il socket
potrebbe essere chiuso in ogni momento per i più svariati motivi...
//Lato server:
public class Server() {
  //...
  public void apriServer() {
    //il Socket lo ottengo da un´altra classe
    Socket s = miaClasse.getSocket();
    outputBuffer = new BufferedOutputStream(sock.getOutputStream());
    br = new BufferedReader(new
InputStreamReader(sock.getInputStream()));
  }
  public void spedisci() {
    String nomeFileRichiesto = br.readLine();
//--------------------------------------------------AGGIUNTA DI [Ech3L0n]
    File filerichiesto=new File(nomeFileRichiesto);
    //perchè non controllare anche che il file esista?
    String dimesionefile=Long.toString(filerichiesto.length());
    //trasmettiamo la dimensione del file carattere per carattere
    //terminiamo trasmettendo un singolo byte 0x00 senza chiudere lo stream
    for(int i=0;i<dimensionefile.length();i++)
       outputBuffer.write(dimensionefile.charAt(i));
    outputBuffer.write(0x00);
//--------------------------------------------------FINE AGGIUNTA
    FileInputStream fis = new FileInputStream(nomeFileRichiesto);
    byte[] buffer = new byte[512];
    for (int i = fis.read(buffer);
             i != -1;
             i = fis.read(buffer)) {
      outputBuffer.write(buffer, 0, i);
    }
    outputBuffer.flush();
    fis.close();
    System.out.println("File " + nomeFileRichiesto + " spedito");
  }
  public static void main(String args[0]) {
    apriServer();
    while (true) {
      spedisci();
    }
  }
}
//Lato client:
public class Client() {
  //...
  public void apriCollegamento() {
    // apertura Socket...
    Socket s...;
    inputBuffer = new BufferedInputStream(s.getInputStream());
    pw = new PrintWriter(new
BufferedOutputStream(s.getOutputStream()));
    //...
  }
  public void ricevi(String richiesta) {
    pw.println(richiesta);
    pw.flush();
//--------------------------------------------------AGGIUNTA DI [Ech3L0n]
    StringBuffer dimensionestr=new StringBuffer();
    int rd=inputBuffer.read();
    while (rd!=0) {
       dimensionestr.append((char)rd);
       rd=inputBuffer.read());
    }
    long dimensione=Long.parseLong(dimensionestr);
//--------------------------------------------------FINE AGGIUNTA
    FileOutputStream fos = new FileOutputStream(richiesta);
    //questo è meglio evitarlo... la "read" standard di BufferedInputStream
    //è bloccante e usa la chiusura dello stream come segnale per sbloccarsi
    //byte[] buffer = new byte[512];
    //for (int i = inputBuffer.read(buffer);
    //         i != -1;
    //         i = inputBuffer.read(buffer)) {
    //  os.write(buffer, 0, i);
    //}
//--------------------------------------------------AGGIUNTA DI [Ech3L0n]
    //il nuovo loop...
    for (long l=0;l<dimensione;l++) {
       rd=inputBuffer.read());
       fos.write(rd);
    }
    //è il server a dover garantire la trasmissione di tutti i byte
//--------------------------------------------------FINE AGGIUNTA
    fos.close();
    System.out.println("ricevuto");
  }
  public static void main(String args[0]) {
    apriCollegamento();
    ricevi("nomePrimoFile");
    ricevi("nomeSecondoFile");
    ricevi("nomeTerzoFile");
  }
}
-- 
Fermiamo Scientology prima che sia troppo tardi!!!
http://xenu.com-it.net
 - 
http://xenu.freewinds.cx/
(per rispondere personalmente leva IlDitoDalCulo)

 altrimenti come fai a discriminare tra una richiesta e l'altra ?
Volevo evitare di ricollegarmi sempre, perchč il socket č SSL, e
questo credo implichi l'handshake completo. Ma se questa č la prassi
credo che lo farň
Grazie sei stao molto gentile e preciso.

 
Sì sì, sono tutti "dettgli" che sul codice non avevo scritto per
evitare di postare un mega di messaggio :)
Grazie per il codice, ora lo studio e poi ti faccio sapere! Ciao!

Ciao, GePs!
Il giorno 02/03/2004 alle ore 10:30 hai scritto:
 G> Ma a questo non dovrebbero pensare i layer sottostanti? Se bufferizzo
 G> i flussi la cosa non dovrebbe essere automatica?
Non con i socket standard. Se poi una classe implementa un sistema di
controllo di quel genere e' un altro discorso. Il protocollo HTTP ad esempio
prevede l'header Content-length che indica appunto la lunghezza del file che
sta trasferendo, se un browser supporta l'HTTP 1.1 puo' inviare richieste
multiple in una stessa connessione e sapra' esattamente quando inizia e
quando finisce ogni file.
L'FTP standard di contro non prevede nessun meccanismo di controllo sulla
dimensione del file. Per lui un file transfer termina quando il socket che
trasferisce il file viene chiuso. I programmi piu' evoluti "catturano" la
dimensione del file dalle risposte del server e ti fanno vedere le barre
progressive o ti avvertono se il trasferimento viene interrotto prima del
previsto, ma il protocollo non prevede nessun tipo di controllo.
 G> E' una delle due soluzioni alle quali avevo pensato. L'altra è fare
 G> anche il client multithread e lanciare un thread per ogni richiesta di
 G> file, con le opportune sincronizzazioni. Che te ne pare?
Non risolvi niente. Il problema e' alla base, come fai a sapere che un file
e' finito ? Anche con 100 thread non lo saprai mai finche' non metti
qualcosa che indichi la fine.
 G> Volevo evitare di ricollegarmi sempre, perchè il socket è SSL, e
 G> questo credo implichi l'handshake completo. Ma se questa è la prassi
 G> credo che lo farò
E' la prassi nei browser, tu puoi fare quello che vuoi chiaramente. Ma il
discorso di base non cambia.


 Ciao, GePs!
[CUT]
Grazie per i chiarimenti: ho risolto il problema e iniziato a capire
meglio il funzionamento dei socket. E' proprio vero che se non ci
sbatti la testa certe cose non le impari mai!
Ciao!

 On 1 Mar 2004 01:18:37 -0800, GePs wrote:
 
[CUT]
 
Grazie Ech3L0n, il tuo codice mi è stato molto utile!