Versenden von Daten übers Netzwerk

Status
Nicht offen für weitere Antworten.

Kr0e

Gesperrter Benutzer
Hallo,
wie kann ich eine Datei effizient versenden ? Mein Problem ist,
dass ich zwar ne gute Netzwerkauslastung habe (ca. 80%) aber auch ne recht ordentlich
Prozessorauslastung von 100%. Viele FTP Programme wie zb FileZilla schaffen durchaus 90-95 %
und das bei 10 % Prozessorlast. Das gleiche tritt beim Kopieren von Dateien über den Windows Explorer auf...
CPU = 20 % nur ... Liegt das an Java im allgemeinen ? Oder gibt es Kniffe beim übermitteln von großen Daten ?
Mir ist schon klar, dass ich ne Datei Stück für Stück einlesen und versenden muss ;)
Aber ich meine vlt Dinge wie "erst 1MB von der Festplatte laden und diesen dann verschicken in kleinen teilen" oder "verändern der Puffergröße" (Meine liegt bei 1024*32).

Habt ihr da Erfahrungen/Ideen ?

Gruß Chris

PS: Ich arbeite zur Zeit mit dem Apache Mina Framework!
 

sparrow

Top Contributor
Wie wäre es mit einem kleinen, funktionierenden Beispiel das sowohl Server als auch Client enthält?
Dann könnte man direkt am Code schauen wie und wo man uU optimieren kann.
 
T

tuxedo

Gast
Naja, bei dem MINA Framework kann man zwar nicht so viel falsch machen wie wenn man es zu Fuß programmiert, aber Fehler kann man da ebenfalls machen. Lass mal sehen wie du Dinge überträgst.

Generell kann ich sagen: Das MINA Framework ist sehr performant (selbst ausprobiert). Daran oder an Java kanns nicht liegen.
 

Kr0e

Gesperrter Benutzer
Hallo,
das ist ganz schön viel Material, da der File Transfer ein Teil des gesamt Protocols ist.
Ich werde mal meinen FileTransferFilter posten, der eine Instanz vom Typ FileTransfer benutzt und meine Hilfsklassen Upload & Download & FileTask, da ist alles wichtige drin denke:

FileTransferFilter
Java:
package foxnet.network.protocol.filter;

import foxnet.network.protocol.requests.FileRequests;
import foxnet.network.protocol.transport.FileTransfer;
import org.apache.mina.core.filterchain.IoFilter.NextFilter;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.session.IoSession;

/**
 *
 * @author Kr0e
 */
public class FileTransferFilter extends IoFilterAdapter {

    protected FileTransfer transfer;

    public FileTransferFilter(FileTransfer transfer) {
        if(transfer == null)
            throw new NullPointerException("transfer is null");

        this.transfer = transfer;
    }

    @Override
    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
        if(message instanceof FileRequests.Transfer) {
            if(message instanceof FileRequests.Start) {
                FileRequests.Start f = (FileRequests.Start)message;

                transfer.addDownload(session, f);
            }
            else if(message instanceof FileRequests.Data) {
                FileRequests.Data f = (FileRequests.Data)message;

                transfer.extendDownload(f);
            }
            else if(message instanceof FileRequests.Finished) {
                FileRequests.Finished f = (FileRequests.Finished)message;

                transfer.finishDownload(f);
            }
            else if(message instanceof FileRequests.Accepted) {
                FileRequests.Accepted f = (FileRequests.Accepted)message;

                transfer.acceptUpload(f);
            }
        }
        else
            nextFilter.messageReceived(session, message);
    }
}

Nun kommt die Verwaltungsklasse FileTransfer:
Java:
package foxnet.network.protocol.transport;

import foxnet.network.protocol.event.FileTransferListener;
import foxnet.network.protocol.event.FileTransferEvent;
import foxnet.network.protocol.requests.FileRequests;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.mina.core.session.IoSession;

/**
 *
 * @author Kr0e
 */
public class FileTransfer {

    protected long counter;

    protected LinkedHashMap<Long, FileTask> waitingQueue;

    protected LinkedBlockingQueue<FileTask> transfers;

    protected LinkedList<FileTransferListener> listeners;

    protected ExecutorService threadPool;

    protected Thread workerThread;

    protected File downloadDir;

    protected Long inc() {
        counter++;

        if(counter == Long.MIN_VALUE)
            counter = 0;

        return Long.valueOf(counter);
    }

    protected void init() {
        counter = 0;
        transfers = new LinkedBlockingQueue<FileTask>();
        waitingQueue = new LinkedHashMap<Long, FileTask>();
        listeners = new LinkedList<FileTransferListener>();
    }

    protected class RunnableTransfer implements Runnable {

        protected FileTask task;

        public RunnableTransfer(FileTask task) {
            this.task = task;
        }

        @Override
        public void run() {
            try {
                //Upload...
                if(task instanceof Upload) {
                    ((Upload) task).upload();

                    //Notify
                    for(FileTransferListener l : listeners)
                        l.onUploadData(new FileTransferEvent(this, task));

                    //Put it back
                    if(!task.isReady() &&
                       !task.isAbort())
                        transfers.put(task);
                    else
                        //Notify
                        for(FileTransferListener l : listeners)
                            l.onUploadFinished(new FileTransferEvent(this, task));
                }
                //Download...
                else if(task instanceof Download) {
                    ((Download) task).download();

                    if(task.isReady() ||
                       task.isAbort()) {
                        waitingQueue.remove(Long.valueOf(task.getId()));

                        //Notify
                        for(FileTransferListener l : listeners)
                            l.onDownloadFinished(new FileTransferEvent(this, task));
                    }
                    else
                        //Notify
                        for(FileTransferListener l : listeners)
                            l.onDownloadData(new FileTransferEvent(this, task));
                }
            }
            catch(Exception e) {
                e.printStackTrace();
            }
        }
    }

    protected class WorkerThread implements Runnable {

        @Override
        public void run() {

            while(!Thread.interrupted()) {
                try {
                    //Next task
                    threadPool.submit(new RunnableTransfer(transfers.take()));
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
            }

            threadPool.shutdown();
        }
    }

    public FileTransfer(File downloadDir, int maxThreads) {
        if(downloadDir == null)
            throw new NullPointerException("downloadDir is null");

        //Init
        init();

        //Create a threadPool for the uploads
        threadPool = Executors.newFixedThreadPool(maxThreads);

        //Save
        this.downloadDir = downloadDir;

        //Worker thread
        (workerThread = new Thread(new WorkerThread())).start();
    }

    public void addFileTransferListener(FileTransferListener listener) {
        if(listener != null)
            listeners.add(listener);
    }

    public void removeFileTransferListener(FileTransferListener listener) {
        listeners.remove(listener);
    }

    public void acceptUpload(FileRequests.Accepted f) throws Exception {
        //Get and remove
        FileTask tf = waitingQueue.remove(Long.valueOf(f.getUploadId()));

        if(! (tf instanceof Upload) )
            waitingQueue.put(Long.valueOf(f.getUploadId()), tf);
        else if(tf != null) {
            Upload upload = (Upload)tf;

            //Set the new id
            upload.setId(f.getFileId());

            //Register for uploading
            transfers.put(upload);

            //Notify
            for(FileTransferListener l : listeners)
                l.onUploadAccepted(new FileTransferEvent(this, upload));
        }
    }

    public void requestUpload(Upload upload) throws Exception {
        if(upload == null)
            return;

        //Save as waiting
        waitingQueue.put(inc(), upload);

        //Send a start request
        upload.getSession().write(new FileRequests.Start(upload, counter));

        //Notify
        for(FileTransferListener l : listeners)
            l.onUploadRequested(new FileTransferEvent(this, upload));
    }

    public FileTask getFileTask(long id) {
        return waitingQueue.get(Long.valueOf(id));
    }

    public void extendDownload(FileRequests.Data data) throws Exception {
        if(data == null)
            return;

        FileTask ft = getFileTask(data.getFileId());

        if(ft instanceof Download) {
            ((Download) ft).getQueue().put(data);
            transfers.put(ft);
        }
    }

    public void finishDownload(FileRequests.Finished f) throws Exception {
        FileTask ft = getFileTask(f.getFileId());

        if(ft instanceof Download) {
            ((Download) ft).getQueue().put(new FileRequests.Data(null, 0, 0));
            transfers.put(ft);
        }
    }

    public void addDownload(IoSession session, FileRequests.Start s) throws Exception {
        if(s == null)
            return;

        Download download = new Download(new File(downloadDir.getPath()+
                                                  "/" + s.getFileName()),
                                                  session, s.getFileSize(),
                                                  s.getFileOffset(), s.getLength());

        waitingQueue.put(inc(), download);
        download.setId(counter);
        download.getSession().write(new FileRequests.Accepted(counter, s.getUploadId()));

        //Notify
        for(FileTransferListener l : listeners)
            l.onDownloadAdded(new FileTransferEvent(this, download));
    }

    public void close() {
        workerThread.interrupt();
    }
}

Jetzt folgt die Super von Upload & Download -> FileTask:
Java:
package foxnet.network.protocol.transport;

import java.io.File;
import java.io.RandomAccessFile;
import org.apache.mina.core.session.IoSession;

/**
 *
 * @author Kr0e
 */
public abstract class FileTask {

    protected RandomAccessFile file;

    protected File path;

    protected long fileId;

    protected IoSession session;

    protected long fileSize;

    protected long fileOffset;

    protected long length;

    protected long transfered;

    protected boolean abort;

    protected boolean ready;

    protected void setId(long fileId) {
        this.fileId = fileId;
    }

    protected synchronized void ready() throws Exception {
        ready = true;        
        file.close();
    }

    protected abstract void openFile() throws Exception;

    public FileTask(File path, IoSession session, long fileSize,
                    long fileOffset, long length) {
        this.path = path;
        this.session = session;
        this.fileSize = fileSize;
        this.fileOffset = fileOffset;
        this.length = length;
        abort = false;
        file = null;
    }

    public RandomAccessFile getFile() {
        return file;
    }

    public File getPath() {
        return path;
    }

    public IoSession getSession() {
        return session;
    }

    public synchronized boolean isReady() {
        return ready;
    }

    public synchronized boolean isAbort() {
        return abort;
    }

    public synchronized void abort() throws Exception {
        abort = true;
        file.close();
    }

    public long getId() {
        return fileId;
    }

    public long getFileSize() {
        return fileSize;
    }

    public long getFileOffset() {
        return fileOffset;
    }

    public long getLength() {
        return length;
    }   

    public double getPercent() {
        return transfered * 100.0 / length;
    }

    public long getRemaining() {
        return length - transfered;
    }
}

Upload...
Java:
package foxnet.network.protocol.transport;

import foxnet.network.protocol.requests.FileRequests;
import java.io.File;
import java.io.RandomAccessFile;
import org.apache.mina.core.session.IoSession;

/**
 *
 * @author Kr0e
 */
public class Upload extends FileTask {

    public static final int DEFAULT_BUFFER_SIZE = 1024*48;

    protected byte[] buffer;

    @Override
    protected void openFile() throws Exception {
        file = new RandomAccessFile(path, "rw");
        file.seek(fileOffset);
    }

    protected int getNext(byte[] buffer) throws Exception {
        if(isReady())
            return -1;

        //Remaining bytes
        long remaining = length - transfered;

        //Read the next block
        int tmpRead = file.read(buffer, 0,
                                buffer.length < remaining ?
                                    buffer.length : (int)remaining);

        //Add
        transfered += tmpRead;

        //FileTask is ready
        if(transfered >= length)
            ready();

        return tmpRead;
    }

    public Upload(File path, IoSession session) {
        this(path, 0, path.length(), session);
    }

    public Upload(File path, long start, long length, IoSession session) {
        super(path, session, path.length(), start, length);
        setId(-1);
        transfered = 0;
        buffer = new byte[DEFAULT_BUFFER_SIZE];
    }
    
    public void upload() throws Exception {
        if(file == null)
            openFile();

        int readSize = getNext(buffer);
        
        session.write(new FileRequests.Data(buffer, readSize, getId())).await();

        if(isReady())
            session.write(new FileRequests.Finished(getId())).await();
    }
}

Und der Download...
Java:
package foxnet.network.protocol.transport;

import foxnet.network.protocol.requests.FileRequests;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.mina.core.session.IoSession;

/**
 *
 * @author Kr0e
 */
public class Download extends FileTask {

    public static final int DEFAULT_SIZE = 1024;

    protected LinkedBlockingQueue<FileRequests.Data> queue;

    @Override
    protected void openFile() throws Exception {
        file = new RandomAccessFile(path, "rw");
        file.setLength(fileSize);
        file.seek(fileOffset);
    }

    public Download(File path, IoSession session,
                    long fileSize, long start,
                    long length) throws Exception {
        super(path, session, fileSize, start, length);

        queue = new LinkedBlockingQueue<FileRequests.Data>(DEFAULT_SIZE);
    }

    public LinkedBlockingQueue<FileRequests.Data> getQueue() {
        return queue;
    }

    public synchronized void download() throws Exception {
        if(file == null)
            openFile();

        FileRequests.Data d = (FileRequests.Data) queue.take();

        if(d != null)
            if(d.getBuffer() != null) {
                file.write(d.getBuffer(), 0, d.getArrayLength());
                transfered += d.getArrayLength();
            }
            else
                ready();                
    }
}

Ich weiß, dass das SEHR VIEL code ist, aber nur so bekommt ihr den nötigen Überblick über mein
Protokoll...

Im Prinzip ist die Funktionsweise recht simpel: Ich benutze Java Serialisierung um Objekte zu verschicken (liegts daran ?) Auf der anderen Seite überprüfe ich, ob der Request von einer bestimmten Sorte ist, falls ja, handle ich ihn entsprechend. Mit den Klassen ist paralleles Uploaden und Downloaden möglich!

Gruß Chris
 
T

tuxedo

Gast
Also um Datein zu verschicken solltest du KEINE Serialisierung benutzen. Des weiteren solltest du dir dringend mal den ProtocolCodecFilter bzw. das HowTo auf der MINA Seite anschauen. Damit kannst du dieses extreme "instanceof?" vermeiden (denn das kostet AFAIK auch etwas Zeit).

Da MINA bestens mit ByteBuffern umgehen kann: Nimm wo's geht statt byte[] ein ByteBuffer und vermeide wo es nur geht die Konvertierung zwischen byte[] und ByteBuffer.

Gruß
Alex
 

Kr0e

Gesperrter Benutzer
Hi, danke für die Tips, aber es bleiben ein paar FRagen:

1. Der ByteBuffer ist doch auch nur ein byte[] sofern man keine DirectBuffer verwendet... Zumindest sagt das die Impl vom ByteBuffer...

2. Ich habe es geschafft den Netzwerk Durchsatz zu erhöhen auf 80-90%. Der Fehler ist zu doof :D Ich hatte ganz am Anfang SSL implementiert und einfach vergessen das abzuschalten, aber das mit der hohen Prozessorlast bleibt...
Was ich nun interesant finde ist, dass Serialisierung und diese instanceof sachen anscheinend keinen Einfluss auf den Durchsatz haben aber sehr wohl auf die CPU-Last, was ja ansich auch Sinn macht, da das ständige Wrappen von byte[] viel Overhead verursacht.

3. Das man für Dateitransfer keine Serialisierung verwenden soll leuchtet mir ein, aber bei anderen simpleren Dingen kann man dies doch ruhig verwenden oder ? Immerhin gestattet man damit, dass es vollkommen egal ist was man sendet!

@Tuxedo
Wie würdest du sowas genau realisieren ? Also wenn es die "Performance" von Mina optimal ausnutzt, ohne dabei den nervigen Overhead zu verursachen... Vlt iwie MappedByteBuffer verwenden ? Habe kaum Erfahrungen was den Geschwindigkeitsunterschied zwischen java.io / java.nio -> FileChannel angeht...

Gruß Chris

EDIT:

Achja, ist es falsch RandomAccessFiles zu benutzen oder ist das ok ?
 
Zuletzt bearbeitet:

Kr0e

Gesperrter Benutzer
Hallo,
wie wäre es, wenn ich einfach für Daten mit hohem Durchsatz diesem Interface implementiere ?

Externalizable

Damit müsste könnte ich praktisch Objekte, die "sensibel" von der Performance her sind anpassen können ohne dafür mein gesamtes Konzept zu überdenken....
 
T

tuxedo

Gast
Zu Externalizable:

Ja, das wäre ein Anfang.

ByteBuffer, sei es Direkt oder Nicht-Direkt, werden von der JVM besser/schneller verarbeitet. Wieso das so ist steht in "Java NIO" von Ron Hitchens.

Warum du byte[] meiden sollst: Mina arbeitet intern ausschließlich mti ByteBuffer. Das Konvertieren eines normalen byte[] in ByteBUffer und umgekehrt kostet unnötig Zeit. Lieber gleich auf den gemeinsamen Nenner "ByteBuffer" setzen. Dann spart man sich die Konvertiererei.
Klar, das mag nicht überall gehen, aber an vielen Ecken geht's.

3. Das man für Dateitransfer keine Serialisierung verwenden soll leuchtet mir ein, aber bei anderen simpleren Dingen kann man dies doch ruhig verwenden oder ? Immerhin gestattet man damit, dass es vollkommen egal ist was man sendet!

Funktionieren tut beides. Kommt halt drauf an was man haben will. Die meiste Performance wirst du haben wenn du nicht den ganzen Objektbaum serialisieren musst.
Wenn du nicht selbst die Daten extrahieren und senden willst benutze flache POJOs. Das wäre schonmal eine Verbesserung zu Objekten die nen halben Objektwald hinter sich her zerren.
 
Zuletzt bearbeitet von einem Moderator:

Kr0e

Gesperrter Benutzer
Ok, hab nun ne Idee, die vlt funktionieren könnte...
Ich werde den Codec selber schreiben undzwar ähnlich wie ObjectSerializationCodecFactory von Mina.
Allerdings werde ich die Möglichkeit implementieren, direkt IoBuffer zu verschicken. Damit werde ich in der Lage sein, beide Verfahren entsprechend meiner Bedürfnisse zu unterstützen.

Mal sehen, ob das wirklich was am Overhead ändert. Ich errinnere mich noch an ältere Programme von mir, die über simple Sockets eine Datei verschoben haben, ohne Serialisierung und so... Dort war die CPU Last ebenfalls sehr hoch bei einer Lese/Schreib Puffergröße von 4096 bytes... Werden die ByteBuffer von NIO vlt schon auf OS Ebene besser unterstützt als byte[]im allgemeinen ?
 
T

tuxedo

Gast
Ok, hab nun ne Idee, die vlt funktionieren könnte...
Ich werde den Codec selber schreiben undzwar ähnlich wie ObjectSerializationCodecFactory von Mina.

Hey, das war nicht deine Idee, das war meine:

tuxedo hat gesagt.:
Des weiteren solltest du dir dringend mal den ProtocolCodecFilter bzw. das HowTo auf der MINA Seite anschauen.

Aber egal. Mit einem eigenen Codec kannst du dir das ganze "instanceof?" sparen. Das sollte schon etwas Zeit sparen. Beim Datei einlesen und verschicken würde ich die Pakete nicht <8k machen. I.d.R. ist der Buffer auf dem Netzwerk 8k groß. Wenn du nun in 4k Schritten schreibst musst du 2x schreiben bevor die Daten raus gehen.

Solltest mal probieren wie sich das System verhält wenn du bis 512k fragmentierst ... Also mal testweise 8, 16, 32, 64, 128, 256 und 512k probieren.

IoBuffer ist von ByteBuffer abgeleitet. Die kannst du beruhuigt benutzen. Aber auch hier kann man noch byte[] mit ins Spiel bringen.

Werden die ByteBuffer von NIO vlt schon auf OS Ebene besser unterstützt als byte[]im allgemeinen ?

Mir scheint du hast nicht aufmerksam gelesen was ich geschrieben hatte:

tuxedo hat gesagt.:
ByteBuffer, sei es Direkt oder Nicht-Direkt, werden von der JVM besser/schneller verarbeitet. Wieso das so ist steht in "Java NIO" von Ron Hitchens.
 

Kr0e

Gesperrter Benutzer
Hi,

@Tuxedo:

Klingt vlt doof gerade, aber mir fällt grad nicht ein, wie man ne Datei mit Mina verschicken soll,
ohne den Buffer der empfangen wird, nochmals zu duplizieren .... Ansich hört sich das einfach an, aber der Inhalt des Buffers (Ich benutze den CumulativeProtocolDecoder) scheint nicht konstant zu sein, wenn er einfach weitergeleitet wird, sobald genug da ist:

Java:
    static class Decoder extends CumulativeProtocolDecoder {

        @Override
        protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
            if(!in.prefixedDataAvailable(4))
                return false;

            in.skip(4); //Brauch ich ab hier nicht mehr...
            out.write(in);

            return true;
        }

    }

Das geht schonmal nicht, da der CumulativeDecoder nach dem Aufruf dieser Methode die Position ändert.... Aber auch folgender Aufruf endet in einem ByteSalat bei mir:

Java:
    static class Decoder extends CumulativeProtocolDecoder {

        @Override
        protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
            if(!in.prefixedDataAvailable(4))
                return false;

            IoBuffer b = in.duplicate();
            b.limit(in.getInt());
            b.flip();
            b.skip(4);            
            out.write(b);

            return true;
        }

    }


Ich finds peinlich, dass ich das grad iwie nicht hinbekomme... Mina ist ja stellenweise noch nicht ausdokumentiert...
Wie würdest du es machen, wenn du mit Mina unglaublich performant eine Datei übertragen würdest ?

Gruß chris
 
T

tuxedo

Gast
Du musst dir merken wieviel schon gelesen wurde.... Da kommst du mit dieser Art Codec nicht dran vorbei.

Schau mal hier rein:

SIMON - /trunk/src/main/java/de/root1/simon/codec/messages - root1.de - Software Engineering

SIMON - /trunk/src/main/java/de/root1/simon/codec/base - root1.de - Software Engineering

Das erste sind die Nachrichtenarten, das zweite sind die entsprechenden Encode und Decoder dieser Nachrichten.

Wie du feststellen wirst hab ich keinen reinen CumulativeProtocolCodec, sondern einen ganzen protocol Codec. Das hat den Vorteil dass ich verschiedene Nachrichten verschicken kann, die entsprechend ausgewertet werden können.

Der "Trick" dabei ist: Die Nachricht an sich enthält jeweils folgendes:

* Nachrichten Art
* Sequenz-Nummer
* Nachricht selbst

Mit der Nachrichtenart wird entschieden welches Message-Objekt gerade angekommen ist und dementsprechend der Decoder ausgewählt.

Da's bei mir eigtl um RemoteCalls geht sind die meisten Nachrichten für dich wohl nicht von belang. Aber schau dir mal den RawChannel-Krempel an. Da hab ich, neben der RMI artigen Kommunikation, auch die Möglichkeit Rohdaten zu versenden. Da ist dann sehr wenig reflection und serialisierung im Spiel so dass es ziemlich fix geht.

Wenn ich nun eine Datei übertrage, dann wird diese schon beim lesen in kleinen Stücken gelesen. Jedes Stück ergibt eine MsgRawChannelData.

Da drin steht wie groß das zu lesende Stück ist. Und das wird beim empfangen ausgewertet. Erst wenn das ganze Stück da ist, wird die Nachricht in der Filterketter zur weiteren Verarbeitung weiter gereicht.

MINA ist in der Tat nicht optimal dokumentiert. Hab auch viel ausprobieren müssen. Aber wenns irgendwo klemmt, dann hat mir die Mailingliste immer geholfen.

Wenn du mit meinem Source nicht weiter kommst würd ich dort mal schauen..

- Alex
 

Kr0e

Gesperrter Benutzer
Hi, das hilft iwie alles nichts.... Hab ne extrem schlanke Version mit Mina zum Übertragen von Daten ... Auslastung bleibt bei 70-80 % CPU... Netzwerk ist gut ausgelastet... Das mit der CPU Auslastung ist wohl ein Problem von Java ... Selbst das simple kopieren von einer Datei komplett ohne Sockets mit 4096 oder auch 8192 byte Blöcken bringt bei mir ne Auslastung von 70-80 % .... Ich werde weiter hin Serialisierung verwenden.... Ohne einen entscheidenen Vorteil lohnt sich ne neue Implementierung nicht....
 
T

tuxedo

Gast
Schonmal nen Profiler drauf angesetzt und geschaut wo die ganze Rechenzeit hingeht?

btw: Per PM sowas weiter zu diskutieren ist nicht gerade förderlich für dritte, die selbiges Problem haben und nach einer Lösung suchen. Ergo: Hier weiter diskutieren.

Im übrigen: Ich sagte nix von Mailingliste durchsuchen. Ich sagte was von "Der Mailingliste das Problem schildern"... Denn DA sitzen die Experten.

- Alex
 

Kr0e

Gesperrter Benutzer
Hi, hab ne andere extrem coole Lösung gefunden, beim durchsuchen der IoSession (Code) ist mir was interesantes aufgefallen:

Es wird unterstützt ein File/FileChannel zu schicken als objekt, ganz ohne Änderungen... Mina scheint das nativ zu unterstützen und vor allen Filtern zu managen... Hätte Mina doch bloß ne Dokumentation !!!
Bleiben Extrawünsche wie SSL & Kompression kann ein FileRegionWriteFilter genommen werden...
Ich werde mal ein wenig rumprobieren und hier nochmal meine Ergebnisse mitteilen...

Gruß Chris
 

Kr0e

Gesperrter Benutzer
Hallo,
habe nun endlich gemerkt, dass das was ich vorhab mit Java leider nicht möglich ist... Hab ein kleines simples C Programm geschrieben, dass eine Datei ganz simpel mit 4096 byte Blöcken verschickt... 96% Netzwerk .. 20 % CPU ... Gleiches Spiel mit Java (Ganz normale Sockets) ergab zwar auch 90 % netzwerk aber auch 70-90 % cpu... Da ich mit Mina vorhatte einen Streaming Server zu schreiben, kann ich es mir nicht leisten 100 % Cpu zu benutzen, da sonst die Videodekompriemierung mit Fobs4Jmf ins stottern kommt (Auch Medien sind der Horror unter Java... CPU > 80 % für eine simples Mpeg Video) Werde nun versuchen eine Übertragungsgeschwindigkeit zu finden (Weit unter 100 mbit :( ), bei der die CPU garnicht ausgelastet wird... vlt so 2-3 mb/sek... Reicht ja zum Film guggen vollkommen aus...
Oder ich werde Mina nur für die Übertragung der Befehle nutzen und die Dateiübertragungen über C regeln....
Falls es dennoch irgendwer hinbekommen sollte, mein Problem in irgendeiner Weise zu lösen, wäre ich sehr dankbar für die Hilfe!!!

-> Plattformunabhängig vs. Geschwindigkeit :(

Gruß Chris

PS: Falls irgendwer sagt, dass meine Impl. schlecht ist, sollte wissen, dass ich die Speedtests mit dem Mina FTP Server durchgeführt hab... Die sollten ja wohl wissen, wie man am meisten aus Mina rausholt ;)
 
T

tuxedo

Gast
Ich schlage nach wie vor vor, dass du mal nen Profiler einsetzt um ein Gefühl dafür zu bekommen wo denn die ganze CPU-Last hingeht.

Nur mal so rein hypothetisch:

Vielleicht hast du ein Mainboard mit Onboard-Netzwerk. In nicht allzuseltenen Fällen übernimmt da die CPU die Arbeit einer "anständigen Netzwerkkarte". Und wenn du da dann Full-Speed überträgst, ist eben nicht nur das Netzwerk gut damit besachäftigt, sondern auch die CPU. Wobei: 20% klingt da schon nach recht viel. Hab da keine Erfahrungswerte parat. Ich will damit nur sagen: Es kann unter Umständen auch an der Hardware oder am OS liegen (hast du mal selbiges mit Linux probiert?).

Kann im übrigen nach wie vor nicht ganz glauben dass Java demaßen inperformant (in bezug auf die CPU) Daten überträgt. Wenn ich dazu komm mach ich mal ne Testimplementierung ...


- Alex
 
T

tuxedo

Gast
Sodele.

Bin dazu gekommen selbst mal zu experimentieren:

Java:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;


public class FileSender
{
    
    public static void main (String[] args) throws UnknownHostException, IOException
    {
        Socket s = new Socket("150.158.180.121", 1234);
//        Socket s = new Socket("127.0.0.1", 1234);
        
        OutputStream os = s.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);
        
        FileInputStream fis = new FileInputStream("C:/Documents and Settings/achr/Desktop/Transfer/tmp/ubuntu-9.04-desktop-amd64.iso");
        BufferedInputStream bis = new BufferedInputStream(fis);
        
        int read = 0;
        long total = 0;
        byte[] data = new byte[8*1024]; // 8k
        System.out.println("Sending file ...");
        long start = System.currentTimeMillis();
        while (read!=-1){
            read = bis.read(data);
            if (read>0)
                bos.write(data, 0, read);
            total+=read;
            if (total%(10*1024*1024)==0)
                System.out.println(total + " bytes sent");
        }
        long duration = System.currentTimeMillis()-start;
        System.out.println("All done... "+ total+" bytes written.");
        System.out.println("Duration: "+duration+"ms. Speed: "+((long)(total/(duration/1000l)))+" bytes/sek");
        bis.close();
        bos.close();
        
    }

}

Java:
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class FileReceiver
{
    
    public static void main (String[] args) throws IOException
    {
        ServerSocket ss = new ServerSocket(1234);
        System.out.println("Waiting for connection... ");
        Socket accept = ss.accept();
        System.out.println("Got connection, receiving file ...");
        InputStream is = accept.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(is);
        FileOutputStream fos = new FileOutputStream("received.file");
        
        int read = 0;
        int total = 0;
        byte[] data = new byte[8*1024];
        long start = System.currentTimeMillis();
        while (read!=-1){
            read = bis.read(data);
            if (read>0)
                fos.write(data, 0, read);
            total += read;
            if (total%(10*1024*1024)==0)
                System.out.println(total + " bytes received");
        }
        long duration = System.currentTimeMillis()-start;
        System.out.println("File received. "+total+" bytes read.");
        System.out.println("Duration: "+duration+"ms. Speed: "+((long)(total/(duration/1000l)))+" bytes/sek");
        bis.close();
        fos.close();
        
    }

}

Ergebnis auf Senderseite:
All done... 730554367 bytes written.
Duration: 67078ms. Speed: 10903796 bytes/sek

Auf Empfängerseite:
File received. 730554367 bytes read.
Duration: 66694ms. Speed: 11069005 bytes/sek

Wie man am Sourcecode sehen kann hab ich die BufferedStreams benutzt, sowie eine Fragmentierung von 8k.

CPU Last war auf beiden Seiten nie über 5% (ProcessExplorer statt TaskManager in Windows, "Systemüberwachung" in Ubuntu. Nur CPU-Last des Java-Prozesses betrachtet. Rest war/ist irrelevant. Dennoch war auch die Gesamt-CPU-Last auf beiden Systemen nie über 6%).

Beide Systeme:
Core2Duo 2x2,13Ghz mit 2GB RAM und OnBoard Netzwerk
Beide haben 80GB Saegate Platten an IDE
Sender: Windowx XP i386
Empfänger: Ubuntu 9.04 amd64
 
Zuletzt bearbeitet von einem Moderator:

Kr0e

Gesperrter Benutzer
Danke für den Hinweiß! War jetzt ne zeitlang weg.... Werde mir deine Impl. und deinen Ratschlag zu Herzen nehmen... Hab hier bestimmt noch ne alte pci lankarte rumliegen... Danke!

Gruß Chris
 
T

tuxedo

Gast
Naja. Ob "ne alte PCI-Lan Karte" so viel besser ist wie eine OnBoard wage ich zu bezweifeln. Die sind meist "genauso billig" wie die OnBoard Dinger.
Es gibt aber auch - nur ein Beispiel - Netzwerkkarten wie sie z.B. Intel im Programm hat die sogut wie keine Last auf der CPU erzeugen und "viel selbst machen". Die haben wir hier in der Firma für unser VideoLAN im Einsatz (zum einen wegen der etwas besseren Performance, zum aneeren weil wir hier Gigabit brauchen).

In meinem Test waren ja auch 2 Onboard Karten (100mbit) involviert.

- Alex
 

Kr0e

Gesperrter Benutzer
Ja, da wirst du wohl Recht haben :(

Hatte hier ne Realtek... Ist genauso doof... Onboard is ne VIA Gigabit verbaut und auch eher schlecht als recht...

Naja, mal schauen ob ich ne neue besorge oder einfach warte, bis der nächste pc angeschafft wird. Wenigstens weiß ich nun, dass es nicht an der Impl. liegt. Zumindest in diesem Fall... Ich hatte mich auch gewundert, dass Java so inperformant sein soll.... Aber wenn auch 20-30 % schon zu viel ist (C Programm), dann liegt es wirklich an der Karte... Aber dennoch merkwürdig, warum das C Programm soviel besser funktioniert hat ..-. :(

Gruß Chris
 
T

tuxedo

Gast
Öhm, woher weißt du denn dass es nicht an der Impl. liegt? Hast du mein Sample schon ausprobiert?

Erst wenn du damit genauso schlechte werte wie vorher erzielst kannst du's auf die HW schieben.

- Alex
 

Kr0e

Gesperrter Benutzer
Japp, deine Impl. erzeugt 91% netzwerklast + 99% cpu last....
Ich hasse billige Hardware... Werde so einen Fehler nie wieder machen...
Aber ne andere Sache, die evt noch interessant wäre...
Wenn ich eine Datei lokal kopiere, dann erzeugt das mit Java ca 20 % CPU Last.
Wenn ich den Explorer benutze, komme ihc nicht über 5%... Es scheint, als würde iwie
beides eine Rolle spielen... Einerseits, dass es in Java geschrieben ist und zum anderen,
dass ich miese Hardware (Also jetzt auf die Netzwerkkarte bezogen) habe :(
Mit der Funktion "transferTo" aus dem FileChannel geht das zwar ähnlich gut, wie
beim Explorer, allerdings sind hier Dateien > 2 gb tabu (OutOfMemory Gedöhns). Ist das ein Bug oder normal ?
Sprich muss man die Datei hier auch in "Chunks" verschicken ? Ich weiß leider nicht,
wie die Impl. von transferTo aussieht... (Ist denke ich mal dann bei jedem OS eine andere...)

Gruß Chris
 
T

tuxedo

Gast
Hast du dir mal die JavaDoc zu "transferTo" angesehen?

FileChannel (Java 2 Platform SE v1.4.2))

Du kannst, egal in welcher Sprache, keine x-beliebig große Datei komplett laden ohne irgendwann im "OutOfMemory" zu laden. Du musst IMMER in Chunks arbeiten wenn du sicher gehen willst dass nix raucht. Wenn es nicht intern getan wird (ist ja offensichtlich bei FileChannel nicht der Fall), dann musst du's selbst tun.

- Alex
 

Kr0e

Gesperrter Benutzer
Ok, bin jetzt mit dem Video/Audio Stream Server fertig. Danke für eure Hilfe.

Gruß Chris

PS: Beim Streamen habe ich nun eine Geschwindigkeitsbremse eingebaut, damit man vernüftig das Video ansehen kann UND nebenbei laden. Ich habe mein Programm auf mehreren REchnern nun getestet... Da viele PCs schlechte Onboard Lan Karten hatten, war diese Bremse unumgänglich... Nur sehr hoch wertige Computer(3 von 11) hatten in meinem Test eine ordentliche Lankarte drinne...
Immerhin kann man ja schlecht verlangen, dass sich die User ne neue Karte kaufen, nur damit man das Programm ordentlich nutzen kann.


Für die Datenübertragung ansich, verwende ich nun den FtpServer von Apache. Das hat den Vorteil, dass ich nicht den ganzen Kram mit Verzeichnissen usw. selbst impl. muss... Allerdings kann man auf PCs mit schlechten Lankarten nicht beides gleichzeitig benutzen... Entweder bufferen + Video schauen oder Datei laden mit Fullspeed laden... Dieser ganze Workaround ist allerdings nur wichtig, solange man das Programm im Lan benutzt... Über Internet ist die Bandbreite "genügend" gering, damit die CPU auch bei schlechten Lankarten nicht ins schwitzen kommt...
 
T

tuxedo

Gast
Nur mal so am Rande:

Ich glaube nicht dass es ausschließlich die LAN-Karte ist. Die trägt vllt. mit dazu bei. Aber allein ausschlaggebend ist sie sicher nicht.

Die meisten OnBoard Karten sind "tauglich genug". Haben im Büro auch Rehner mit nur 100mbit onboard Lan am Videonetzwerk hängen. Und da haben 10 gestreamte Videos (PAL Auflösung) immernoch keine 100% CPU Last. Zumindest nicht auf Client-Seite wo die Videos angezeigt werden.

Da die Videos von einem Video-Encoder (Sat-Receiver -> Video-Encoder -> LAN) kommen ist auch kein lesen/schreiben vom/aufs Filesystem involviert.

Tippe vllt. noch auf schlecht konfigurierte Rechner (kein UDMA3/4/5/6?), langsame Platten oder ein verknotetes Windows XP ...

- Alex
 

Kr0e

Gesperrter Benutzer
Wie findet man denn raus, ob UDMA aktiv ist, oder nicht ? Und was meinst du mit "verknotetem" XP ??
Das ich nciht die schnellsten Platten hab, is mir klar... ATA133... Aber langsame Platten haben nix mit der CPU Last zu tun!!

Gruß Chris
 
T

tuxedo

Gast
Na ich kann von hier aus ja nur mutmaßen ... Vielleicht hast du ja auch in einer Schleife viel "+" Operatoren auf Strings angewendet (seeehr böse). Oder verbrätst sonstwo CPU Zeit?

Wie man die aktuelle DMA Einstellung unter Windows rausfindet? Keine Ahnung. Benutze Windows nicht so intensiv wie Linux. Kannst aber mit "hdtune" schauen wieviel MB/s du lesen und schreiben kannst. Sollte wohl so um die 50-60mb/sek liegen.

Wo die ganze CPU Zeit hingeht findest du nur raus, wenn du einen Profiler benutzt (glaub ich erwähn das schon zum zweiten mal?!).

Ein aktuelles JDk bringt jvisualvm (glaub das heißt so...) im bin-Ordner mit. Netbeans hat auch selbst nen Profiler drin. Und zur Not gibts noch Java Profiler - .NET Profiler - The profilers for Java and .NET professionals. Da gibts ne 15 Tage Testversion. Und wenn du n OpenSource Projekt hast kriegst du ne kostenlose Lizenz.

Alles andere ist nur "geraten". Nimm also den Profiler, beschäftig dich ein wenig damit und versuche zu verstehen wie man die Ergebnisse zu interpretieren hat.

Gruß
Alex
 

Kr0e

Gesperrter Benutzer
Hi, also erstmal saugt schon allein das Java Visual VM Dingen fast 60 % Prozessor :D Aber nungut.... die Methode sessionFecth() steht ganz oben danach kommt transferFile() ... sessionFetch braucht 70 % und transferFile 20 %...
Wohl gemerkt 70 % von der gesamt Prozessauslastung... nicht 70 % von der CPU ... Bzw. wenn ich das richtig verstanden hab... Und danach gibet nix mehr... nur sachen im 0.1% Bereich

Hab den FtpServer von Apache getestet...

Gruß Chris
 
T

tuxedo

Gast
Und wenn du jetzt genau diese Info auf der Minamailingliste präsentierst wird dir sicher jemand sagen können wieso das so ist und ob/wie man dagegen etwas tun kann.

Was sagt denn das Profiling bei meiner Testimplementierung?

Btw: Profiling bringt etwas Overhead mit sich. Das ist normal. Gute haben weniger Overhead, andere etwas mehr.
 

Kr0e

Gesperrter Benutzer
Bei deiner Impl. ist das Versenden über die Sockets das Nadelöhr... Also von der CPU Last her...
Liegt iwie am Netzwerk. Obs jetzt die Karte selber ist oder wirklich irgendwelche falschen Einstellungen bleibt offen...
Danke für die Infos!
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
H Socket Kann ein Socket server 2 dimensionale Arrays empfangen und versenden? Netzwerkprogrammierung 3
T E-Mail versenden. Netzwerkprogrammierung 8
L Versenden von "Bildschirm" Netzwerkprogrammierung 2
E Objekte versenden, Client-Server Netzwerkprogrammierung 25
L Email versenden mit Java funktioniert nicht, Fehlermeldungen: MessagingException & SocketException Netzwerkprogrammierung 10
precoc String Array versenden Netzwerkprogrammierung 7
B Viele verschiedene E-Mails an unterschiedliche Empfänger schnell versenden? Netzwerkprogrammierung 8
S Email via Googlemail versenden Netzwerkprogrammierung 17
M Mail über Exchange versenden Netzwerkprogrammierung 13
P Dateiennamen versenden Netzwerkprogrammierung 4
P Mit Java Javascript-Anfrage versenden Netzwerkprogrammierung 4
T Objekte im NIO unblocking mode versenden Netzwerkprogrammierung 11
N String als byte Array über Socket versenden Netzwerkprogrammierung 8
N Socket File über Socket vom Server an Client versenden Netzwerkprogrammierung 15
M Socket Datei über Socket versenden Netzwerkprogrammierung 5
C An WebDAV einen "MKCOL" Befehl versenden Netzwerkprogrammierung 4
X Versenden von Objekten braucht zu lange Netzwerkprogrammierung 5
N Technologie zum versenden von Objects Netzwerkprogrammierung 5
R Versenden einer MIME-Datei per E-Mail [solved] Netzwerkprogrammierung 5
N E-mail versenden Netzwerkprogrammierung 4
B Wie HTTP GET/POST Anfrage versenden? Netzwerkprogrammierung 7
T SWT Image versenden Netzwerkprogrammierung 2
T Einzelne Bits per Socket versenden Netzwerkprogrammierung 16
F Link erzeugen und versenden Netzwerkprogrammierung 4
O Wert versenden? Netzwerkprogrammierung 12
G Datein versenden funktioniert nicht ! Netzwerkprogrammierung 19
S Objekt Felder versenden Netzwerkprogrammierung 5
A Konflikt: Blocking und Non-Blocking bei Objekte versenden Netzwerkprogrammierung 4
O Über Socket Array versenden Netzwerkprogrammierung 5
G Bytes versenden Netzwerkprogrammierung 9
G Datei über ObjectInputStream versenden Netzwerkprogrammierung 8
B Strings versenden | Nur 1 Client Netzwerkprogrammierung 6
P Objekte über DatagramSocket versenden Netzwerkprogrammierung 12
V Kann keine DatagramPackets versenden von einem Jar Archiv Netzwerkprogrammierung 4
J Problem beim versenden von eigenen Objekten über RMI Netzwerkprogrammierung 2
S Dateien versenden Netzwerkprogrammierung 16
C Dateien über Sockets versenden? Netzwerkprogrammierung 10
F IP "versenden" Netzwerkprogrammierung 6
N *.* Dateien versenden im Lan Netzwerkprogrammierung 5
H Versenden von Objekt Netzwerkprogrammierung 2
L JavaMail: Versenden von mails funktioniert nicht Netzwerkprogrammierung 7
P Array per Socketverbindung versenden Netzwerkprogrammierung 2
O Sms versenden Netzwerkprogrammierung 5
T Dateien wia P2P Connection versenden Netzwerkprogrammierung 2
S E-Mails versenden. Netzwerkprogrammierung 10
D Bits über UDP versenden Netzwerkprogrammierung 3
M generelle Frage zum Versenden Netzwerkprogrammierung 4
T Mit JavaMail API Faxe versenden? Netzwerkprogrammierung 5
K Files versenden Netzwerkprogrammierung 5
OnDemand Daten per API senden, parallel Netzwerkprogrammierung 9
X Kann ich einen Client/Server verbindung hinkriegen die mir alle paar Sekunden die aktuellen Daten per Realtime zuschickt ? Netzwerkprogrammierung 9
Z Kann nicht Daten vom Server lesen Socket Netzwerkprogrammierung 10
S Daten über TCP/IP senden Netzwerkprogrammierung 3
B Daten an Javaprogramm per URI Aufruf übergeben Netzwerkprogrammierung 7
N websocket - keine Daten mehr nach ca 80 Sekunden Netzwerkprogrammierung 0
C Spezielle Daten aus Website entnehmen Netzwerkprogrammierung 5
H Daten auf einer Webseite eintragen Netzwerkprogrammierung 11
A Socket Daten in Textdokument speichern? Netzwerkprogrammierung 1
T Socket Java Programm hängt sich auf bei dem versuch von einem Socket scanner Daten zu erhalten. Netzwerkprogrammierung 1
J Daten von einem HTML-Textfeld abrufen Netzwerkprogrammierung 3
S Fakturierungsprogramm - Daten aktuell halten (blutiger Anfänger) Netzwerkprogrammierung 1
D Mit Server Daten austauschen Netzwerkprogrammierung 4
K Server liest Daten nicht Netzwerkprogrammierung 6
L HTTP Daten an Server übergeben Netzwerkprogrammierung 2
R Daten von Cloud laden Netzwerkprogrammierung 5
fLooojava Daten an ein Arduino im selben Netzwerk senden Netzwerkprogrammierung 1
X Daten können nicht sofort empfangen werden Netzwerkprogrammierung 1
S Server - Mehrere Klassen sollen Daten senden und empfangen Netzwerkprogrammierung 25
P Socket Best Practice: Daten bündeln Netzwerkprogrammierung 5
E Verfügbarkeit von Daten in Streams Netzwerkprogrammierung 4
F Daten aus Internetseiten auslesen Netzwerkprogrammierung 56
S Server Client Daten hin und herschicken Netzwerkprogrammierung 2
S Seltsames Verhalten beim Empfangen von Daten über DataInputStream Netzwerkprogrammierung 12
P Socket Daten senden mit ServerSocket? Netzwerkprogrammierung 2
H Daten an Textfeld einer Webseite schicken Netzwerkprogrammierung 2
A versch. Daten im Stream erkennen Netzwerkprogrammierung 2
D daten per post senden Netzwerkprogrammierung 3
M Senden von Daten nicht direkt möglich? Netzwerkprogrammierung 6
J Applet soll Daten auf Server ablegen - einfachster Weg fuer n00bs? Netzwerkprogrammierung 4
F Socket Daten über verschiedene IP's schicken Netzwerkprogrammierung 5
F UDP Daten kommen nicht an Netzwerkprogrammierung 22
E Socket OutputSream abbruch.Wegen zuviel !empfangener! Daten? Netzwerkprogrammierung 10
C Client zu Client Daten übertragen Netzwerkprogrammierung 13
S Thread, Daten vom Socket lesen Netzwerkprogrammierung 2
S Socket XML-Daten und Parameter an Server schicken Netzwerkprogrammierung 3
M Objekt über Object-Stream, empfange "alte" Daten Netzwerkprogrammierung 2
P HttpClient - Daten einer Website "unvollständig" Netzwerkprogrammierung 5
P Server/Client Daten empfangen, wenn Daten gesendet werden Netzwerkprogrammierung 9
K Socket Daten lesen ohne Längenangabe Netzwerkprogrammierung 19
T RMI Effizenteste Übertragung von Daten Netzwerkprogrammierung 6
A Socket Client Server Connection wird aufgebaut aber keine daten geschickt. Netzwerkprogrammierung 5
B Socket Daten empfangen funktioniert nicht richtig - wo liegt der Fehler? Netzwerkprogrammierung 7
E Daten kommen anders an als gesendert ?! Netzwerkprogrammierung 6
S HttpURLConnection POST splittet Daten in zwei Pakete Netzwerkprogrammierung 9
F POST-Daten sende Netzwerkprogrammierung 3
E HTTPS Debuggen (verschlüsselte Daten anzeigen)? Netzwerkprogrammierung 12
N Per POST -Methode Daten an den Web-Server übertragen. Netzwerkprogrammierung 9
Iron Monkey Große Daten ins Vector füllen Netzwerkprogrammierung 4
S Probleme beim senden von Daten per POST Methode an PHP Scrip Netzwerkprogrammierung 5
D Daten, die mit PHP erzeugt werden, mit Java auslesen Netzwerkprogrammierung 8

Ähnliche Java Themen

Neue Themen


Oben