PDA

View Full Version : [JAVA]Trasferire file binari attraverso socket


alby89
15-06-2009, 19:25
Ciao a tutti, devo fare un piccolo programma per trasferire dei file tra 2 computer.
questo è il codice del client:

Socket socketDelClient=new Socket(ip,6789);

FileInputStream in = new FileInputStream("img.jpg");

BufferedInputStream inB=new BufferedInputStream(in);

DataOutputStream outputAlServer=new DataOutputStream(socketDelClient.getOutputStream());

byte[] vett=new byte[20];

while((inB.read(vett,0,20))!= -1)
{
outputAlServer.write(vett,0,20);
}

inB.close();
socketDelClient.close();

il programma copia i file, solo che le dimensioni non combaciano, e nel caso di una canzone ogni tanto si sentono dei disturbi...
devo modificare la dimensione del buffere?se si a quanto?

PGI-Bis
15-06-2009, 20:19
read non legge necessariamente 20 byte. In più se le dimensioni del file non sono un multiplo di 20 il file di destinazione si trova necessariamente con della monnezza in coda.

Se usi un BufferedStream puoi tranquillamente leggere un byte alla volta, ci pensa lui a creare e gestire un array di byte. E' più corta che dover star lì a quantificare il numero di byte letti, inserire gli indici affinchè venga scritta solo la porzione dell'array di byte effettivamente riempita eccetera eccetera.

FileInputStream fin = new FileInputStream(il file);
BufferedInputStream in = new BufferedInputStream(fin);
BufferedOutputStream out = new BufferedOutputStream(client.getOutputStream());
for(int b = in.read() ; b != -1; b = in.read()) out.write(b);

Sarebbe forse meglio mandare il numero di byte che saranno trasmessi prima del file vero e proprio, in modo da dare un'idea all'altro capo della connessione di quanta roba stia arrivando.

Il tutto viene molto meglio coi canali che con i flussi. A occhio:

FileChannel in = new FileInputStream(il file).getChannel();
WritableByteChannel out = Channels.newChannel(socket.getOutputStream());
ByteBuffer size = ByteBuffer.allocate(8);
size.putLong(in.size()).flip();
ByteBuffer data = ByteBuffer.allocate((int)in.size());
while(data.hasRemaining()) in.read(data);
data.flip();
while(size.hasRemaining()) out.write(size);
while(data.hasRemaining()) out.write(data);

alby89
15-06-2009, 20:50
grazie per la risposta.
avevo pensato anch'io alla monnezza in coda, solo che se leggo un byte alla volta il trasferimento diventa molto lento.
Non esiste un "algoritmo" per poter velocizzare il trasferimento e allo stesso tempo non creare monnezza alla fine del file?

PGI-Bis
15-06-2009, 20:56
Se il flusso è di tipo Buffered il trasferimento per byte non causa rallentamenti dovuti al fatto che mandi-leggi un byte alla volta perchè dietro le quinte lui li accumula e ne manda-legge a blocchi.

Assicurati che siano dotati di buffer sia il flusso in lettura dal file che il flusso in scrittura sul socket.

alby89
15-06-2009, 21:21
Se il flusso è di tipo Buffered il trasferimento per byte non causa rallentamenti dovuti al fatto che mandi-leggi un byte alla volta perchè dietro le quinte lui li accumula e ne manda-legge a blocchi.

Assicurati che siano dotati di buffer sia il flusso in lettura dal file che il flusso in scrittura sul socket.
eppure sono buffer e se metto cm n byte 1 ci mette 30 sec circa per 3 mb, mentre se metto n=20 ci mette 1-2 sec..

PGI-Bis
15-06-2009, 21:49
E' strano perchè è come dire che il BufferedXYZStream in verità non ha un buffer. Non è che scrivi anche un byte alla volta sul file in ricezione?

Comunque sono questioni scolastiche: coi canali il "raw input" si fa molto meglio e la bufferizzazione - termine orrendo - è precotta. Usa i canali.

FileChannel in = new FileInputStream(il file).getChannel();

per aprire un canale sul file. Per leggere i dati da un canale usi un Buffer, nella fattispecie un ByteBuffer. I Buffer hanno la meravigliosa proprietà di sapere da soli quanti byte sono stati inseriti, fin dove devano essere letti e cose così. In pratica dato il canale "in" se il file non ha dimensioni enormi puoi creare un Buffer che contenga tutto quanto il file:

ByteBuffer buffer = ByteBuffer.allocate((int)in.size());

Poi riempi il buffer dal canale:

while(buffer.hasRemaining()) in.read(buffer);

la "read" legge quanti più byte possibile e siccome il buffer ha le stesse dimensioni del file sottostante sappiamo che il file è stato completamente letto quando il buffer non ha più spazio rimanente.

Una volta riempito buffer lo "riavvolgi" (cioè lo prepari ad un nuovo uso):

buffer.flip() oppure buffer.rewind()

A questo punto puoi scaricarlo in un canale in uscita. Per un socket "normale" ottieni un canale in uscita con:

WritableByteChannel out = Channels.newChannel(socket.getOutputStream());

Per rendere più breve l'operazione di lettura dall'altra parte del socket puoi inviare prima dei dati del file la lunghezza del file stesso, cioè il numero di byte che compongono il file. Pigli la dimension del file:

long size = in.size();

La incapsuli in un ByteBuffer:

ByteBuffer sizeBuffer = ByteBuffer.allocate(8);
sizeBuffer.putLong(size).flip();

e spari il buffer nel canale in uscita del socket:

while(sizeBuffer.hasRemaining()) out.write(sizeBuffer);

La scrittura del file è come la lettura: finchè il buffer ha dei byte non ancora esaminati dal canale, lo passi al canale:

while(buffer.hasRemaining()) out.write(buffer);

Anche qui il ciclo tenta di inviare il numero più alto possibile di byte in un blocco solo. Se non ci riesce ritenta al passaggio successivo finchè tutti i byte non sono stati inviati.

Lato client fai una cosa simile. Prendi il socket e crei un canale in entrata:

ReadableByteChannel in = Channels.newChannel(socket.getInputStream());

Sai di aver mandato prima di tutto la dimensione del file in un long, che sono 8 byte. Quindi leggi 8 byte:

ByteBuffer sizeBuffer = ByteBuffer.allocate(8);
while(sizeBuffer.hasRemaining()) in.read(sizeBuffer);

dopo riavvolgi il buffer:

sizeBuffer.rewind();

e ottieni la dimensione con getLong():

long size = sizeBuffer.getLong();

A questo punto sai quanti byte devi leggere. Puoi creare un buffer per contenerli tutti:

ByteBuffer data = ByteBuffer.allocate((int)size);

e riempirlo dal socket:

while(data.hasRemaining()) in.read(data);

Anche qui legge il numero massimo possibile di byte in una volta sola, gestisce da solo indici e cursori e compagnia bella.

Una volta riempito il buffer con i dati del file puoi riavvolgerlo:

data.rewind();

e spararlo in un file:

FileChannel out = new FileOutputStream(un file).getChannel();
while(data.hasRemaining()) out.write(data);

e hai fatto, con il tuo bel buffering, al top della banda disponibile, senza dover star lì a contare.

alby89
15-06-2009, 22:42
grazie mille per la spiegazione:D