SocketChannel ByteBuffer Bilder verschicken

BaluEtDespi

Mitglied
Hallo an alle!
Ich habe ein Problem mit dem Senden von Bildern mithilfe von SocketChannels und ByteBuffern.
Da viele Bilder verschickt werden sollen, muss ich auf ein simples Protokoll zurückgreifen, das wie folgt funktionieren soll:
In ein zuvor gecleartes writeBuffer (ByteBuffer) füge ich die Größe der zu übertragenden Bilddatei ein: ByteBuffer.putLong();
Dann folgen die Daten des Bildes, die ebenfalls in das Buffer eingefügt werden. Dann wird geflippt und das writeBuffer über den SocketChannel gesendet.
Auf der Empfängerseite wird immer erst das readBuffer (ByteBuffer) gecleared. Dann wird zunächst die Länge der Bilddatei ausgelesen (ByteBuffer.getLong();). Dann wird das Limit des readBuffers auf DATEILAENGE+8 gesetzt schließlich das readBuffer in einen FileChannel gelesen. Nach diesem Vorgang wird das readBuffer wieder gecleared und auf ein weiteres Bild gewartet.
Nun das Problem: Bei der ersten Übertragung läuft alles einwandfrei. Doch beim zweiten Eintreffen von Daten im readBuffer ergibt ByteBuffer.getLong() plötzlich völlig absurde Werte, z.B. Server erwartet ein Bild der Groesse: 3927928765588602075.
Als Quelltext sieht das ganze wie folgt aus:
Senderseite:
Java:
Path path = Paths.get(fileName);
            long fileSize = Files.size(path);
            System.out.println(fileName + " size: " + fileSize);
            socketChannelEntity.getWriteBuffer().putLong(fileSize);
            try{
                FileChannel fileChannel = FileChannel.open(path);
                int numRead = 0;
                int counter = 0;
               
                while((numRead = fileChannel.read(socketChannelEntity.getWriteBuffer())) > 0){
                    counter += numRead;
                    socketChannelEntity.getWriteBuffer().flip();
                    do {
                        numRead -= socketChannel.write(socketChannelEntity.getWriteBuffer());
                        socketChannelEntity.getWriteBuffer().clear();
                    } while (numRead > 0);
                }
                fileChannel.close();
                System.out.println("NIO_CLIENT: " + socketChannelEntity.getEntityName() + ": Image " + fileName + " sent: " + counter + " bytes long");
                socketChannelEntity.setCheckSum(fileSize);
                socketChannelEntity.setSendReceiveStatus(SendReceiveStatusClient.RECEIVE_CHECKSUM_IMAGE);
            } catch(IOException e) {
                e.printStackTrace();
            }

Empfängerseite:
Java:
Path path = Paths.get(outputFile);
            FileChannel fileChannel = FileChannel.open(path,
                    EnumSet.of(StandardOpenOption.CREATE,
                            StandardOpenOption.TRUNCATE_EXISTING,
                            StandardOpenOption.WRITE));
            int numRead = 0;
            int counter = 0;
            socketChannel.read(readBuffer);
            readBuffer.flip();
            checkSum = readBuffer.getLong();
            System.out.println("Server erwartet ein Bild der Groesse: " + checkSum);
            readBuffer.limit((int)checkSum+8);
            fileChannel.write(readBuffer);
            fileChannel.close();
            if(readBuffer.hasRemaining()){
                System.out.println("Ist noch was im ReadBuffer!");
            }
            prepareWriteBuffer(checkSum);
            System.out.println("NIO_SERVER: Received image.");
            sendReceiveStatus = SendReceiveStatusServer.SEND_CHECKSUM_IMAGE;

Hinweis: das sind jeweils die relevanten Codeausschnitte!
Ich bin für jede Hilfe dankbar!
Was mache ich falsch?
 

mihe7

Top Contributor
Was mache ich falsch?
Du ignorierst, wie viele Daten Dein Buffer tatsächlich zur Verfügung hat, indem Du das Limit auf einen "gewünschten" Wert setzt. Ich würde es mal nach dem folgendem Ansatz probieren.

Senderseite:
Java:
long size = fileChannel.size();
buf.putLong(size);
buf.flip();
do {
    fileChannel.write(buf);
while (buf.hasRemaining());

fileChannel.transferTo(0L, size, socketChannel);

Empfängerseite:
Java:
boolean expectLength = true;
long size = 0L;
while (socketChannel.read(buf) != -1) {
    if (expectLength) {
        if (buf.position() > 7) {
            buf.flip();
            size = buf.getLong();
            expectLength = false;
            buf.compact();
            fileChannel = ...
        }
    } else {
        buf.flip();
        while (size > 0 && buf.hasRemaining()) {
            size -= fileChannel.transferFrom(buf, 0, Math.min(buf.remaining(), size));
        }
        buf.compact();
        if (size == 0L) {
            fileChannel.close();
            expectLength = true;
        }
    }
}
 

BaluEtDespi

Mitglied
Danke für Deinen Code. Habe versucht, ihn umzusetzen:
Exception in thread "Thread-0" java.nio.channels.NonWritableChannelException
auf Senderseite. Des Weiteren verstehe ich nicht, warum Du nach einfügen der size den Buffer flipst, denn damit überschreibst Du doch diese Daten, wenn die Imagedaten aus dem FileChannel in den Buffer geschrieben werden!?
Und auf Senderseite schreibe ich doch nicht in den fileChannel, sondern möchte aus diesem LESEN, um die Daten dann in den Buffer zu wrappen.
Ändere ich die Operation in fileChannel.read(buf) werden die Daten übertragen. Das Problem ist nach wie vor die Empfängerseite, jedoch: FileChannel.transferFrom erwartet als erstes Argument einen ReadableByteChannel. Caste ich per Versuch einfach den ByteBuffer, ist dies natürlich nicht möglich.
Wie kann es anders funktionieren? Mir fehlen die Ideen.
 

mihe7

Top Contributor
Ach, Mist. Ich hatte das in der Nacht schon geschrieben und heute früh ungelesen reinkopiert :(

1. Sender: in der do-while-Schleife musst es natürlich socketChannel heißen
2. Empfänger: da darf es kein transferFrom stehen sondern müsste ein write sein.

Theoretisch könnte man auch im Anschluss an die while-Schleife mit fileChannel.transferFrom(socketChannel, x, size); den kompletten Rest der Datei einlesen. Aber: wozu? :)
 

BaluEtDespi

Mitglied
Hey, bin damit einen Schritt weiter gekommen. Habe bei 30 Bildern (nur noch) einen Lost von 6 Bildern, alle anderen werden richtig übertragen. Habe mehrere Versuche repliziert, immer wird ein ganzes Bild geschluckt!? Ich arbeite morgen weiter dran, dann poste ich mal meinen Code.
Schon mal vielen Dank soweit!
 

BaluEtDespi

Mitglied
Also hier der Code der Empfängerseite.
Ergebnis: Die Filesizes werden jeweils richtig gelesen. Als Output werden einige Bilder exakt gespeichert, jedoch existieren zudem noch einige Bilder der Länge 0 ?!?? Was ist falsch? Deine letzte Condition (size == 0) tritt in einigen Fällen gar nicht ein, wenn von size insgesamt mehr Zeichen in den FileChannel geschrieben wurden, da mehr erreichbar waren.

Java:
boolean expectLength = true;
            boolean first = true;
            long size = 0L;
            while (socketChannel.read(readBuffer) > 0) {
                if (expectLength) {
                    if (readBuffer.position() > 7) {
                        readBuffer.flip();
                        size = readBuffer.getLong();
                        if(first){
                            first = false;
                            checkSum = size;
                        }
                        System.out.println("Server expects image of size: " + checkSum);
                        expectLength = false;
                        readBuffer.compact();
                    }
                } else {
                    readBuffer.flip();
                    while (size > 0 && readBuffer.hasRemaining()) {
                        size -= fileChannel.write(readBuffer);
                    }
                    readBuffer.compact();
                    if (size <= 0L) {
                        fileChannel.close();
                        expectLength = true;
                        System.out.println("Bei fileChannel.close angekommen.");
                    }
                }
                readBuffer.clear();
 

mihe7

Top Contributor
Du darfst nicht den ganzen Buffer in die Datei schreiben, sondern nur den Teil, der ggf. noch bis zum Dateieende fehlt. Aus dem Grund erreichst Du manchmal auch nicht den Fall size == 0.
Java:
           size -= fileChannel.write(buf, 0, Math.min(buf.remaining(), size));
 
X

Xyz1

Gast
Ja ;) Das wäre logisch. Aber 4 KB sind kleiner als 2 MB, damit ist size überwiegend viel kleiner als remaining().
 

mihe7

Top Contributor
Argh, das wird allmählich zum Ratespiel. Wenn jetzt wieder was nicht passt, muss ich es doch mal in Code umsetzen. Eine Möglichkeit sollte sein, das Limit zu setzen:
Java:
                    while (size > 0 && readBuffer.hasRemaining()) {
                        int oldLimit = readBuffer.limit();
                        int remaining = Math.min(buf.remaining(), size);
                        readBuffer.limit(readBuffer.position() + remaining);
                        size -= fileChannel.write(readBuffer);
                        readBuffer.limit(oldLimit);
                    }
Schön ist das nicht.
 

BaluEtDespi

Mitglied
Hm, so sieht der Code insgesamt aus. Aber 0Byte-Files kommen leider immer noch vor.


Java:
boolean expectLength = true;
            boolean first = true;
            long size = 0L;
            while (socketChannel.read(readBuffer) > 0) {
                if (expectLength) {
                    if (readBuffer.position() > 7) {
                        readBuffer.flip();
                        size = readBuffer.getLong();
                        if(first){
                            first = false;
                            checkSum = size;
                        }
                        System.out.println("Server expects image of size: " + checkSum);
                        expectLength = false;
                        readBuffer.compact();
                    }
                } else {
                    readBuffer.flip();
                    while (size > 0 && readBuffer.hasRemaining()) {
                        int oldLimit = readBuffer.limit();
                        int remaining = (int) Math.min(readBuffer.remaining(), size);
                        readBuffer.limit(readBuffer.position() + remaining);
                        size -= fileChannel.write(readBuffer);
                        readBuffer.limit(oldLimit);
                    }
                    readBuffer.compact();
                    if (size <= 0L) {
                        fileChannel.close();
                        expectLength = true;
                    }
                }
            }
 

mihe7

Top Contributor
Grr...
Java:
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import static java.nio.file.StandardOpenOption.*;

public class Server {
    private SocketChannel channel;
    private ByteBuffer buf;


    public static void main(String[] args) throws Exception {
        new Server().run();
    }

    public void run() throws IOException {
        buf = ByteBuffer.allocateDirect(4096);
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress("localhost", 5000));
        channel = serverChannel.accept();
        receive();
    }

    private void receive() throws IOException {
        boolean expectLength = true;
        long size = 0L;
        FileChannel fc = null;
        int count = 0;
        while (channel.read(buf) != -1) {
            if (expectLength && buf.position() > 7) {
                buf.flip();
                size = buf.getLong();
                System.out.printf("Receiving %d bytes...\n", size);
                count++;
                Path file = Paths.get("out/file" + count + ".jpg");
                fc = FileChannel.open(file, WRITE, CREATE, TRUNCATE_EXISTING);
                expectLength = false;
                buf.compact();
            } else if (!expectLength) {
                buf.flip();
                while (size > 0L && buf.hasRemaining()) {
                    int oldLimit = buf.limit();
                    int remaining = (int) Math.min(buf.remaining(), size);
                    buf.limit(buf.position() + remaining);            
                    size -= fc.write(buf);
                    System.out.printf("%d bytes to go...\n", size);
                    buf.limit(oldLimit);
                }

                buf.compact();

                if (size == 0L) {
                    System.out.printf("done\n", size);
                    fc.close();
                    expectLength = true;
                }
            }
        }
    }
   
}

Java:
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;

public class Client implements AutoCloseable {
    private SocketChannel channel;

    public void connect(SocketAddress addr) throws IOException {
        channel = SocketChannel.open(addr);
    }

    public void send(Path file) {
        try {
            FileChannel fc = FileChannel.open(file, StandardOpenOption.READ);
            ByteBuffer buf = ByteBuffer.allocate(8);
            long size = Files.size(file);
            buf.putLong(size);
            buf.flip();
            send(buf);
            int sent = 0;
            while (sent < size) {
                sent += fc.transferTo(sent, size - sent, channel);
            }
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    private void send(ByteBuffer buf) throws IOException {
        while (buf.hasRemaining()) {
            channel.write(buf);
        }
    }            

    public void close() throws IOException {
        if (channel != null) {
            channel.close();
        }
    }

    public static void main(String[] args) throws IOException {
        Path dir = Paths.get(args[0]);
        Client c = new Client();
        c.connect(new InetSocketAddress("localhost", 5000));
        Files.list(dir).filter(p -> p.toString().toLowerCase().endsWith(".jpg")).forEach(c::send);
        c.close();
    }
}
 

BaluEtDespi

Mitglied
Ich kann es kaum glauben!! Mit einer winzigen Änderung funktioniert es!
In der letzten if(size == 0)-Condition fehlt am Ende ein break, falls doch noch etwas aus dem Channel gelesen werden kann, != -1 also noch nicht eingetreten ist.
Ich werde noch ein paar Durchgänge replizieren, ob das Ergebnis immer das selbe ist (ist es natürlich nicht, da bei mehreren Entitäten immer anders selektiert werden kann, aber ob das Ergebnis immer ein erfolgreiches ist).
... (probiert)
Konnte erfolgreich 600 Bilder, insgesamt ca. 1,5 GB versenden.
Wow, DANKE!
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
lumo SocketChannel + Write Netzwerkprogrammierung 11
K Frage zum Verhalten von SocketChannel.write(.) Netzwerkprogrammierung 9
A FileChannel+SocketChannel:Datei wird nur teilweise übertrage Netzwerkprogrammierung 4
A Was kann SocketChannel was Socket nicht kann? Netzwerkprogrammierung 11
P SocketChannel & Socket & JDom & Encoding Netzwerkprogrammierung 4
A ByteBuffer - Client/Server Netzwerkprogrammierung 9
M Eingehender ByteBuffer mit unbestimmter Größe Netzwerkprogrammierung 2
A String per ByteBuffer empfangen? Netzwerkprogrammierung 6
E Bilder übergeben Netzwerkprogrammierung 16
S Socket Bilder über Socket Senden Netzwerkprogrammierung 0
B Socket Bilder verschicken via Sockets. Heap-Problem. Netzwerkprogrammierung 2
M Socket Bilder senden Netzwerkprogrammierung 1
S Bilder durch's Netzwerk usw ... Netzwerkprogrammierung 10
R Webservice und Bilder Netzwerkprogrammierung 29
TheJavaKid Bilder mit nem ObjectStream übertragen Netzwerkprogrammierung 8
F Bilder aus Internet downloaden Netzwerkprogrammierung 12
S Socket Bilder übertragen Netzwerkprogrammierung 7
M Socket Bilder über das Netzwerk übertragen Netzwerkprogrammierung 4
G Frage zu Servlets und deren Objekte + Bilder in Servlets anzeigen? Netzwerkprogrammierung 11
Benji0815 Bilder oder PDF downloaden Netzwerkprogrammierung 11
H Bilder mit HttpClient hochladen Netzwerkprogrammierung 3
A html - E-mail - Bilder Netzwerkprogrammierung 4
A [URLConnection] Bilder hochladen - aber anders! Netzwerkprogrammierung 4
Lazybone Bilder über Socket Netzwerkprogrammierung 4
P Bilder: FTP-Upload funktioniert nicht richtig Netzwerkprogrammierung 2

Ähnliche Java Themen


Oben