I draw the following topology where each node is an object of class SensorNode
, the blue links indicate the links between each node with its neighbors where the circles around the nodes represent the transmission range for each node.
The sink also is an object of class Sink
.
I need to instantiate messaging and communication between them but I have no idea what mechanisms should I used to perform message passing between theses objects (sensor nodes) where each node has its unique ID, the sink has a fixed ID which is 1 in my code because I use only a single sink.
The following are the classes where I am still stuck to how to implement send receive, and forward in terms of making this communication applicable between these different objects...
Class "SensorNode"
namespace CRN_Topology
{
class SensorNode
{
public int snID;
public string snName;
public int snDepth;
public DateTime schedulingTime;
public double holdingTime;
public double energy;
public List<int> queue11 = new List<int>();
public List<DateTime> queue12 = new List<DateTime>();
public List<Packet> queue21 = new List<Packet>();
public List<DateTime> queue22 = new List<DateTime>();
public SensorNode(int id,string name,int depth, double energy)
{
this.snID = id;
this.snName = name;
this.snDepth = depth;
this.energy = energy;
}
public void insertHistoryQueue(int packetID, DateTime receivingTime)
{
queue11.Add(packetID);
queue12.Add(receivingTime);
}
public void insertPriorityQueue(Packet packet, DateTime schedulingTime)
{
queue21.Add(packet);
queue22.Add(schedulingTime);
}
public DateTime schedulingTimeCalculations(double holdingTime, DateTime systemTime)
{
schedulingTime = DateTime.FromOADate(holdingTime).Date + systemTime.TimeOfDay;
return schedulingTime;
}
public double holdingTimeCalculations(double alpha, double depth, double beta)
{
holdingTime = alpha * depth + beta;
return holdingTime;
}
public void receive(Packet packet)
{
}
public void forward(Packet packet, int neighborID)
{
}
public void remove()
{
}
public void sendDirect(int rxID, Packet packet)
{
}
}
}
Class "Sink"
namespace CRN_Topology
{
class Sink
{
public string name;
public int sinkID;
public int sinkX;
public int sinkY;
public List<Packet> queue1 = new List<Packet>();
public List<DateTime> queue2 = new List<DateTime>();
public Sink(string name, int Id , int xLocation, int yLocation)
{
this.name = name;
this.sinkID = Id;
this.sinkX = xLocation;
this.sinkY = yLocation;
}
public void insert(Packet packet, DateTime receivingTime)
{
queue1.Add(packet);
queue2.Add(receivingTime);
}
}
}
Any idea, I need your suggestions and your help as I do not have an idea how to pass information between these objects (sensor nodes) and between the sensor nodes and the sink. What is the library which is responsible for this application in C#?
You could use actual events. Yet, for this case IObservable and IObserver seems to provide a better pattern. Although while trying to implement this, I quickly moved away from that pattern.
Below is the solution I developed. What I present is an abstract class Node
indended to serve as base for SensorNode
and Sink
as both can recieve connections.
Edit 1: Or you may make it into its own thing and use composition, you may implement the abstract method Recieve
to raise a custom event.
Edit 2: Perhaps it is better to thing of the Recieve
method as Send
? I mean, in my code the intended implementation is to have it use _connections
to broadcast or try to get the Packet
to its destination, and do logging and whatever else. I don really know if that is what you indended for your Recieve
method.
abstract class Node
{
/// <summary>
/// Set of all the ids.
/// </summary>
private static readonly Dictionary<int, object> _nodes;
/// <summary>
/// The Id of the node.
/// </summary>
/// <remarks>Can't change.</remarks>
private readonly int _id;
/// <summary>
/// The connections of the node.
/// </summary>
protected readonly Dictionary<int, Node> _connections;
static Node()
{
_nodes = new Dictionary<int, object>();
}
protected Node(int id)
{
// Try register the Id provided
if (_nodes.ContainsKey(id))
{
// If we fail to add it, it means another Node has the same Id already.
throw new ArgumentException($"The id {id} is already in use", nameof(id));
}
_nodes.Add(id, null);
// Store the Id for future reference
_id = id;
_connections = new Dictionary<int, Node>();
}
~Node()
{
// Try to release the Id
// AppDomain unload could be happening
// Any reference could have been set to null
// Do not start async operations
// Do not throw exceptions
// You may, if you so desire, make Node IDisposable, and dispose including this code
var nodes = _nodes;
if (nodes != null)
{
nodes.Remove(Id);
}
}
/// <summary>
/// The Id of the Node
/// </summary>
public int Id { get => _id; }
/// <summary>
/// Connects nodes, bidirectionally.
/// Connect(x, y) is equivalent to Connect(y, x).
/// </summary>
/// <param name="x">The first node to connect</param>
/// <param name="y">The second node to connect</param>
public static void Connect(Node x, Node y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
// Bidirectional
x._connections[y.Id] = y;
y._connections[x.Id] = x;
}
/// <summary>
/// Disconnects nodes, bidirectionally.
/// Disconnect(x, y) is equivalent to Disconnect(y, x).
/// </summary>
/// <param name="x">The first node to connect</param>
/// <param name="y">The second node to connect</param>
public static void Disconnect(Node x, Node y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
// Short circuit
if (y._connections.ContainsKey(x.Id) && x._connections.ContainsKey(y.Id))
{
// Bidirectional
x._connections.Remove(y.Id);
y._connections.Remove(x.Id);
}
}
protected abstract void Recieve(Packet value);
}
Note: I didn't add anything to prevent a connection from a Node
to itself
I have left Recieve
abstract for you to implement. Sink
will probably just log the messages, while the SensorNode
will have to check the destination and fordward the message.
To send a message from a node to another, use the field _connections. The key is the
idof the connected
Node. Thefore, if you want to broadcast, you can iterate over
_connections`. If you want to run them in parallel, I have a thread-safe version below.
I have considered that you may need to attach information to the connections (such as wight / distance / cost / delay / latency). If that's the case, consider to create a Connection
class and make _connections
a dictionary of it. The pragmatic advantage it would have is that you may add the same Connection
object to both Nodes
and then updates to it will be visible to them both. Or just use Tuple
or add more dictionaries, whatever, I don't care.
It took me a while to figure out a good thread-safe implementation. If it used Monitor
it would block reads to the connections dictionary, which you need to do to send Pakets, so that's not good. A Read-Write lock is a bit better, but it may result in starving the Connect
and Disconnect
methods.
What I've come up with is a good old state machine. I added another dictionary to keep the state. Made all the dictionaries ConcurrentDictionary
to allow parallel operations and to be able to modify the state atomically.
The code is the following:
abstract class Node
{
/// <summary>
/// Set of all the ids.
/// </summary>
private static readonly ConcurrentDictionary<int, object> _nodes;
/// <summary>
/// The Id of the node.
/// </summary>
/// <remarks>Can't change.</remarks>
private readonly int _id;
/// <summary>
/// The connections of the node.
/// </summary>
protected readonly ConcurrentDictionary<int, Node> _connections;
/// <summary>
/// Status of the connection for synchronization
/// </summary>
private readonly ConcurrentDictionary<int, int> _connectionStatus;
private const int _connecting = 0;
private const int _connected = _connecting + 1;
private const int _disconnecting = _connected + 1;
static Node()
{
_nodes = new ConcurrentDictionary<int, object>();
}
protected Node(int id)
{
// Try register the Id provided
if (!_nodes.TryAdd(id, null))
{
// If we fail to add it, it means another Node has the same Id already.
throw new ArgumentException($"The id {id} is already in use", nameof(id));
}
// Store the Id for future reference
_id = id;
_connections = new ConcurrentDictionary<int, Node>();
_connectionStatus = new oncurrentDictionary<int, int>();
}
~Node()
{
// Try to release the Id
// AppDomain unload could be happening
// Any reference could have been set to null
// Do not start async operations
// Do not throw exceptions
// You may, if you so desire, make Node IDisposable, and dispose including this code
var nodes = _nodes;
if (nodes != null)
{
nodes.TryRemove(Id, out object waste);
}
}
/// <summary>
/// The Id of the Node
/// </summary>
public int Id { get => _id; }
/// <summary>
/// Connects nodes, bidirectionally.
/// Connect(x, y) is equivalent to Connect(y, x).
/// </summary>
/// <param name="x">The first node to connect</param>
/// <param name="y">The second node to connect</param>
public static bool Connect(Node x, Node y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
// Bidirectional
// Take nodes in order of Id, for syncrhonization
var a = x;
var b = y;
if (b.Id < a.Id)
{
a = y;
b = x;
}
if (a._connectionStatus.TryAdd(b.Id, _connecting)
&& b._connectionStatus.TryAdd(a.Id, _connecting))
{
a._connections[b.Id] = b;
b._connections[a.Id] = a;
a._connectionStatus[b.Id] = _connected;
b._connectionStatus[a.Id] = _connected;
return true;
}
return false;
}
/// <summary>
/// Disconnects nodes, bidirectionally.
/// Disconnect(x, y) is equivalent to Disconnect(y, x).
/// </summary>
/// <param name="x">The first node to connect</param>
/// <param name="y">The second node to connect</param>
public static bool Disconnect(Node x, Node y)
{
if (x == null)
{
throw new ArgumentNullException(nameof(x));
}
if (y == null)
{
throw new ArgumentNullException(nameof(y));
}
// Short circuit
if (!y._connections.ContainsKey(x.Id) && !x._connections.ContainsKey(y.Id))
{
return false;
}
// Take nodes in order of Id, for syncrhonization
var a = x;
var b = y;
if (b.Id < a.Id)
{
a = y;
b = x;
}
if (a._connectionStatus.TryUpdate(b.Id, _disconnecting, _connected)
&& b._connectionStatus.TryUpdate(a.Id, _disconnecting, _connected))
{
a._connections.TryRemove(b.Id, out x);
b._connections.TryRemove(a.Id, out y);
int waste;
a._connectionStatus.TryRemove(b.Id, out waste);
b._connectionStatus.TryRemove(a.Id, out waste);
return true;
}
return false;
}
protected abstract void Recieve(Packet value);
}
Threading chatter:
Connect
and Disconnect
must try to operate on the same order, which is why I have ordered by Id
. If they didn't operate on the same order, concurrent execution of Connect
and Disconnect
might result in unidirectional connection.
If you thread are trying to add the same connection, only one will success (due to TryAdd
). If two threads where trying to remove the same connection, only one will success (due to TryUpdate
). If the connection exists, Connect
will fail. If the connction doesn't exist Disconnect
will fail.
If Connect
and Disconnect
happen concurrently and the connection existed, Connect
will not be able to add it and fail unless Disconnect
manages to remove it first. If the connection didn't exist, Disconnect
will fail unless Connect
manages to add it first.
The status _connecting
and _disconnecting
are there to prevent Connect
and Disconnect
to see the situation where the connectin exist in one direction but not in the other.
No thread will ever have to wait for another thread to finish. And there is no need for syncrhonization when only reading _connections
.
While, in theory a thread that is only reading _connections
may be able to see a situation where a connection exists only in one direction, that thread will not be able to send Packet
in both directions at the same time. Thefore from the point of view of that thread, the connection was added or removed between its attempts to send Packet
.
There is no method to verify if a connection exists, such method would not be reliable because as soon as a thread checks a connection exists, another thread may remove the connection before the first one is able to use it. Preventing a connection from being removed was not part of the requirements, yet it can be added as another connection status.