Introduction
In one of my projects I needed to use udp multicast messaging to distribute notifications. I've found numerous articles about multicasting but these were mostly oriented towards an explanation of the technology with just simple code snippets demonstrating basic api calls.
My aim was to build a component which would provide reliable encapsulation of network interactions and that would expose multicast functionality to upper layers through publish/subscribe implementation of the observer pattern.
Requirements for message broker component:
- distribute string messages to anyone interested (observer pattern) using udp multicasting
- single component encapsulating both send & receive logic
- no machine specific configuration
- basic error detection
- thread-safe interaction
- detection of duplicate messages
- ability to handle fragmented data
- ability to recover from data received in unexpected format (e.g. network errors, interrupted transmission, etc.)
Background
IP multicasting is extensive topic which I don't want to cover here as there are already a lot of resources on the web. To mention just a few:
In the next sections I will focus on usage and implementation details of this component.
Using the code
Message broker consists of one class - MulticastMessageBroker
, just drop it where you need it.
In the following snippet is demonstrated basic interaction with message broker:
var multicastBroker = new MulticastMessageBroker(IPAddress.Parse("239.255.255.20"), 6000);
multicastBroker.Connect();
var subscriptionId = Guid.NewGuid();
multicastBroker.Subscribe(subscriptionId,
(m) =>
{
Console.WriteLine("Message received: {0}", m);
});
multicastBroker.SendMessage(subscriptionId, "hello!");
multicastBroker.ClearAllSubscribers();
multicastBroker.Disconnect();
In the downloads section there is also included a very simple console chat application built on top of message broker. With this you can quickly test multicasting functionality inside your network. The application connects to 239.255.255.20:6000 and allows you to send/receive multicast messages. It should work on the same machine (when you run multiple instances you can send messages between them) as well across multiple machines inside multicast-enabled network.
Implementation
Message broker implementation is based on UdpClient
from System.Net.Sockets
.
Following message format is used internally:
Guid:myself(sender) + Guid:uniqueMessageId + Guid:subscriptionId + int:messageLenght + byte[2]:CRC16 + string:message
To support detection & recovery from data received in an unexpected format, CRC check of header is included in the message. If this check doesn't pass after message header bytes are read from the incoming data stream, receiving pipeline is reset and broker continues to search for start of the message. CRC algo is not my own implementation, I've included first one that I've found which had required interface.
One of my requirements was that the broker should work straight out of the box on any machine without the need of configuration. Because of this, by default, broker transmits/receives on all network interfaces it finds on the machine. If you want to change this behaviour you can adjust the interface filter in JoinMulticastGroup
method. Currently the filter looks like this:
Dns.GetHostAddresses(Dns.GetHostName()).Where(i => i.AddressFamily == AddressFamily.InterNetwork)
There is one constructor:
public MulticastMessageBroker(
IPAddress multicastGroup,
int port,
int multicastTTL = 255,
int processedMessageHistoryCount = 30,
int maxSendByteCount = -1)
...
Each message carries unique id. To detect duplicate messages, message broker stores unique id of each received message into the list. If the received message unique id is in this list, the message is discarded. The length of this list can be controlled by processedMessageHistoryCount
. By setting value to -1, you can turn off duplicate message detection.
With maxSendByteCount
you can specify how many bytes will be sent at once (= one call to UdpClient.Send
method). This was used mainly for testing, but there was no reason to remove it, maybe it will be useful in some scenarios.
No time intensive work is done in the constructor. To connect to network and start receiving/sending messages you need to call Connect
method. Current connection status is indicated by IsConnected
property. Connect
/ Disconnect
methods can be called independently from Subscribe
/ Unsubscribe
methods.
To subscribe for specific feed you can use:
public void Subscribe(Guid subscriptionId, Action<string> callback)
When calling this method you specify callback which will be called when a message with given subscription id is received. Each callback is invoked on new thread taken from the thread-pool, so there is no delay in case one of callbacks takes longer time to finish. If you would like to change or adjust this you can do it in FinalizeMessage
method.
All of the public methods are thread-safe.
Points of Interest
There is one problem I came across when testing this through wifi connection on the battery powered laptop. I was able to send notifications, but reception seemed to be broken - sometimes I did not get messages sent from another computer in network. After some investigation I've found out that the problem was in the power profile for wifi adapter, after disabling power savings everything worked as expected.
History
- Version 1.0 - Initial release.