Search code examples
c++oopqtdesign-patternssignals-slots

Design pattern for data exchange


I have a set of the virtual devices (D). Devices can be cross-connected (assume, physical connect):

D1 -- D2

D2 -- D3

Each link possibly will have different latency.

Each device contains a set of virtual sub-devices (s) which can generate some data. Sub-devices can be organized to logical connections. Sub-devices will transmitt real byte-data.

I use C++, Qt. I tried to use signal-slot mechanism, QSignalMapper, but I didn't find good solution.

Please, help me to build clear abstraction. Can I use any design pattern?


Solution

  • You should define a Link class simulating a physical link between your classes. The most interesting question is, how to connect your devices and simulate the delay using signal and slots?

    My suggestion is: Implement a send(QByteArray data) slot, which enqueues the data to an internal queue (simulating the wire) and sets a timeout to a given default delay with optional jitter. The timeout then fires a signal with the data popped from the queue.

    If you want to simulate routers between the devices, you should take into account that the more data is in the queue, the bigger the delays become, since retransmissions need to be done. To simulate this approximately, you could make the timeout value depending on the current queue length.

    Take this as a start:

    class Link : public QObject
    {
        Q_OBJECT
    
    public:
        Link(Device *from, Device *to) :
            QObject(to), m_from(from), m_to(to)
        {
            //make my life dependant on both "from" and "to" objects
            connect(from, SIGNAL(destroyed()), SLOT(deleteLater()));
    
            //connect to the signals and slots of the devices
            connect(from, SIGNAL(send(QByteArray,Device*)),
                    this, SLOT(  send(QByteArray,Device*)));
            connect(this, SIGNAL(receive(QByteArray,Device*,int)),
                    to,   SLOT(  receive(QByteArray,Device*,int)));
        }
    
    public slots:
        void send(QByteArray data, Device *receiver) {
            Message msg(data, 0, qobject_cast<Device*>(sender()), receiver);
            send(msg);
        }
        void send(Message msg) {
            msg.hops++; // here we increase the hops counter
            m_queue.enqueue(msg);
            QTimer::signalShot(m_delay, this, SLOT(timeout()));
        }
    
    signals:
        void receive(QByteArray data, Device *sender, int hops);
        void forward(Message);
    
    private slots:
        void timeout() {
            receive(m_queue.dequeue());
        }
        void receive(Message msg) {
            if(msg.receiver == m_to)
                // msg reached destination!
                emit receive(msg.data, msg.sender, msg.hops);
            else
                // forward to next link
                emit forward(msg);
        }
    
    private:
        static const int m_delay = 100; // delay set to 100 ms
        QQueue<Message> m_queue;
        Device *m_from, *m_to;
    };
    

    The type Message is defined as follows:

    struct Message {
        QByteArray data;
        int hops;
        Device *sender;
        Device *receiver;
        Message(data, hops, sender) : data(data), hops(hops),
                                      sender(sender), receiver(receiver) {}
    };
    

    Then just create devices and links like this:

    // Create devices:
    Device *d1 = new Device(this);
    Device *d2 = new Device(this);
    
    // Create link:
    Link *d1d2 = new Link(d1, d2);
    

    Or chained links with forwarding rules:

    // Create devices:
    Device *d1 = new Device(this);
    Device *d2 = new Device(this);
    Device *d3 = new Device(this);
    
    // Create links:
    Link *l1 = new Link(d1, d2);
    Link *l2 = new Link(d2, d3);
    
    // Create forwarding rule:
    connect(l1, SIGNAL(forward(Message)), l2, SLOT(send(Message)));
    

    Every data sent by d1 (when it emits the signal send(QByteArray)) will then be transferred with a delay of 100 ms to the slot receive(QByteArray) of d2. If the data wasn't for d2, the signal forward(Message) gets emitted, which has to be caught by another link (see forwarding rule). It then gets treated as a new incoming message and delivered to d3.

    Note that real networks don't function like that. You'd need to implement routing strategies to fully simulate such a setup; which is quite difficult.

    Also note that I didn't test this code. ;)

    This approach doesn't simulate splitting the data into small segments (of abount 1.5 KB each). To simulate a real Ethernet setup, you'd need to do this, too. Ask in the comments if you need to, and I may extend the class.