C++ Sockets unter Ubuntu.



  • Hi,

    ich muss (beziehungsweise habe schon) eine Socket-Schnittstelle für die Interprozesskommunikation unter Ubuntu einbauen.

    Ich übergebe darin 2 Vektoren unterschiedlicher Größe, die ich nacheinander sende. Das funktioniert momentan solange wie Prozess A und B abwechselnd lesen und schreiben. Wenn aber A zunächst zwei mal schreibt liest B das als eine Nachricht aus und der Prozess bleibt hängen weil noch eine zweite Antwort erwartet wird.

    Folgende Möglichkeiten fallen mir ein:

    1. Kann man auf der Senderseite irgendwie prüfen ob der Puffer schon leer ist, die Nachricht also schon empfangen wurde?
    2. Muss ich auf der Empfängerseite den ganzen Puffer auslesen und dann selber schauen an welchen Stellen des Streams Vektor A aufhört und Vektor B anfängt? Also ob beispielsweise noch eine Nachricht folgt?
    3. Soll ich mit Mutex oder ähnlichem arbeiten? Ich hoffe mal ich habe mich verständlich ausgedrückt 🙂
    4. Alles serialisieren und auf einmal schicken (Vektoren sind recht klein, maximal 10 Einträge).
    5. Alternativer Vorschlag?

    Was würdet ihr denn empfehlen? Wie ist das denn wenn im Puffer beispielsweise 2 Bytes sind und ich nur eins auslese? Wird das zweite Byte verworfen weil der Puffer voll ist oder kann ich das auch noch hinterher auslesen? Wenn das funktionieren würde wäre es denke ich die einfachste Lösung. Habe leider keine Möglichkeit das hier gerade auszuprobieren.

    LG, mike.



  • 1.) Nein.
    2.) Ja, vermutlich.
    3.) Nein, hast du nicht. Aber lass es lieber.
    4.) Abhaengig von der Applikation. Keine Ahnung.
    5.) Keiner.



  • Ehm, du nutzt doch sicher TCP und nicht UDP, oder? Da kannst du dich auf Pakete mal so gar nicht verlassen, wie kommst du darauf? Natürlich schickt man zu erst 1,2,4 oder 8 byte als Länge des Vektors (musst du festlegen - wenn's so kleine Werte sind, reichen 1-2 byte wahrscheinlich) und dann empfängst du halt den Vector. TCP-Verbindungen sind ein Stream, keine einzelnen Pakete.



  • Du musst definitiv ein Protokoll definieren, bei dem klar ist, wann die Nachricht zu ende ist. Eine Möglichkeit und recht üblich ist tatsächlich ein Längenwort voraus zuschicken. Eine andere Möglichkeit ist ein Serialisierungsformat zu verwenden, wie zum Beispiel XML. Da kann der Empfänger dann bis zum Ende-Tag des XML-Dokumentes lesen.

    Und mit Ubuntu hat das im Prinzip nichts zu tun. Das gleiche Problem taucht immer in der Netzwerkkommunikation auf.

    Wir haben hier eine Bibliothek mit einem Serialisierungsframework und darauf ein rpc Framework. Das rpc Framework kann unterschiedliche Protokolle verwenden wie beispielsweise xmlrpc. Da wird es dann ganz einfach, wenn man so was fertiges hat.



  • Also ich habe mich wohl ein bißchen undeutig ausgedrückt :).

    Hier mal der Pseudo-Code auf der Senderseite:

    Sende(Array mit Vector-Längen)
    Sende(Vector1)
    Sende(Vector2)
    

    Und hier der Empfänger:

    Empfange(Array mit Vector-Längen)
    Empfange(Vector1)
    Empfange(Vector2)
    

    Es kann jetzt passieren, dass die Längen ausgetauscht werden und der Sender beide Vektoren versendet bevor der Empfänger den Stream ausliest. Das hat zur Folge, dass der Empfänger beide Vektoren mit einem Empfange()-Aufruf ausliest. Dann bleibt er bei Empfange(Vector2) hängen weil er eben noch eine Nachricht erwartet. Ich habe jetzt zwei Möglichkeiten:

    1. Ich prüfe nach dem Empfangen die Byte-Größe und schaue ob ich mehr als Vector1 empfangen habe.
    2. Ich stelle den Empfangs-Puffer auf die Größe von Vector1 ein und lese mit Empfange() daher auch nur den ersten Vector aus. Anschließend kann ich dann noch den zweiten Vektor empfangen.

    Bei 2. bin ich mir nicht sicher ob das dann einen Pufferüberlauf gibt und Vector2 verworfen wird. Kann mir das jemand sagen? Wenn es nicht so ist ist es denke ich die einfachste Möglichkeit.

    Wie sieht das eigentlich Performanzmäßig aus? Ist es schneller die (kleinen) Vektoren hintereinander in den Speicher zu kopieren und mit einer Socket-Nachricht zu verschicken oder mehrere Nachrichten zu verwenden?



  • Wo soll da bei 2. ein Pufferüberlauf stattfinden? O_o



  • Mein Puffer ist so groß wie ein Vektor, es sind aber schon zwei gesendet worden.



  • Wieso versendest Du statt "2x Länge - Vektor1 - Vektor2" nicht "Länge + Vektor1" und "Länge + Vektor2"? Dann hättest Du das Problem nicht.



  • Wenn Du deine "Empfange()"-Funktion richtig implementierst gibt es keinen Pufferüberlauf. Deine Vorgehensweise entspricht in etwa der einfachsten vorgeschlagenen Lösung. Also ein Längenwort (bzw. bei Dir 2) und die Daten hinterher. Und wenn der Client zuerst die Länge liest und dann genau so viele Bytes, wie die Länge angibt, dann funktioniert das. Wenn Du dabei einen Pufferüberlauf bekommst, hast Du es einfach nur falsch implementiert.

    Ich empfehle eine Pufferungsschicht einzubauen. Wenn Du dann das Längenwort liest, dann liest die Pufferungsschicht so viel Daten, wie am Socket anliegen und liefert nur das angeforderte Längenwort. Der nächste Aufruf wird dann aus den bereits gelesenen Daten versorgt.

    Das gleiche gilt für die Senderseite. Du fragst Dich, ob Du erst die Daten zurecht legen sollst und dann senden oder stückchenweise senden? Pufferung löst das ganze. Die obere Schicht schreibt ein Längenwort (bzw. 2 bei Dir) in den Puffer und dann übergibt es die Daten. Wenn es fertig ist, dann sagt sie der Pufferungsschicht, dass er fertig ist. Das nennt man in der regel "flush". Die PUfferungsschicht hat eine feste Puffergrösse, wie beispielsweise 8k. Immer wenn sie voll wird, wird geschickt. Und wenn dann ein flush kommt, dann wird der Rest geschickt.

    Der Vorteil ist, dass die Applikationsschicht die Daten logisch so verarbeiten kann, wie es sinnvoll erscheint. Byteweises lesen beispielsweise ist dann kein Problem, da die Pufferungsschicht dieses ineffiziente byteweise lesen in größere Blöcke übersetzt.

    Aber das ganze musst Du nur beachten, wenn Du wirklich alles zu Fuß programmieren willst. Besser ist es, fertige Module zu verwenden. Eventuell mit einer höheren Abstraktionsschicht. Wie gesagt verwenden wir eine Bibliothek, die vieles abnimmt. Da machen wir uns in der Applikation keine Gedanken mehr, ob wir ein Längenbyte schicken, sondern wir schicken einfach Datenstrukturen. Die Bibliothek hat sich um das Protokoll zu kümmern. Auch diese ganze Pufferung geschieht dort in den unteren Ebenen.



  • Wieso versendest Du statt "2x Länge - Vektor1 - Vektor2" nicht "Länge + Vektor1" und "Länge + Vektor2"? Dann hättest Du das Problem nicht.

    Das ist doch voellig egal. Es darf halt nur soviel ausgelesen werden wie Vector1 lang ist.

    Wie wird gesendet und empfangen (Code bitte)?



  • Habe es jetzt umgeschrieben und er läuft wesentlich besser. Ab und zu bleibt er trotzdem noch hängen. Vielleicht findet ja jemand den Fehler oder sagt mir wie ich das Programm verbessern kann. Als Socket-Typ benutze ich AF_LOCAL.

    Funktion zum Senden:

    bool Socket::send(SocketMsg msg) const {
        // check size
        int size[5];
        size[0] = msg.functionType;
        size[1] = msg.doubleIO.size();
        size[2] = msg.doubleIO2.size();
        size[3] = msg.int64IO.size();
        size[4] = msg.boolIO;
        // Send size information
        int status = ::send(m_sock, size, sizeof(int)*5, 0);
        if(status == -1) {
            return false;
        }
        // Send first double vector
        status = ::send(m_sock, &msg.doubleIO[0], sizeof(double)*size[1], 0);
        if(status == -1) {
            return false;
        }
        // Send second double vector
        status = ::send(m_sock, &msg.doubleIO2[0], sizeof(double)*size[2], 0);
        if(status == -1) {
            return false;
        }
        // Send uint64_t vector
        status = ::send(m_sock, &msg.int64IO[0], sizeof(uint64_t)*size[3], 0);
        if(status == -1) {
            return false;
        }
    
        return true;
    }
    

    Empfangen:

    SocketMsg Socket::recv() const {
        SocketMsg msg;
    
        int size[5];
    
        // Receive function type
        int status = ::recv(m_sock, size, sizeof(int)*5, 0);
    
        if (status == -1) {
            std::cout << "status == -1   errno == " << errno << "  in Socket::recv\n";
            return msg;
        } else if(status == 0) {
            std::cout << "nothing received" << std::endl;
            return msg;
        }
    
        // initialize message
        msg.functionType = static_cast<FunctionType>(size[0]);
        msg.doubleIO = std::vector<double>(size[1]);
        msg.doubleIO2 = std::vector<double>(size[2]);
        msg.int64IO = std::vector<uint64_t>(size[3]);
        msg.boolIO = size[4];
    
        // calculate total packet size
        int packetSize = sizeof(double)*size[1]
                + sizeof(double)*size[2]
                + sizeof(uint64_t)*size[3];
    
        char buffer[5000];
        int received = 0;
    
        while (received < packetSize) {
            status = ::recv(m_sock, &buffer[received], packetSize, 0);
            if (status == -1) {
                std::cout << "status == -1   errno == " << errno << "  in Socket::recv\n";
                msg.functionType = Undefined;
                return msg;
            }
            received += status;
        }
    
        // reassamble message
        memcpy(&msg.doubleIO[0], &buffer[0], sizeof(double)*size[1]);
        memcpy(&msg.doubleIO2[0], &buffer[sizeof(double)*size[1]], sizeof(double)*size[2]);
        memcpy(&msg.int64IO[0], &buffer[sizeof(double)*size[1]+sizeof(double)*size[2]], sizeof(uint64_t)*size[3]);
        return msg;
    }
    

    Die zwei Funktionen sollen von Sender und Empfänger quasi immer abwechselnd aufgerufen werden. Sieht da jemand einen Fehler? Ansonsten müsste ich ihn an einer anderen Stelle suchen.



  • So spontan fällt mir erst mal auf, dass Du mit delete einen Puffer löschst, der auf dem Stack liegt. Außerdem prüfst Du beim ersten recv nicht, ob die Daten vollständig sind. Es könnte sein, dass Du nur ein Teil empfängst.

    Auf der Senderseite könnte das gleiche passieren. send könnte nur einen Teil der Nachricht senden. Dann geht dein Protokoll kaputt.

    Vielleicht hat Du meinen vorigen Kommentar gelesen. Umgesetzt hast Du das nicht.

    Es ist leichter und ganz automatisch aus effizienter, wenn Du verschiedene Schichten programmierst. Die Applikationsschicht schickt einfach nur vektoren. Die mittlere Protokollschicht fügt die notwendigen Protokollinformationen wie die Vektorlängen und so hinzu. Die Netzwerkschicht empfängt einfach nur Bytes und verschickt sie und kümmert sich um die Pufferung. Und auf der anderen Seite genauso.

    Architektonisch ist es falsch, wenn Deine Klasse mit dem Namen "Socket" irgendetwas über "functionType" oder so was weiß. Socketkommunikation ist schwer genug.

    Kleinigkeiten, die Du nicht berücksichtigt hast sind auch so was wie EINTR. Ein Systemaufruf könnte immer durch ein Signal unterbrochen werden. In der Regel sollte der Aufruf wiederholt werden.

    Und nochmal: Nimm eine fertige Library. Es ist echt einfacher. Unter Ubuntu findest Du in den Repositories beispielsweise boost oder mein cxxtools. Poco ist wohl auch dabei.





  • Ja das mit dem delete kannst du ignorieren. Ich hatte hier kurz was im Code geändert und es vergessen hier im Formular anzupassen.

    Ich hatte am Anfang mal überlegt boost zu verwenden, aber dann gelesen, dass es für so kleine Probleme wohl ziemlich überdimensioniert sei. Dann begebe ich mich mal auf die Suche nach ner gescheiten Library..



  • Warum bitte schreibst du erst in einen Puffer auf dem Stack, nur um dann die Daten zu kopieren? Hä?



  • Also ich habe dann doch noch meine Lösung etwas weiterentwickelt und sie funktioniert jetzt soweit. Sie muss nicht perfekt sein, ich brauche sie nur um mein Projekt zu evaluieren. Wenn das ohne Crash durchläuft ist es in Ordnung :). Es war sowieso nur eine Notlösung weil ich zwei Anwendungen nicht verknüpfen konnte und es daher über IPC machen musste.

    Und @tntnet: Ja ich stimme dir zu, dass der Socket den allgemeinen Datentyp nutzen sollte (wie hier jetzt nur über den Char-Pointer). Es erben allerdings SocketClient und SocketServer von dieser Klasse und ich fand es daher "einfacher" das so zu machen. Jetzt gibt es ja wenigstens noch zusätzlich die allgemeinere Schnittstelle :). Der Hinweis, dass der Sendevorgang unterbrochen werden kann hat auch sehr geholfen! Ich bin eigentlich davon ausgegangen, dass so eine Systemfunktion ein atomarer Vorgang ist (also ich dachte er kann vom BS unterbrochen werden, wird dann aber fortgesetzt und wird nach außen ganz normal beendet).

    Jedenfalls danke an alle für eure Hilfe.

    Hier ist mal der Code:

    bool Socket::send(char* dataPointer, int dataSize) const {
        int sent = 0;
    
        while(sent < dataSize) {
            int status = ::send(m_sock, dataPointer+sent, dataSize-sent, 0);
    
            if(status == -1 && status != EINTR) {
                return false;
            }
            sent += status;
        }
    
        return true;
    }
    
    bool Socket::send(SocketMsg msg) const {
        int size[5];
        size[0] = msg.functionType;
        size[1] = msg.doubleIO.size();
        size[2] = msg.doubleIO2.size();
        size[3] = msg.int64IO.size();
        size[4] = msg.boolIO;
    
        bool success = true;
    
        send(reinterpret_cast<char*>(&size[0]), sizeof(int)*5) ? true : false;
        send(reinterpret_cast<char*>(&msg.doubleIO[0]), sizeof(double)*size[1]);
        send(reinterpret_cast<char*>(&msg.doubleIO2[0]), sizeof(double)*size[2]);
        send(reinterpret_cast<char*>(&msg.int64IO[0]), sizeof(uint64_t)*size[3]);
    
        // TODO: success flag
        return success;
    }
    
    int Socket::recv(char* buffer, int size) const {
        int received = 0;
    
        while(received < size) {
            int status = ::recv(m_sock, &buffer[received], size-received, 0);
            if (status == -1 && status != EINTR) {
                std::cout << "status == -1   errno == " << errno << "  in Socket::recv\n";
                return -1;
            }
            received += status;
        }
    
        return received;
    }
    
    SocketMsg Socket::recv() const {
        SocketMsg msg;
    
        int size[5];
    
        // Receive function type
        recv(reinterpret_cast<char*>(&size[0]), 5*sizeof(int));
    
        // initialize message
        msg.functionType = static_cast<FunctionType>(size[0]);
        msg.doubleIO = std::vector<double>(size[1]);
        msg.doubleIO2 = std::vector<double>(size[2]);
        msg.int64IO = std::vector<uint64_t>(size[3]);
        msg.boolIO = size[4];
    
        recv(reinterpret_cast<char *>(&msg.doubleIO[0]), sizeof(double)*size[1]);
        recv(reinterpret_cast<char *>(&msg.doubleIO2[0]), sizeof(double)*size[2]);
        recv(reinterpret_cast<char *>(&msg.int64IO[0]), sizeof(uint64_t)*size[3]);
    
        // TODO: exception when return of recv is -1
        return msg;
    }
    

    PS: Spontane Verbesserungsvorschläge für den Code werden natürlich gerne angenommen.



  • Wenn es denn so spontan sein soll, dann hätte ich was: bei EINTR ist status = -1, also ziehst Du von den bisher empfangenen Bytes 1 ab. Das ist sicher nicht so gemeint. Mach einfach mal ein else vor dem received += status; .



  • if (status == -1 && status != EINTR) {
    

    Das ist Quatsch. Hast du mal die Dokumentation zu gelesen? recv() gibt niemals EINTR zurueck. Falls recv() fehl schlaegt wird errno gesetzt. errno ist abzufragen.



  • Oh ja, danke. So war eigentlich der Plan :).


Anmelden zum Antworten