I am working on a .NET application which would enable pairs of computers located anywhere on the Internet (and usually behind NATs) to exchange messages between each other in parallel. I did some research for possible solutions and came to the conlsuion that maybe WCF duplex P2P connectivity would be suitable for what I need. Since I am new to WCF, I created a small test application and it seems to work well in general with one exception - peer which sends particular message to the other side also gets a call on its own interface and receives it back. Here is the code
using System;
using System.Threading.Tasks;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
[ServiceContract(CallbackContract = typeof(IDataChannel))]
public interface IDataChannel
{
[OperationContract(IsOneWay = true)]
void SendData(string data);
}
public class WCFP2PTransport : IDataChannel
{
private IDataChannel m_outChannel = null;
private DuplexChannelFactory<IDataChannel> m_factory = null;
private Task m_completion;
private string m_name;
public WCFP2PTransport(string service, string name)
{
m_name = name;
try
{
var binding = new NetPeerTcpBinding();
binding.Security.Mode = SecurityMode.None;
var endpoint = new ServiceEndpoint(
ContractDescription.GetContract(typeof(IDataChannel)),
binding,
new EndpointAddress("net.p2p://" + service));
m_factory = new DuplexChannelFactory<IDataChannel>(
new InstanceContext(this),
endpoint);
m_outChannel = m_factory.CreateChannel();
m_completion = Task.Factory.FromAsync(((ICommunicationObject)m_outChannel).BeginOpen, ((ICommunicationObject)m_outChannel).EndOpen, null);
}
catch (Exception ex)
{
Console.WriteLine(ex);
var tsk = new TaskCompletionSource<bool>();
tsk.SetException(ex);
m_completion = tsk.Task;
}
}
public Task ChannelOpened
{
get
{
return m_completion;
}
}
public void SendToPeer(string data)
{
m_outChannel.SendData(data);
}
// IDataChannel method(s), handle incoming traffic
public void SendData(string data)
{
Console.WriteLine("{0} Received data: {1}", m_name, data);
}
// cleanup code omitted for brevity
}
class Program
{
static void Main(string[] args)
{
var peer1 = new WCFP2PTransport("WCF_P2P_Test", "Peer1");
var peer2 = new WCFP2PTransport("WCF_P2P_Test", "Peer2");
Task.WaitAll(peer1.ChannelOpened, peer2.ChannelOpened);
peer1.SendToPeer("Packet1");
peer2.SendToPeer("Packet2");
Console.ReadKey();
}
}
Output of this application is
Peer1 Received data: Packet1
Peer2 Received data: Packet1
Peer1 Received data: Packet2
Peer2 Received data: Packet2
while I would like to see
Peer2 Received data: Packet1
Peer1 Received data: Packet2
Of course I can do some tricks in my application to filter out unwanted incoming traffic but before doing that I would like to know
Is it possible to configure WCF to not call the peer back in
response to its own call?
What is the best way to restrict peers to only connect in pairs and
each peer connecting to specific peer only? I can think of using
unique P2P service name for each pair that is why there is an
argument providing it to the peer class constructor?
You need the RemoteOnlyMessagePropagationFilter: when you create your Peer 2 Peer Channel:
var sourceContext = new InstanceContext(sourceObject);
var sourceFactory = new DuplexChannelFactory<TChannel>(sourceContext, endpointName);
TChannel sourceProxy = sourceFactory.CreateChannel();
var remoteOnlyFilter = new RemoteOnlyMessagePropagationFilter();
var peerNode = ((IClientChannel)sourceProxy).GetProperty<PeerNode>();
peerNode.MessagePropagationFilter = remoteOnlyFilter;
try
{
sourceProxy.Open();
}
catch (Exception)
{
sourceProxy.TryCloseOrAbort();
throw;
}
The most immediate answer I can think of would be to use an identification scheme and make sure you aren't sending to yourself. Once you've identified all the peers you are connected to (a managed list maybe?) just iterate over that and send the packets to each one.
Related
I have an Azure IOThub that contains one edge device. Within this edge device I have several modules running and I can change the twin property of any individual module by connecting to it with it's connection string.
Now I would like the module to do something when it's twin property is changed but the module doesn't have access to it's connection string and it shouldn't because it shouldn't need to connect to itself.
How can a module detect it's twin property change without having a connection string?
I have followed this tutorial but this uses a connection string to detect changes: https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-csharp-csharp-module-twin-getstarted#create-a-module-identity
Here's the code for this module:
using System;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Shared;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace DataSyncService
{
class Program
{
private const string ModuleConnectionString = "CONNECTION STRING";
private static ModuleClient Client = null;
static void ConnectionStatusChangeHandler(ConnectionStatus status,
ConnectionStatusChangeReason reason)
{
Console.WriteLine("Connection Status Changed to {0}; the reason is {1}",
status, reason);
}
static void Main(string[] args)
{
Microsoft.Azure.Devices.Client.TransportType transport =
Microsoft.Azure.Devices.Client.TransportType.Amqp;
try
{
Client =
ModuleClient.CreateFromConnectionString(ModuleConnectionString, transport);
Client.SetConnectionStatusChangesHandler(ConnectionStatusChangeHandler);
// I want to set this callback but this requires a client and the client requires
// a connection string.
Client.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertyChanged, null).Wait();
Console.WriteLine("Retrieving twin");
var twinTask = Client.GetTwinAsync();
twinTask.Wait();
var twin = twinTask.Result;
Console.WriteLine(JsonConvert.SerializeObject(twin.Properties));
Console.WriteLine("Sending app start time as reported property");
TwinCollection reportedProperties = new TwinCollection();
reportedProperties["DateTimeLastAppLaunch"] = DateTime.Now;
Client.UpdateReportedPropertiesAsync(reportedProperties);
}
catch (AggregateException ex)
{
Console.WriteLine("Error in sample: {0}", ex);
}
Console.WriteLine("Waiting for Events. Press enter to exit...");
Console.ReadLine();
Client.CloseAsync().Wait();
}
private static async Task OnDesiredPropertyChanged(TwinCollection desiredProperties,
object userContext)
{
Console.WriteLine("desired property change:");
Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
Console.WriteLine("Sending current time as reported property");
TwinCollection reportedProperties = new TwinCollection
{
["DateTimeLastDesiredPropertyChangeReceived"] = DateTime.Now
};
await Client.UpdateReportedPropertiesAsync(reportedProperties).ConfigureAwait(false);
}
}
}
When you create a new module in Visual Studio Code, it generates a template module for you that will show you how it's done. I'll include the important bit below:
static async Task Init()
{
MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_Tcp_Only);
ITransportSettings[] settings = { mqttSetting };
// Open a connection to the Edge runtime
ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
await ioTHubModuleClient.OpenAsync();
Console.WriteLine("IoT Hub module client initialized.");
// Register callback to be called when a message is received by the module
await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient);
}
The way this works is because the Azure IoT Edge runtime will create the module as a Docker container with the connection settings as environment variables. The module client uses those variables to connect to IoT Hub when you call
ModuleClient ioTHubModuleClient = await
ModuleClient.CreateFromEnvironmentAsync(settings);
There is a good sample on this Microsoft tutorial that also covers listening for Twin updates.
using System;
using System.Text;
using System.Threading.Tasks;
namespace eNtsaIOTMqttApp
{
class Program
{
// you can get DeviceConnectionString from your IoT hub
private const string DeviceConnection = "HostName=eNstaIot.azure-devices.net;DeviceId=GcobaniTesting1;SharedAccessKey=*********";
static ServiceClient serviceClient;
static void Main(string[] args)
{
serviceClient = serviceClient.CreateFromConnectionString(DeviceConnectionString);
Program prog = new Program();
}
public Program() {
DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(DeviceConnectionString);
SendEvent().Wait();
ReceiveCommands(deviceClient).Wait();
}
// This method is responsible for sending Event to Iot Hub.
static async Task SendEvent()
{
string dataBuffer = "IOT in 90 seconds";
Microsoft.Azure.Devices.Message eventMessage = new Microsoft.Azure.Devices.Messages(Encoding.ASCII);
await serviceClient.SendAysnc("GcobaniTesting1", eventMessage);
}
// this method is responbile for receive message on the IOT hub.
async TaskReceiveCommands(DeviceClient deviceClient)
{
Console.WriteLine("\nDevice waiting for IoT hub command...\n");
Microsoft.Azure.Devices.Client.Message receiveMessage;
string messageData;
while (true)
{
receiveMessage = await deviceClient.ReceiveAsync(TimeSpan.FromSeconds(1));
if(receiveMessage != null)
{
messageData = Encoding.ASCII.GetString(receiveMessage.GetBytes());
Console.WriteLine("\t{0}> Message received: {1}", DateTime.Now.ToLocalTime(),messageData);
await deviceClient.CompleteAsync(receiveMessage);
}
}
}
}
}
Why am i not receive the message on my console when i am running this console application? I do get iot device, but when i send message from iot hub i dont get on visual studio.
is there some useful link to this rule?
This line is wrong:
serviceClient = serviceClient.CreateFromConnectionString(DeviceConnectionString);
You must use the service-side connection string to create the ServiceClient, for example the IoTHubOwner connection string. You are trying to create your service client with the device connection string. This should actually throw an error.
See here for guidance on connection strings: https://devblogs.microsoft.com/iotdev/understand-different-connection-strings-in-azure-iot-hub/
I have Implemented SingalR with existing web Api Application, where SignalR hub class will check the database for every few seconds and update the clients which are connected based on connection Id.
here is my hub class :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
using VHC.Core.Business.Managers.Old;
using VHC.Core.Common.Requests;
using VHC.Core.Business.Interfaces;
using VHC.Core.ServiceProxy.ServiceProxies;
namespace VHC.Core.WebAPI
{
public class NotifyHub : Hub
{
public static string ConnectionId { get; set; }
public string NotificationResult { get; set; }
//public NotifyHub()
//{
//}
public void PushNotificationData(Int32 userId, Int16 userTypeId)
{
lock (this)
{
var request = new PushNotificationRequest
{
FilterProperty = new Common.Filters.FilterProperty { Offset = 0, RecordLimit = 0, OrderBy = "datechecked desc" },
Filters = new List<Common.Filters.FilterObject> { new Common.Filters.FilterObject { LogicOperator = 0, ConditionOperator = 0, Function = 0, FieldName = "", FieldValue = "", FieldType = 0 } }
};
INotificationManager inotifity = new NotificationManager();
var taskTimer = Task.Run(async () =>
{
while (true)
{
//var serviceProxy = new NotificationServiceProxy();
// var NotificationResult = inotifity.PublishResultsAsync(request, userId, userTypeId);--check database
if (userId == 3134)
{
NotificationResult = "validated";
//Sending the server time to all the connected clients on the client method SendServerTime()
Clients.Client(ConnectionId).NotificationToClient(NotificationResult);
}
else
{
NotificationResult = "not validated";
//Sending the server time to all the connected clients on the client method SendServerTime()
Clients.Client(ConnectionId).NotificationToClient(NotificationResult);
}
//Delaying by 6 seconds.
await Task.Delay(6000);
}
} );
}
}
public override Task OnConnected()
{
ConnectionId = Context.ConnectionId;
return base.OnConnected();
}
}
}
Problem I'm facing is,
As we know Client Application will create hub instance and connection will be established. When I'm running a client application at once, I didn't get any issue.
When I'm running the client application at two or more parallely, I'm getting an issue which means data are completely messed up.
eg: for PushNotificationData method, Client application will pass the userID dynamically. In above code , I have written a logic when userID ==3143 ,the response should be 'validated', else 'not validated'.
if i'm running an application in one browser, I'm getting the response every 6 seconds as 'validated'
but if i'm running an application at two or more parallely,I'm getting the response every 6 seconds as sometime 'validated' some time 'not validated'.
I guess something messed up. kindly assist me. I hope its clear.
Hubs lifecycle is "per request".
So you should not save any state in hub and call async cycling function of course.
Try to rewrite you code as follows:
Create separated cycle during your app startup (near app.MapSinglaR() or something like this).
Create static concurrent dictionary or list or whatever you need and use it to store active users.
Get Hub instance inside your async cycle using GlobalHost.ConnectionManager.GetHubContext<NotifyHub>().
I have built a peer to peer C# video conferencing application that uses a specific TCP port(17500) for audio communication. Currently, on my application interface, I enter the other IP address which has the program opened in order to communicate. What I want to do is to find the IP addresses automatically.
So, I though the best way to achieve this is to obtain the local IP addresses that are using the same TCP port number, 17500. How can I do that ? or is there any other methods getting IP addresses using the same application ?
As mentioned in comments, you need some kind of peer-discovery protocol.
As many multimedia devices, routers etc. use multicast based discovery protocols like SSDP, I created a similar discovery service sample .
Usage is simple. Just use
Discoverer.PeerJoined = ip => Console.WriteLine("JOINED:" + ip);
Discoverer.PeerLeft= ip => Console.WriteLine("LEFT:" + ip);
Discoverer.Start();
All your clients will use the same code.
using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Caching; // add this library from the reference tab
using System.Text;
using System.Threading.Tasks;
namespace SO
{
public class Discoverer
{
static string MULTICAST_IP = "238.212.223.50"; //Random between 224.X.X.X - 239.X.X.X
static int MULTICAST_PORT = 2015; //Random
static UdpClient _UdpClient;
static MemoryCache _Peers = new MemoryCache("_PEERS_");
public static Action<string> PeerJoined = null;
public static Action<string> PeerLeft = null;
public static void Start()
{
_UdpClient = new UdpClient();
_UdpClient.Client.Bind(new IPEndPoint(IPAddress.Any, MULTICAST_PORT));
_UdpClient.JoinMulticastGroup(IPAddress.Parse(MULTICAST_IP));
Task.Run(() => Receiver());
Task.Run(() => Sender());
}
static void Sender()
{
var IamHere = Encoding.UTF8.GetBytes("I AM ALIVE");
IPEndPoint mcastEndPoint = new IPEndPoint(IPAddress.Parse(MULTICAST_IP), MULTICAST_PORT);
while (true)
{
_UdpClient.Send(IamHere, IamHere.Length, mcastEndPoint);
Task.Delay(1000).Wait();
}
}
static void Receiver()
{
var from = new IPEndPoint(0, 0);
while (true)
{
_UdpClient.Receive(ref from);
if (_Peers.Add(new CacheItem(from.Address.ToString(), from),
new CacheItemPolicy() {
SlidingExpiration = TimeSpan.FromSeconds(20),
RemovedCallback = (x) => { if (PeerLeft != null) PeerLeft(x.CacheItem.Key); }
}
)
)
{
if (PeerJoined != null) PeerJoined(from.Address.ToString());
}
Console.WriteLine(from.Address.ToString());
}
}
}
}
Now a little bit about the algorithm:
Every client multicasts a packet every seconds.
if the receiver(every client has it) gets a packet from an IP that isn't in its cache, it will fire PeerJoined method.
Cache will expire in 20 seconds. If a client doesn't receive a packet within that duration from another client in cache, it will fire PeerLeft method.
I believe if you are using a peer to peer application to exchange packets, when you need to know if someone "Is Online and Ready for connection", you need to send a broadcast. We can do it easily using an UDP connection.
I'll post an example where you use two methods: one to ask the entire network for ready clients in a broadcast message, and the other will start a listener to answer back broadcast asking message, or start a connection if a response of type "i am here" comes.
Hope it helps!
public sealed class UdpUtility
{
// Our UDP Port
private const int broadcastPort = 11000;
// Our message to ask if anyone is ready for connection
private const string askMessage = "ARE ANYONE OUT THERE?";
// Our answer message
private const string responseMessage = "I AM HERE!";
// We use this method to look for a client to connect with us.
// It will send a broadcast to the network, asking if any client is ready for connection.
public void SendBroadcastMessage()
{
var udp = new UdpClient(broadcastPort);
var endpoint = new IPEndPoint(IPAddress.Broadcast, broadcastPort);
try
{
var bytes = Encoding.ASCII.GetBytes(askMessage);
udp.Send(bytes, bytes.Length, endpoint);
}
catch (Exception ex)
{
// Treat your connection exceptions here!
}
}
// This method will start a listener on the port.
// The client will listen for the ask message and the ready message.
// It can then, answer back with a ready response, or start the TCP connection.
public void ListenBroadcastMessage()
{
var udp = new UdpClient(broadcastPort);
var endpoint = new IPEndPoint(IPAddress.Broadcast, broadcastPort);
bool received = false;
try
{
while (!received)
{
// We start listening broadcast messages on the broadcast IP Address interface.
// When a message comes, the endpoing IP Address will be updated with the sender IP Address.
// Then we can answer back the response telling that we are here, ready for connection.
var bytes = udp.Receive(ref endpoint);
var message = Encoding.ASCII.GetString(bytes);
if (message == askMessage)
{
// Our client received the ask message. We must answer back!
// When the client receives our response, his endpoint will be updated with our IP Address.
// The other client can, then, start the TCP connection and do the desired stuff.
var responseBytes = Encoding.ASCII.GetBytes(responseMessage);
udp.Send(responseBytes, responseBytes.Length, endpoint);
}
else if (message == responseMessage)
{
// We received a connection ready message! We can stop listening.
received = true;
// We received a response message!
// We can start our TCP connection here and do the desired stuff.
// Remember: The other client IP Address (the thing you want) will be on the
// endpoint object at this point. Just use it and start your TCP connection!
}
}
}
catch (Exception ex)
{
// Treat your connection exceptions here!
}
}
}
Invoke your command prompt to do "netstat -n" and extract the output.
Here is a piece of code taken from a program that I have wrote modified to fit your requirements. You will still need to further process the data to get the IP addresses
Process netP = new Process();
ProcessStartInfo netPI = new ProcessStartInfo();
netPI.FileName = "cmd";
netPI.UseShellExecute = false;
netPI.RedirectStandardOutput = true;
netPI.RedirectStandardInput = true;
netPI.RedirectStandardError = true;
netPI.CreateNoWindow = true;
netP.StartInfo = NetPI;
netP.Start();
while (!netP.Start())
Thread.Sleep(100);
StreamWriter sW = netP.StandardInput;
StreamReader sR = netP.StandardOutput;
sW.WriteLine("netstat -n")
sW.Close();
string data = sR.ReadToEnd();
sR.Close();
//Do some further processing to filter out the addresses and extract
When i send a request to my server or a reply to my client, the message i send is always divided into multiple parts.
So i need to call Receive multiple times to get to the last part/frame.
Why does this happen.. is there a better way of sending and receiving an xml encoded string?
This is the code of my client:
private void SendRequestAsyncTaskStart<T>(object contextObj, T request)
{
ZmqContext context = (ZmqContext)contextObj;
ZmqSocket requestSocket = CreateServerSocket(context);
SerializeAndSendRequest(request, requestSocket);
}
private ZmqSocket CreateServerSocket(ZmqContext context)
{
var client = context.CreateSocket(SocketType.REQ);
client.Connect(_requestReplyEndpoint);
client.Linger = TimeSpan.Zero;
client.ReceiveReady += PollInReplyHandler;
return client;
}
public static string Serialize(this object obj)
{
string result;
using (var memoryStream = new MemoryStream())
{
using (var reader = new StreamReader(memoryStream))
{
var serializer = new DataContractSerializer(obj.GetType());
serializer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
result = reader.ReadToEnd();
}
}
return result;
}
This is the code of my server:
private void ListenForRequestsThreadStart(object contextObj)
{
ZmqContext context = (ZmqContext)contextObj;
using (
ZmqSocket frontend = context.CreateSocket(SocketType.REP),
backend = context.CreateSocket(SocketType.DEALER))
{
string bindAddress = string.Format("tcp://*:{0}", _listenForRequetsPort);
frontend.Bind(bindAddress);
backend.Bind("inproc://backend");
frontend.ReceiveReady += HandleRequestReceived;
// polling
}
}
private void HandleRequestReceived(object sender, SocketEventArgs e)
{
string message;
bool hasNext;
do
{
message = socket.Receive(Encoding.ASCII);
hasNext = socket.ReceiveMore;
} while (hasNext);
// after calling Receive 3 times i get my actual message
}
Since you're sending via a socket you're at the mercy of the network. First, the network will have broken your message down in multiple packates each of which is received separately by your listener. Every now and then, the underlying socket on the listening machine will say to itself 'Got some incoming, but there's more to come. Wait a bit'. After a while it'll say, 'Oh well, give what I've got' and keep waiting'.
That's what's happening. In WCF, the WCF implementation gets its data via sockets which do exactly the same thing. But WCF waits till the whole message arrives before giving it to your waiting code. That's one of the advantages of using a Framework like WCF. It protects you from the metal.
Any message sent over TCP may be divided into several packets depending on its size. That's why you should never assume to get a message in one go, but read until you're sure you've received everything.