Simple message bus: C++ and Qt

This is the C++/Qt implementation of the message bus program. The principles of the message bus are described in the main message bus article. Do read that article first.

Here I have used Qt’s abstractions for socket, so sorry, no down-and-dirty socket()/bind()/accept() code.

The source code

This source is split in two files, since Qt wants a header file to scan; putting the class declarations in the .cc file does not work. And anyway, it is good style, and more readable, to put declarations is a separate header file.

The Qt project file

# -*- mode: Makefile; coding: us-ascii-unix -*-

QT      += core
QT      += widgets
QT      += network

HEADERS += redgreenblue.h

SOURCES += redgreenblue.cc

Header file

// -*- coding: us-ascii-unix -*-

# pragma once
# include <qobject.h>
# include <qwidget.h>

class QTcpServer;
class QTcpSocket;
class QTextStream;
class QPushButton;

class Server : public QObject {
    Q_OBJECT

  public:
    Server(int port, QObject *parent = 0);
   ~Server();
    void accept_connection();

  private:
    QTcpServer *_tcp_server = 0;
};

class Worker : public QObject {
    Q_OBJECT

  public:
    Worker(QTcpSocket *s, Server *parent = 0);
   ~Worker();
    void write_current_colour();

  private:
    void _read_from_client();

    QTcpSocket *_tcp_socket = 0;
};

class Client : public QObject {
  public:
    Client(int port);
    void send_message(char const *buf);
    static Client *instance;

  private:
    void _data_arrived();
    void _connection_closed();

    QTcpSocket *_tcp_socket = 0;
};

class Gui : public QWidget {
    Q_OBJECT

  public:
    Gui(QWidget *parent = 0);
    void on_incoming_message(char const *message);
    static Gui *instance;

  private:
    QPushButton *btn(char const *text, int id, Gui *parent);
    void         on_clicked();

    QPushButton *red   = 0;
    QPushButton *green = 0;
    QPushButton *blue  = 0;
};

Source file

// -*- coding: us-ascii-unix -*-
# include <redgreenblue.h>

# include <qapplication.h> //-- QApplication
# include <qtcpserver.h>   //-- QTcpServer
# include <qtcpsocket.h>   //-- QTcpSocket
# include <qhostaddress.h> //-- QHostAddress
# include <qevent.h>       //-- QEvent
# include <qlabel.h>       //-- QLabel
# include <qlayout.h>      //-- QLayout
# include <qpushbutton.h>  //-- QPushButton
# include <qbuttongroup.h> //-- QButtonGroup
# include <qtextstream.h>  //-- QTextSTream
# include <qpalette.h>     //-- QPalette
# include <qcolor.h>       //-- QColor

# include <set>

# include <assert.h>
# include <stdlib.h>

enum { RED = 1001, GREEN, BLUE };

class Server;
class Worker;
static struct {
    std::set<Worker *> clients;
    int                      current_colour  = RED;
} s;

//----------------------------------------------------------------------------------------

Server::Server(int port, QObject *parent)
  : QObject(parent),
    _tcp_server(new QTcpServer(this))
{
    if (_tcp_server->listen(QHostAddress::Any, port))
        assert(connect(_tcp_server, &QTcpServer::newConnection,  this, &Server::accept_connection));
}

Server::~Server() { }

void Server::accept_connection()
{
    printf("Incoming connection\n");
    while (QTcpSocket *s = _tcp_server->nextPendingConnection()) {
        printf("Adding a pending connection\n");
        (new Worker(s, this))->write_current_colour();
    }
}

//----------------------------------------------------------------------------------------

Worker::Worker(QTcpSocket *tcp_socket, Server *parent)
  : QObject(parent),
  _tcp_socket(tcp_socket)
{
    s.clients.insert(this);
    assert(connect(_tcp_socket, &QTcpSocket::readyRead,    this,        &Worker    ::_read_from_client));
    assert(connect(_tcp_socket, &QTcpSocket::disconnected, this,        &Worker    ::deleteLater      ));
    assert(connect(_tcp_socket, &QTcpSocket::disconnected, _tcp_socket, &QTcpSocket::deleteLater      ));
}

Worker::~Worker()
{
    s.clients.erase(this);
}

void Worker::write_current_colour()
{
    char buf[20];
    snprintf(buf, sizeof buf, "%d\n", s.current_colour);
    assert(strlen(buf) == 0LLU + _tcp_socket->write(buf));
}

void Worker::_read_from_client()
{
    char buf[20];
    auto count = _tcp_socket->QIODevice::readLine(buf, sizeof buf);
    assert(1 < count && count < 10);
    for (auto it : s.clients)
        assert(strlen(buf) == 0LLU + it->_tcp_socket->write(buf));
}

//-----------------------------------------------------------------------------

Client *Client::instance = 0;

Client::Client(int port)
  : _tcp_socket(new QTcpSocket(this))
{
    assert(0 == instance);
    _tcp_socket->connectToHost("localhost", port);
    printf("Connection opened\n");
    if (_tcp_socket->isOpen()) {
        assert(connect(_tcp_socket, &QTcpSocket::readyRead,    this, &Client::_data_arrived     ));  //dataAvailable(int)
        assert(connect(_tcp_socket, &QTcpSocket::disconnected, this, &Client::_connection_closed));
    } else {
        printf("******* Gui::open_connection(): Failed\n");
        exit(1);
    }
    instance = this;
}

void Client::_data_arrived()
{
    char buf[20];
    auto n = _tcp_socket->QIODevice::readLine(buf, sizeof buf);

    assert(1 < n && n < 10);
    for (--n; n >= 0 && buf[n] <= ' '; n--)
        buf[n] = 0;
    Gui::instance->on_incoming_message(buf);
}

void Client::_connection_closed() { exit(0); }

void Client::send_message(char const *buf)
{
    printf("Writing to server: [%s]\n", buf);
    _tcp_socket->write(buf);
}

//-----------------------------------------------------------------------------

Gui *Gui::instance = 0;

static struct {
    QPalette p_white;
    QPalette p_red;
    QPalette p_green;
    QPalette p_blue;
} s_gui;

Gui::Gui(QWidget *parent)
  : QWidget(parent                   ),
    red    (btn("Red",   RED,   this)),
    green  (btn("Green", GREEN, this)),
    blue   (btn("Blue",  BLUE,  this))
{
    assert(0 == instance);
    QBoxLayout   *buttons  = new QHBoxLayout();

    buttons->addWidget(red  );
    buttons->addWidget(green);
    buttons->addWidget(blue );

    s_gui.p_white = red->palette();
    s_gui.p_red   = s_gui.p_white;
    s_gui.p_green = s_gui.p_white;
    s_gui.p_blue  = s_gui.p_white;

    s_gui.p_red  .setColor(QPalette::Button, QColor(Qt::red  ));
    s_gui.p_green.setColor(QPalette::Button, QColor(Qt::green));
    s_gui.p_blue .setColor(QPalette::Button, QColor(Qt::blue ));

    this->setWindowTitle("C++ and Qt");
    this->setLayout(buttons);
    instance = this;
}

QPushButton *Gui::btn(char const *text, int id, Gui *parent) {
    QPushButton *rval = new QPushButton(text,   parent);
    rval  ->setMinimumSize(rval  ->sizeHint());
    rval  ->setProperty   ("id",  id);
    rval  ->setMinimumSize(100, 30);
    assert(QObject::connect(rval,   &QPushButton::clicked, parent, &Gui::on_clicked));
    return rval;
}

void Gui::on_clicked  () {
    char buf[20];
    auto var = sender()->property("id");
    int  id  = var.toInt();
    snprintf(buf, sizeof buf, "%d\n", id);
    Client::instance->send_message(buf);
}

void Gui::on_incoming_message(char const *message)
{
    int colour = atoi(message);
    red  ->setPalette(RED   == colour ? s_gui.p_red   : s_gui.p_white);     red  ->update();
    green->setPalette(GREEN == colour ? s_gui.p_green : s_gui.p_white);     green->update();
    blue ->setPalette(BLUE  == colour ? s_gui.p_blue  : s_gui.p_white);     blue ->update();
}

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    Server server(4711);
    Client client(4711);

    Gui *f = new Gui();
    f->show();

    app.exec();
    return 0;
}

You can reach me by email at “lars dash 7 dot sdu dot se” or by telephone +46 705 189090

View source for the content of this page.