4 Stimmen

Streaming über eine TCP/IP-Verbindung

Ich stoße ständig auf eine Situation, in der ich eine Reihe von Nachrichten über eine TCP/IP-Verbindung senden muss. Ich habe nie eine gute Lösung für das Design der Nachrichtenklasse gefunden. Ich würde gerne eine Basisklasse für Nachrichten haben, von der alle Nachrichten abgeleitet sind. Da jede Nachricht unterschiedliche Felder haben wird, würde dies es mir ermöglichen, auf die Felder über Member-Variablen oder Methoden zuzugreifen. Etwas in der Art von...

class message_base
{
  public:
   message_base();
   virtual ~message_base();

   unsigned int type;
};

class message_control : public message_base
{
  public:
   message_control();
   virtual ~message_control();

   unsigned int action;
};

Dadurch kann ich eine message_control erstellen und auf das action-Mitglied zugreifen, um zu lesen und zu schreiben. Ich kann auch die Nachrichten weitergeben, ohne zu viel Code schreiben zu müssen.

Das Problem ergibt sich, wenn ich die Nachrichten senden muss. Wenn ich die Operatoren << und >> überschreibe, kann ich die Nachrichten über eine Variable nacheinander senden. Das Problem bei dieser Lösung ist, dass bei so vielen Aufrufen zum Senden von Daten die Kontextwechsel den Prozessor überlasten werden. Außerdem landet der Streaming-Operator in der Socket-Klasse und nicht in der Nachrichtenklasse, wo ich ihn lieber hätte.

socket& socket::operator<<(message_control& message)
{
   sock << type;
   sock << action;
}

Wenn ich die Daten in einem Puffer packe, entferne ich mich von C++ und bewege mich mehr in den Bereich von C und verwende großzügig Zeiger und ähnliches. Die Änderung des Codes ist schwierig und fehleranfällig. Außerdem ist der Streaming-Operator immer noch in der Socket-Klasse und nicht in der Nachrichtenklasse.

socket& socket::operator<<(message_control& message)
{
   byte* buffer = new byte[sizeof(message.type) + sizeof(message.action)];

   memcpy(buffer, message.type, sizeof(message.type));
   memcpy(buffer + sizeof(message.type), message.action, sizeof(message.action));

   sock.send(buffer);
}

Mein letzter Versuch verwendete eine Zwischenklasse, um das Packen und Entpacken der Elemente in einem Puffer zu handhaben. Die Nachrichten könnten die Operatoren << und >> für die Pufferklasse implementieren, und dann wird die Pufferklasse an die Socket gesendet. Das funktioniert, fühlt sich aber nicht richtig an.

class socket
{
  public:
   socket();
   ~socket();

   socket& operator<<(buffer& buff);
};

class buffer
{
  public:
   buffer() {m_buffer = new byte[initial_size];}
   ~buffer() {delete [] m_buffer;}

   buffer& operator<<(unsigned int value);

  private:
   byte* m_buffer;
};

void message_control::serialize(buffer& buff)
{
   buff << type;
   buff << action;
}

Ich kann nicht anders, als zu glauben, dass es eine elegante Lösung für dieses Problem gibt. Ich kann kein Designmuster finden, das dem entspricht, was ich zu erreichen versuche. Hat jemand dieses Problem erlebt und eine gute Lösung gefunden, die einen nicht das Gefühl gibt, dass man besser mit guten alten Zeigern und einem Array von Bytes auskommen würde?

Aktualisierung

In meinem ursprünglichen Beitrag habe ich nicht erwähnt, dass ich es in der Regel mit sehr gut definierten Drahtprotokollen zu tun habe. Deshalb benötige ich in der Regel eine eigene Lösung und kann keine der wunderbaren Toolkits verwenden, die für die Kommunikation über eine Netzwerkverbindung verfügbar sind.

1voto

Steve Jessop Punkte 264569

"Das Problem bei dieser Lösung ist, dass bei so vielen Aufrufen zum Senden von Daten die Kontextwechsel den Prozessor belasten werden. Außerdem landet der Streaming-Operator in der Socket-Klasse und nicht in der Nachrichtenklasse, wo ich bevorzugen würde, dass er sich befindet."

Die Lösung für das zweite Problem besteht darin, operator<< als Nicht-Mitgliedsfunktion im Namespace zu definieren, der die Nachrichtenklasse enthält, anstatt als Mitgliedsfunktion der Socket-Klasse. ADL wird es finden.

Die Lösung für das erste Problem besteht darin, die Daten im Prozess zu puffern und am Ende jeder Nachricht zu leeren. Wenn das Nagle-Puffern die Kontextwechsel nicht verhindert, könnten Sie dies möglicherweise durch Eingriffe in den Socket erreichen, ich weiß es nicht. Was Sie jedoch sicherlich tun können, ist jede Nachricht vor dem Senden auf eine mehr auf C++-Art vorzubereiten. Ersetzen Sie:

sock << type;
sock << action;

durch:

stringstream ss;
ss << type;
ss << action;
sock << ss.str();

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X