A message bus is a method for communicating between two or more programs.
Traditionally, programs communicate using pipes, message queues or sockets. These methods provide point-to-point communication between two parties. One party sets up an endpoint, and another party connects. The resulting connection, or link, has exactly two endpoints, and only two parties can communicate using that link. Of course, a program, such a a server of some kind, may have several such links at the same time, but each link is still point-to-point.
A message bus is different. It is a method for broadcasting messages from one or more broadcasters to one or more listeners. With a message bus, broadcasters and receivers do not need to know about each other. A broadcaster sends its messages to the message bus, without knowing who, if any, is listening. A listener listens for message without knowing what broadcasters are currently active.
Of course, broadcasters and listeners must agree on the structure and meaning of messages. Really simple messages could be just lines of text, one message per line. If a listeners sees a message it does not understand, it may simply ignore it or it may log an error message.
In this article I describe a simple message bus:
- The message bus is implemented by a TCP/IP server that listens on port 4711.
- Messages are plain text, one message per line terminated by a newline.
- All concected clients may send messages to the bus.
- All concected clients will receive all messages. This includes the client that sent the message.
- Clients may run in the same process as the server, in different processes, or on different computers.
A client is a broadcaster, a listener or both.
More advanced message busses may divide messages into categories, somtimes called channels. Each message must indicate which category it belongs to. This can be done by a simple text string at the start of the message. Listeners tell the message bus, using special messages that do not get broadcast, which categories it is interested in. This helps to limit network traffic, especially if many of messages would otherwise be discarded by clients that are not interested in them.
In this article, the message bus is used for synchronizing several instances of similar user interfaces. Each user interface has three buttons, labelled Red, Green and Blue. Each button has by default a white background. When the user clicks one of the buttons, a message ("1001", "1002" or "1003") is sent to all programs connected to the message bus. Receiving programs, including the program that send the message, changes to background colour of that button to red, green or blue, while setting the background of the other two to white.
I chose this behaviour because it makes it really easy to see if all programs are in sync.
As can be seen from the screenshots, there are several implementations:
|C++ and Qt||Client and server|
|Java and AWT||Client and server|
|Perl and Tk||Client and server|
|TCL and Tk||Client only, no server|
|Python and Tk||Client and server|
All except the Tcl one implements both the client part and the server parts (the server consists of two parts, see below). All the implementations work together.
The implementations are all similar. This section serves as a guide to the source.
From a programming perspective there are several parts to a message bus.
- The code that makes up the server, and the socket that it listens on.
- Server-side code that waits for and reads messages from the clients.
- Server-side code that forwards messages to connected clients.
- Client-side code that sends messages to the message bus.
- Client-side code that waits for and reads messages from the message bus.
In addition, there is the code that makes up the three-button graphical user interface.
Code is grouped into four classes
- Server is the socket on port 4711, and the code that listens for and accepts connections.
- Worker is the server-side socket and code for an established connection.
- Client is the client-side socket and code for an established connection, as well as the code that requests that connection.
- GUI is the three-button GUI.
Note that a Client is paired with a Worker, and not with a Server This is simply because the name Server is already taken by the entity that listens for incoming connections, in keeping with the socket naming tradition.
A number of functions appear in most implementations.
- GUI::on_clicked() A button has been clicked. Uses Client::notify_server() to send a message to the message bus.
- Server::accept_connection A client want’s to connect. It creates a Worker.
- Worker::read_from_client() Data sent from the client has arrived.
- Worker::write_to_client() Writes data to one client.Client::read_from_server() Data sent from the message bus has arrived.
- Client::notify_server() Sends a message to the message bus.
Technically, when I say Sends a message to the message bus what happens is that data is written to the client’s socket. That data (hopefully) arrives at the peer socket, belonging to a Worker running in the same process as the Server.
To keep examples short, code for each implementation is in a single file. The exception is the C++/Qt, where a separate header file is required by Qt.
Every program that starts tries to start a TCP server and bind it to the agreed-upon port 4711. If it succeeds then it acts as a server. If it fails, then it failes silently, assuming that there is a server already running.
It then connects to the server; even the program that runs the server connects to the server.
Upon connection the TCP server creates a new socket, and starts a worker, responsible for servicing that socket. The worker may be a separate thread (Java) or it may simple be one or more callback routines. The worker immediately transmits the current colour to the client.
How does the server know which initial colour to send to the client? It cheats, by looking at colour in the GUI in the same program. This is admittedly rather ugly. After cheating the first time, the server could cache the most recent message and use that for subsequent new connection, but actually, it cheats every time
When the user clicks a radio button:
- The GUI sends a message indicating the colour clicked.
- The worker at the other end of the message bus connection receives the message
- The message is broadcast to all connected clients, including the client that sent the message.
- Each client receives the message and updates its GUI.
The message is an ASCII string followed by a newline character. The string "1001" is red, "1002" is green and "1003" is blue. The length of the message is five bytes.
The message is transmitted as-is by the message bus. The server does not look at or try to interpret the message in any way.
Most, but not all, client implementations detect the loss of connection to the server. Loss of connection should only happen if the server process dies. When the connection is lost, clients simply exits.
Most, but not all, Worker implementations detect the loss of connection to its client, and hadle it gracefully Loss of connection should only happen if the client process dies.
I have not tried to iron out every wrinkle in every implementation. These examples are just that, not industrial-strength implementations of message busses.
That is about it. Have look at the various implementations. They are quite different, but they all work together.
You can reach me by email at “lars dash 7 dot sdu dot se” or by telephone +46 705 189090