Bildübertragung übers Netzwerk mit Boost
-
Hallo. Ich hab ein Problem bei dem ich nicht so richtig weiter komme.
Ich versuche meinen Raytracer den ich zu Zeiten der FH geschrieben nun auch Netzwerkfähig zu machen, also daraus eine Netzwerkapplikation zu entwickeln so dass auf mehreren Rechnern das Bild parallel berechnet wird. Nachdem ich mich mit Boost beschäftigt habe, habe ich die Multithreading-Optimierung fertig, aber bei der Übertagung der einen Hälfte des Bildes an den Server vom Client aus treten recht komische Bildfehler auf mit zum Teil Wiederholungen des Bildes. Nach der Ausgabe der einzelnen RGB-Werte fällt mir auf, dass diese sich von der Quelle am Client, also dem Computer der sie berechnet hat, unterscheiden. Am Anfang hatte ich noch das Problem, dass ich übersah, dass das sukzessive Senden und Empfangen den Speicherblock jedesmal überschreibt, sodass am Anfang der Bildbereich schwarz blieb, da auf dem Client die zweite Hälfte des Bild nicht berechnet wird und schwarz ist. Letztlich war mir auch klar, dass ein TCP-IP Paket max. 1460 Bytes Nutzdaten hat, und es wohl besser wäre sich auf diese Größe festzulegen und die Daten dann der Reihe nach mit solcher Größe zu übertragen. Leider blieb das ohne Erfolg. Schließlich hatte ich auch in das erste Byte der Datenstruktur auch die Paketnummer reingeschrieben, hat aber auch nichts gebracht um einen möglichen Fehler der Reihenfolge beim Empfang auszuschließen. Ich muss dazu sagen, dass ich eigentlich gar keine Kenntnisse habe in der Netzwerkprogrammierung, also bitte nicht gleich mit dem Hammer ankommen.

Hier noch die wichtigsten Methoden, in meinem Prorgamm zum Senden und Empfangen und auswerten der Bilddaten.
Client bezieht Koordinaten des zu berechnenden Bildteils (Server des Bildfragments)
void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) { if (!ec) tcp_socket2.shutdown(tcp::socket::shutdown_send); } void accept_handler(const boost::system::error_code &ec) { if (!ec) { for(int i=0;i<255;i++) { pixelStream[i*1460]=i; async_write(tcp_socket2, buffer(&(pixelStream[i*1460]),1460),boost::asio::transfer_exactly(1460), write_handler); } } } void server(void) { tcp_socket.close(); ioservice.stop(); tcp_acceptor.listen(); tcp_acceptor.async_accept(tcp_socket2, accept_handler); ioservice2.run(); } void transfer_framebuffer(void) { char tempColorField[x_res*y_res*3]; for(int i1=0;i1<x_res*y_res;i1++) field.cf[i1] = field2.cf[i1]; for(int x=Jobx1;x<Jobx2;x++) for(int y=Joby1;y<Joby2;y++) { pixelStream[y*x_res+x*3]=(char)(field.cf[y*x_res+x].GetR()*255); pixelStream[y*x_res+x*3+1]=(char)(field.cf[y*x_res+x].GetG()*255); pixelStream[y*x_res+x*3+2]=(char)(field.cf[y*x_res+x].GetB()*255); //printf(" R: %i G: %i B: %i \r\n",pixelStream[y*x_res+x*3],pixelStream[y*x_res+x*3+1],pixelStream[y*x_res+x*3+2]); } } Server (bezieht als Client den Bildteil des Clients der sich vorher die Eckkoordinaten des Bildausschnitts beim Server abgeholt hat) void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) { if (!ec) { //std::cout.write(bytes.data(), bytes_transferred); tcp_socket2.async_read_some(buffer(bytes), read_handler); for(int i=iByte;i<iByte+1460;i++) pixelStream[i]=bytes._Elems[i%1460]; iByte+=1460; } } oder void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) { if (!ec) { //std::cout.write(bytes.data(), bytes_transferred); tcp_socket2.async_read_some(buffer(bytes),read_handler); if (bytes._Elems[0]<0) bytes._Elems[0]-=128; for(int i=1;i<1460;i++) pixelStream[bytes._Elems[0]*1460+i]=bytes._Elems[i]; //iByte+=1460; } } void connect_handler(const boost::system::error_code &ec) { if (!ec) { std::string r = "GET / HTTP/1.1\r\nHost: theboostcpplibraries.com\r\n\r\n"; write(tcp_socket, buffer(r)); tcp_socket.async_read_some(buffer(bytes), read_handler); } } void resolve_handler(const boost::system::error_code &ec, tcp::resolver::iterator it) { if(!ec) tcp_socket2.async_connect(*it, connect_handler); } void client(void) { tcp_socket.close(); ioservice.stop(); tcp::resolver::query q("192.168.0.102", "2015"); resolv.async_resolve(q, resolve_handler); ioservice2.run(); for(int x=data[0].x1;x<data[0].x2*3;x+=3) for(int y=data[0].y1;y<data[0].y2;y++) { //printf(" R: %i G: %i B: %i \r\n",pixelStream[y*x_res+x],pixelStream[y*x_res+x+1],pixelStream[y*x_res+x+2]); field.cf[y*x_res+x/3].SetR(((float)(pixelStream[y*x_res*3+x]))/255.0f); field.cf[y*x_res+x/3].SetG(((float)(pixelStream[y*x_res*3+x+1]))/255.0f); field.cf[y*x_res+x/3].SetB(((float)(pixelStream[y*x_res*3+x+2]))/255.0f); // } }field.cf ist das eigentliche Colorfield, also die Datenstruktur für das Ausgabe-Bild. Hier werden die Werte von Pixelstream reingeschrieben,die vorher vom Client bezogen wurden, von Bytes in Floats umgewandelt, was ich selber schon als sehr dämlich ansehe, aber leider hatten wir das damals so programmiert bzw. vorgegeben bekommen in der FH.
Ich finde es komisch dass ich bei den größeren Daten logische Fehler bekomme. Zuerst werden ja die 4 ints an den Client geschickt, das klappt ohne Probleme. Daher bezieht sich meine Frage eher darauf: Was muss ich bei großen Datenmengen übers Netz beachten ?!?!?
Es sind ja bei 800 x 600er Auflösung bei 4 Bytes pro Float 800x600x4x3 Bytes.
Da ich die Floats vorher in 0..255 Werte umwandle, sinds 800x600x3 ~ 1.5 Mbyte.Was muss ich bei diesen Datenmengen beachten ?
-
johnxx schrieb:
Letztlich war mir auch klar, dass ein TCP-IP Paket max. 1460 Bytes Nutzdaten hat, und es wohl besser wäre sich auf diese Größe festzulegen und die Daten dann der Reihe nach mit solcher Größe zu übertragen.
Das klingt nach einer schlechten Idee. Du nutzt doch schließlich ein Framework wie Boost, damit du dich nicht um solche Details kümmern brauchst. Boost wird das schon sicher halbwegs geschickt machen. Und vor allem wird es nichts direkt falsch machen. Deinen Fehler solltest du zuallererst in deinem Programm suchen, nicht in Boost.
Leider blieb das ohne Erfolg. Schließlich hatte ich auch in das erste Byte der Datenstruktur auch die Paketnummer reingeschrieben, hat aber auch nichts gebracht um einen möglichen Fehler der Reihenfolge beim Empfang auszuschließen.
Wieder etwas, um das du dich weder kümmern musst noch solltest. Boost liefert dir die Daten garantiert in richtiger Reihenfolge. Genau genommen nicht einmal Boost. Boost selber implementiert kein TCP, sondern eine Schnittstelle zu den entsprechenden Funktionen des Betriebssystems. Diese arbeiten garantiert korrekt.
Ich finde es komisch dass ich bei den größeren Daten logische Fehler bekomme. Zuerst werden ja die 4 ints an den Client geschickt, das klappt ohne Probleme. Daher bezieht sich meine Frage eher darauf: Was muss ich bei großen Datenmengen übers Netz beachten ?!?!?
Eigentlich gar nichts, siehe oben. Das Problem ist sicherlich in deinem Programm. Wenn du deinen Beitrag lesbar formatierst, könnte man den Fehler vielleicht sogar finden. Wobei ich an deiner Stelle erst einmal deine oben beschriebenen Verschlimmbesserungen herausnehmen würde. Wenn du dein eigenes TCP über dem richtigen TCP implementierst, dann sorgt das nur für eine unnötige Fehlerquelle und macht alles komplizierter. Dass du Probleme bei großen Datenmengen bekommst deutet auf eventuelle Probleme mit der Größe von Empfangspuffern hin.
PS: Wegen der fehlenden Formatierung habe ich mir den Code nicht wirklich genau angesehen. Aber beim Überfliegen fiel mir spontan dies hier auf:
field.cf[y*x_res+x/3].SetR(((float)(pixelStream[y*x_res*3+x]))/255.0f); field.cf[y*x_res+x/3].SetG(((float)(pixelStream[y*x_res*3+x+1]))/255.0f); field.cf[y*x_res+x/3].SetB(((float)(pixelStream[y*x_res*3+x+2]))/255.0f);Deine Farbkanäle habe laut deiner Aussage 4 Byte (was ungewöhnlich viel klingt, bist du sicher?), hier springst du aber pro Kanal nur 1 Byte weiter.
-
Ja also ich habe natürlich um mir die arbeit einfach zu machen, erstmal auch versucht einfach zu sagen "übertrage die ganze Datenstruktur" - fertig. aber das hat letztlich wie gesagt dazu geführt dass ich feststellen musste, Bilddaten am anfang des puffers wurden von folgenden Sendungen überschrieben. Dies fand ich schon ziemlich dämlich da ich den Puffer groß genug für das ganze Bild festgelegt hatte. dies bestätigte sich dann als ich eine kleinere Größe übertragen lies, dann hatte ich schon mal Bilddaten, die aber wie gesagt nicht stimmen. Ich wunderte mich nämlich warum nur 0-Werte ankamen und mutmaßte, dass die 2 Hälfte des Bildes, die beim Client schwarz ist, da er sie nicht berechnet, diese den Puffer bei mehreren Sendungen überschrieben, das heisst man muss die Daten nach jedem Read vom Netz auswerten und zwischenspeichern.
Was die Farbkanäle anbetrifft. Es wird pro Farbe ein Float verarbeitet. Man kann z.b. bei OpenGL mit FloatFunktionen mit Werten zwischen 0..1 arbeiten oder ByteFunktionen. Bei der Netzwerkprogrammierung fiel mir wegen der Übertragung von Bytes dies als Nachteil auf. Arbeitet man mit Floats hat man pro Farbe 4 das sind also 12 Byte. Arbeitet man mit Bytes braucht man nur 3. Ich springe auch nicht um ein Byte weiter sondern um ein PixelObjekt. Und da ich von Bytes wieder in Floats umwandeln muss, verwende ich die 3 Bytes in Folge und rechne sie in Floats um die wieder in einem Pixelobjekt gespeichert werden. Ich werde noch nen Link posten zu den Bildern, dann kann man sich die Ergebnisse mal angucken. Was halt sehr komisch ist, dass als ich das Programm am nächsten Tag gestartet hatte ohne was dran zu ändern, die gesendeten Bilddaten noch mehr Fehler aufwiesen und wie gesagt, auf der Kommandozeile stimmen die Werte auch auf den beiden Rechnern nicht überrein auf den ersten Blick.
-
dir ist klar das TCP/IP ein Stream ist d.h. die Daten kommen nicht zwangsläufig als 1 Read so wie du sie gesendet hat - es kann sein das es x Pakete werden bis dein Sendung vollständig empfangen wurde
schreib doch erstmal ein kleines Testprogramm das nur Testdaten sendet und damit validierst du erstmal deine Kommunikation - nicht das noch aus anderen Richtungen Probleme kommen
-
Also ich habe jetzt noch folgendes ausprobiert. Ich habe statt der Pixelobjekte den Pixelstream, den ich versende, am Client ausgegeben... da wird er fast komplett richtig angezeigt. bei Reflexionen im Bild scheint es noch kleine Unstimmigkeiten zu geben. Aber auf dem Server wird nach jedem Durchlauf ein anderes bild angezeigt.
-
so bin jetzt nochmal ein Stück weiter gekommen. Es gibt in dem Readhandler ein Parameter der beschrieben wird nach jedem Read der lautet bytes_transferred. Damit lässt kann ich den Pixelstream scheinbar schonmal halbwegs korrekt abspeichern. Ich erhalte jetzt ein Bild wo die Muster schon mal korrekt sind, aber die Farben noch nicht stimmen. Und wenn ich das Programm noch mal laufen lasse, kriege ich auch die gleichen Ergebnisse. Reflexionen und Objekte sind schon mal an der richtigen Position. Es ist also doch ganz wichtig, sich hier an diesen Parameter bei größeren Datenmengen zu halten, anstelle davon auszugehen, dass der Buffer von ganz allein korrekt beschrieben wird.
-
Klingt so, als würdest du bei der Serialisierung und/oder Deserialisierung deines Bildformats einen Fehler machen. Hat vermutlich etwas mit dem Zeug zu tun, das du da mit den floats anstellst.
-
joa habs gelöst. sogesehen war es ein datentyp Umwandlungsfehler und das Problem beim asynchronen Senden im Netz, wodurch unterschiedlich viele Bytes je Durchlauf versendet worden sind, ich aber immer grundsätzlich davon ausging, dass 1460 Bytes ankämen. Man kann sich bei Boost wenn man sagt, transferiere 1460 Bytes, nicht darauf verlassen dass diese auch in einem Zug empfangen werden. Bei größeren Datenmengen muss man auf jeden Fall den Paramter bytes_trasferred auswerten im Read-Handler, der sich ja immer wieder aufgerufen wird. Ich hatte schließlich nachdem ich versucht hatte, Werte von 0..255 die in den Wertbereich, durch den erst als Char Buffer festgelegten Puffer, -127..128 rutschen wieder zurück zu wandeln, einfacher war es aber einfach das ganze als unsigned char festzulegen. Jetzt läuft es, bis auf ein paar Pixel ist alles korrekt. Und jetzt brauch ich nur noch ne Netzwerkfarm und kann auf beliebig vielen Rechnern nach ein paar Frontend-Anpassungen mit späterem zentralen Deployment und Steuerung zu der totalen Netzwerkapp machen

-
Durch nicht Auswertung der transferred_bytes kamen also Bildverschiebungen zustande, durch den verschobenden Wertebereich Farbfehler.
-
Jetzt, wo du diese Fehler gefunden hast, machst du es am besten noch einmal so, wie du es am Anfang hattest, also ohne dein eigenes Streamingprotokoll über dem eigentlichen Streamingprotokoll zu implementieren.
-
[quote]Man kann sich bei Boost wenn man sagt, transferiere 1460 Bytes, nicht darauf verlassen dass diese auch in einem Zug empfangen werden.
das liegt nicht an Boost sondern an dem TCP/IP-Protokoll, jeder TCP/IP-Stack zeigt dieses Verhalten (egal ob Windows, Linux,...) - das ist so gewollt und voellig normal
Bei größeren Datenmengen muss man auf jeden Fall den Paramter bytes_trasferred auswerten
man muss IMMER den bytes_trasferred Parameter auswerten - mit der Datenmenge hat das nichts zu tun, sollte deine TCP/IP-Verbindung ueber ein sehr schlechte Leitung laufen kann das auch (überspitzt) byteweise raus/reintröpfeln
-
noch Tips zur Performanz-Steigerung:
-der Netzwerk-Transport koennte z.B. auch ueber UDP laufen
da koennte die Latenz geringer sein-mit mehreren Threads in deinem Prozess rechnen (Multi-Threading)
-deine Rechnung auf mehrere Prozesse aufteilen (Multi-Processing) - weil jeder Prozess bekommt vom Scheduler ja nur die CPU-Zeit x - mehrere Prozesse lasten deine CPU besser aus