2017-04-20 2 views
1

In meinem System habe ich eine Reihe von TCP-Clients jonglieren und ich bin etwas verwirrt, wie man es gestaltet [die meisten meiner Erfahrung ist in C, daher die Unsicherheit] . Ich verwende Boost ASIO für die Verbindungsverwaltung. Dies sind die Komponenten Ich habeC++ Design: Mehrere TCP Clients, Boost Asio und Beobachter

  • A TCPStream Klasse: dünne Hülle über Boost Asio
  • ein IPC-Protokoll, das ein Protokoll über TCP implementieren: grundsätzlich Jede Nachricht beginnt mit einem Typ und Längenfeld so können wir lese die einzelnen Nachrichten aus dem Stream.
  • Verbindungsklassen, die die Nachrichten
  • Observer Klasse handhaben, die Verbindungen

Ich schreibe Pseudo-C++ Code sein, präzise überwacht. Ich denke, Sie bekommen die Idee wird

class TCPStream { 
    boost::asio::socket socket_; 
public: 

    template <typename F> 
    void connect (F f) 
    { 
     socket_.connect(f); 
    } 

    template <typename F> 
    void read (F f) 
    { 
     socket_.read(f); 
    } 
}; 

class IpcProtocol : public TCPStream { 
public: 
    template <typename F 
    void read (F f) 
    { 
     TCPStream::read(
       [f] (buffer, err) { 

       while (msg = read_indvidual_message(buffer)) { 
         // **** this is a violation of how this pattern is 
         // supposed to work. Ideally there should a callback 
         // for individual message. Here the same callback 
         // is called for N no. of messages. But in our case 
         // its the same callback everytime so this should be  
         // fine - just avoids some function calls. 
         f(msg); 
       }; 
       }; 
     ) 
    } 
}; 

Lets sagen, dass ich eine Reihe von TCP-Verbindungen haben und es gibt eine Handler-Klasse für jede der Verbindung. Wir nennen es Connection1, Connection2 ...

class Connection { 
    virtual int type() = 0; 
}; 

class Connection1 : public Connection { 

    shared_ptr<IpcProtocol> ipc_; 

    int type() 
    { 
     return 1; 
    } 

    void start() 
    { 
     ipc_.connect([self = shared_from_this()](){ self->connected(); }); 

     ipc_.read(
      [self = shared_from_this()](msg, err) { 

       if (!err) 
        self->process(msg); 
       } else { 
        self->error(); 
       } 
      }); 
    } 

    void connected() 
    { 
     observer.notify_connected(shared_from_this()); 
    } 

    void error() 
    { 
     observer.notify_error(shared_from_this()); 
    } 
}; 

Dieses Muster wiederholt sich für alle Verbindungen auf die eine oder andere Weise. Nachrichten werden von der Verbindungsklasse selbst verarbeitet. Aber es wird andere Ereignisse [connect, error] von an einen Beobachter weitergeben. Der Grund -

  1. Starten Sie den Anschluss, jedes Mal wenn sie trennen
  2. Bündel Jungs wissen muss, wenn die Verbindung hergestellt ist, so dass sie anfängliche Anforderung/konfguration zum Server senden können.
  3. Es gibt Dinge, die auf den Verbindungsstatus von muliple Verbindungen basieren getan werden muss, ZB: wenn verbindung1 und Anschluss2 etabliert sind, dann beginnen Anschluss3 usw.

ich eine mittlere Observer-Klasse hinzugefügt gibt es so, dass die Beobachter müssen sich bei jedem Neustart direkt mit der Verbindung verbinden. Bei jedem Verbindungsabbruch wird die Verbindungsklasse gelöscht und eine neue erstellt.

class Listeners { 
public: 
    virtual void notify_error(shared_ptr<Connection>) = 0; 
    virtual void notify_connect(shared_ptr<Connection>) = 0; 
    virtual void interested(int type) = 0; 
}; 


class Observer { 
    std::vector<Listeners *> listeners_; 
public: 

    void notify_connect(shared_ptr<Connection> connection) 
    { 
     for (listener : listeners_) { 
      if (listener->interested(connection->type())) { 
       listener->notify_error(connection); 
      } 
     }  
    } 
}; 

Jetzt funktioniert ein grober Prototyp davon. Aber ich frage mich, ob dieses Klassen-Design gut ist. Es gibt mehrere Streaming-Server, die kontinuierlich Zustände erzeugen und sie an mein Modul senden, um den Status in h/w zu programmieren. Dies muss erweiterbar sein, da in Zukunft weitere Clients hinzugefügt werden.

Threading

Der Legacy-Code ein Thread pro TCP-Verbindung hatte und das hat gut funktioniert. Hier versuche ich mehrere Verbindungen auf demselben Thread zu behandeln. Immer noch werden mehrere Threads ioservice aufrufen. Der Beobachter wird also auf mehreren Threads laufen. Ich plane, einen Mutex pro Listener zu haben, so dass Listener nicht mehrere Ereignisse gleichzeitig erhalten.

Antwort

0

HTTP Implementiert ein Protokoll über TCP so den HTTP-Server asio examples ein guter Ausgangspunkt für Ihr Design, vor allem: HTTP Server 2, HTTP Server 3 und HTTP Server 4.

Hinweis: Diese Verbindungslebensdauer ist wahrscheinlich ein Problem, insbesondere da Sie die Verwendung von Klassenmemberfunktionen als Handler beabsichtigen, siehe die Fragen und Antworten hier: How to design proper release of a boost::asio socket or wrapper thereof.

+0

Ein Blick auf das HTTP-Beispiel half. Danke für den Zeiger. Wie für die Lebenszeit. Die Verbindung gibt shared-ptr an sich selbst zurück, wenn sie eine asynchrone Operation ausführt, um sie am Leben zu halten, bis der asynchrone Beendigungshandler aufgerufen wird. Siehst du Löcher darin? – MGH

+0

Ja @MGH Ich sehe Löcher, insbesondere: Speicher und Ressourcenlecks. Ich ziehe es vor, dass der Server oder Client die shared-ptr * besitzt * und ein schwaches ptr an die Verbindung übergibt, indem ein 'non-member' (oder 'static') - Funktionsrückruf verwendet wird. Siehe zum Beispiel die Verbindungsklasse [hier] (https://github.com/kenba/via-httplib/tree/master/include/via/comms). – kenba

+0

Ich schaute auf die 'Klasse Verbindung'. Ich sehe nur ein paar Unterschiede mit dem, was ich mache 1. statische 'create' Routine, die den gemeinsamen Zeiger erstellt. Ich denke, die Idee ist, die Verbindung zu erzwingen wird immer als gemeinsamer Zeiger erstellt? (Nicht sicher, warum 'make_shared' im' create() 'verwendet wird) - okay. Aber meiner ist ein Pseudo-Code, um die Klassenbeziehung zu zeigen. Ich habe die Details übersprungen. – MGH