Fleck - send data from server - c#

We have today an event based application centered completely on desktop. We want to move to web and drive events using Web Sockets using Fleck. I was unable to figure out how to send message asynchronously from server to client in fleck. Below is the sample for what I am trying to achieve
public class Program
{
static readonly WebSocketServer _webSocketServer = new WebSocketServer("ws://127.0.0.1:8181");
static readonly List<IWebSocketConnection> SocketClients = new List<IWebSocketConnection>();
static void Main(string[] args)
{
_webSocketServer.Start(socket =>
{
socket.OnOpen = () =>
{
SocketClients.Add(socket);
OnOpening(socket.ConnectionInfo.Id);
};
socket.OnClose = () =>
{
OnClosing(socket.ConnectionInfo.Id);
SocketClients.Remove(socket);
};
socket.OnMessage = message => OnMessage(message, socket.ConnectionInfo.Id);
});
Console.ReadLine();
}
private static void OnMessage(string message, Guid id)
{
Console.WriteLine("<i> Recived message " + message + " from Client Id : " + id + " </i>");
}
private static void OnClosing(Guid id)
{
Console.WriteLine("<i> Client Left With Id : " + id + "</i>");
}
private static void OnOpening(Guid id)
{
//Do Something
}
// CALL THIS METHOD FROM MY APPLICATION AND SEND THE MESSAGE TO CLIENT BASED ON THE ID
private static void SendMessage(string message, Guid id)
{
// HOW TO DO SOMETHING LIKE BELOW SEND MESSAGE ASYNC
//_webSocketServer.Start(socket =>
//{
// var clientToSend = SocketClients.Find(client => client.ConnectionInfo.Id == id);
// socket.Send(message);
//});
}
}

You have a similar example in their GitHub: https://github.com/statianzo/Fleck/blob/master/src/Samples/ConsoleApp/Server.cs
I just added the SendToSocketById method>
class Server
{
static void Main()
{
FleckLog.Level = LogLevel.Debug;
var allSockets = new List<IWebSocketConnection>();
var server = new WebSocketServer("ws://0.0.0.0:8181");
server.Start(socket =>
{
socket.OnOpen = () =>
{
Console.WriteLine("Open!");
allSockets.Add(socket);
};
socket.OnClose = () =>
{
Console.WriteLine("Close!");
allSockets.Remove(socket);
};
socket.OnMessage = message =>
{
Console.WriteLine(message);
allSockets.ToList().ForEach(s => s.Send("Echo: " + message));
};
});
var input = Console.ReadLine();
while (input != "exit")
{
foreach (var socket in allSockets.ToList())
{
socket.Send(input);
}
input = Console.ReadLine();
}
}
static void SendToSocketById(String input, Guid id)
{
var socket = allSockets.Find(client => client.ConnectionInfo.Id == id);
socket.Send(input);
}
}

Related

Signal R connection state is 0 after sometime

I am using #aspnet/signalr to connect to a hub and from the hub i am calling the method to return me some data.
When i first initialize the server the connection is established normally, but if i refresh the window 3 or 4 times the client stop sending connection request to the server.
I tried logging the HubConnection, the connectionState at first is equal to 1 but after the problem accrues the connectionState is alwase equal 0
Here is how i am building the hubConnection:
buildConnection() {
this.hubConnection = new HubConnectionBuilder()
.withUrl(this.tradesService.getStockQuotationsHubUrl())
.build();
this.hubConnection.serverTimeoutInMilliseconds = 1000 * 10;
this.hubConnection.onclose(() => setTimeout(() => this.startSignalRConnection(), 2000));}
Here is how i am starting the hubConnection:
startConnection() {
this.hubConnection
.start()
.then(() => {
this.hubConnection.on('updateMethod', (data: any) => {
this.store.push([
{ type: 'update', key: data.stockID, data },
]);
});
this.dataSource = new DataSource({
store: this.store,
reshapeOnPush: true,
});
});}
Note: I am listing to 5 hubs at once in my page but the only one having problems is this one.
[Update]
The problem is from the server because when i restart the server the connection is reestablished between the client and the server, but if the client refresh or quit the page for multiple times the hub does not even try to connect to the client
public class StockQuotationsHub : Microsoft.AspNetCore.SignalR.Hub
{
private string SectorID { get; set; }
private int TradingSession { get; set; }
private int MarketID { get; set; }
public static StockQuotationExt stockQuotationExt { get; set; }
private readonly StockQuotationTicker _stockTicker;
public StockQuotationsHub(StockQuotationTicker stockTicker){
_stockTicker = stockTicker;
}
public override Task OnConnectedAsync(){
return base.OnConnectedAsync();
}
public IEnumerable<StockQuotation> GetAllStockQuotations(string[] stockID, string sectorID, int tradingSession, int marketType){
return _stockTicker.
GetAllStocks(Context.ConnectionId, stockID, sectorID, tradingSession, marketType);
}
public override async Task OnDisconnectedAsync(Exception exception){
await base.OnDisconnectedAsync(exception);
}
and here is my stock ticker class:
public IEnumerable<StockQuotation> GetAllStocks(string connectionId, string[] stockID, string sectorID, int tradingSession, int marketType)
{
_stocks = new List<StockQuotation>();
_stocks = Task.Run(async () => await GetStockQuotationModelAsync("", 0, 1, 0)).Result.ToList();
this.MaxTimeStamp = _stocks.Max(s => s.TStamp);
this.SectorID = sectorID;
this.TradingSession = tradingSession;
this.MarketID = marketType;
AddToGroups(connectionId, stockID);
if (_timer==null)
_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
if (stockID.Length == 0)
{
return _stocks;
}
else
{
var stocksList = new List<StockQuotation>();
foreach (var stock in stockID)
{
stocksList.AddRange(_stocks.Where(s => s.StockID == stock).ToList());
}
return stocksList;
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
private void AddToGroups(string connectionId, string[] stockID)
{
if (_stocks.Count > 0)
{
if (stockID.Length == 0)
{
Hub.Groups.AddToGroupAsync(connectionId, "ALL");
}
else
{
foreach (var stock in stockID)
{
Hub.Groups.AddToGroupAsync(connectionId, stock);
var s = _stocks.FirstOrDefault(s => s.StockID == stock);
if(s != null)
{
s.Snapshots = new List<double>(GetStockQuotationSnapshots(stock));
}
}
}
}
}
I really appreciated the help.
Eventually the problem was that the project type was MVC, so I changed it to be webApi
and everything worked successfully
Even if you fix it I would like to recommend you to add the following code in order to know exactly the root cause of disconnection.
First of all enable the extended debug info in the service configuration:
services.AddSignalR(o =>
{
o.EnableDetailedErrors = true;
});
Furthermore, you need to attach to the event Closed in HubConnection like that:
hubConnection.Closed += (exception) =>
{
if (exception == null)
{
Console.WriteLine("Connection closed without error.");
}
else
{
Console.WriteLine($"Connection closed due to an error: {exception}");
}
return null;
};

WCSession Send Message gives error "payload could not be delivered"

I am using Xamarin to develop an Apple Watch app. I am trying to send a message from my watch to the iPhone with my SendMessage function. When I do this, I get in the out error the message payload could not be delivered. I can only read part of the message ("payload could n...") because I am writing it on a label (since my debugger doesn't work in Xamarin I can't take a look at the message), but after doing some googling I'm assuming that's what it is written. Any idea why? Here is my code:
public sealed class WCSessionManager : NSObject, IWCSessionDelegate
{
// Setup is converted from https://www.natashatherobot.com/watchconnectivity-say-hello-to-wcsession/
// with some extra bits
private static readonly WCSessionManager sharedManager = new WCSessionManager();
private static WCSession session = WCSession.IsSupported ? WCSession.DefaultSession : null;
#if __IOS__
public static string Device = "Phone";
#else
public static string Device = "Watch";
#endif
public event ApplicationContextUpdatedHandler ApplicationContextUpdated;
public delegate void ApplicationContextUpdatedHandler(WCSession session, Dictionary<string, object> applicationContext);
public event MessageReceivedHandler MessageReceived;
public delegate void MessageReceivedHandler(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler);
private WCSession validSession
{
get
{
#if __IOS__
// Even though session.Paired and session.WatchAppInstalled are underlined, it will still build as they are available on the iOS version of WatchConnectivity.WCSession
Console.WriteLine($"Paired status:{(session.Paired ? '✓' : '✗')}\n");
Console.WriteLine($"Watch App Installed status:{(session.WatchAppInstalled ? '✓' : '✗')}\n");
return (session.Paired && session.WatchAppInstalled) ? session : null;
//return session;
#else
return session;
#endif
}
}
private WCSession validReachableSession
{
get
{
return session.Reachable ? validSession : null;
}
}
private WCSessionManager() : base() { }
public static WCSessionManager SharedManager
{
get
{
return sharedManager;
}
}
public void StartSession()
{
if (session != null)
{
session.Delegate = this;
session.ActivateSession();
Console.WriteLine($"Started Watch Connectivity Session on {Device}");
}
}
[Export("sessionReachabilityDidChange:")]
public void SessionReachabilityDidChange(WCSession session)
{
Console.WriteLine($"Watch connectivity Reachable:{(session.Reachable ? '✓' : '✗')} from {Device}");
// handle session reachability change
if (session.Reachable)
{
// great! continue on with Interactive Messaging
}
else {
// 😥 prompt the user to unlock their iOS device
}
}
#region Application Context Methods
public void UpdateApplicationContext(Dictionary<string, object> applicationContext)
{
// Application context doesnt need the watch to be reachable, it will be received when opened
if (validSession != null)
{
try
{
var NSValues = applicationContext.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = applicationContext.Keys.Select(x => new NSString(x)).ToArray();
var NSApplicationContext = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
UpdateApplicationContextOnSession(NSApplicationContext);
}
catch (Exception ex)
{
Console.WriteLine($"Exception Updating Application Context: {ex.Message}");
}
}
}
public void GetApplicationContext()
{
UpdateApplicationContext(new Dictionary<string, object>() { { "GET", null } });
}
[Export("session:didReceiveApplicationContext:")]
public void DidReceiveApplicationContext(WCSession session, NSDictionary<NSString, NSObject> applicationContext)
{
Console.WriteLine($"Recieving Message on {Device}");
if (ApplicationContextUpdated != null)
{
var keys = applicationContext.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
try
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString(), typeof(DoorWatchDTO)));
}
catch (Exception)
{
values = applicationContext.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
}
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
ApplicationContextUpdated(session, dictionary);
}
}
[Export("session:didReceiveMessage::")]
public void DidReceiveMessage(WCSession session, NSDictionary<NSString, NSObject> message, WCSessionReplyHandler replyHandler)
{
if (MessageReceived != null)
{
var keys = message.Keys.Select(k => k.ToString()).ToArray();
IEnumerable<object> values;
values = message.Values.Select(v => JsonConvert.DeserializeObject(v.ToString()));
var dictionary = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value);
MessageReceived(dictionary, (dict) =>
{
var NSValues = dict.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = dict.Keys.Select(x => new NSString(x)).ToArray();
var NSDict = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
replyHandler.Invoke(NSDict);
});
}
}
public void SendMessage(Dictionary<string, object> message, Action<Dictionary<string, object>> replyHandler, WKInterfaceLabel label)
{
if (validSession != null)
{
try
{
var NSValues = message.Values.Select(x => new NSString(JsonConvert.SerializeObject(x))).ToArray();
var NSKeys = message.Keys.Select(x => new NSString(x)).ToArray();
var NSMessage = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(NSValues, NSKeys);
var reply = new WCSessionReplyHandler((replyMessage) =>
{
var values = replyMessage.Values.Select(x => JsonConvert.SerializeObject(x)).ToArray();
var keys = replyMessage.Keys.Select(x => x.ToString()).ToArray();
var dict = keys.Zip(values, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => (object)x.Value);
replyHandler.Invoke(dict);
});
validSession.SendMessage(NSMessage, reply, (error) =>
{
label.SetText(error.ToString()); // I can see the error in here: "payload could n..."
});
}
catch (Exception ex)
{
Console.WriteLine($"Exception sending message: {ex.Message}");
}
}
}
private void UpdateApplicationContextOnSession(NSDictionary<NSString, NSObject> NSApplicationContext)
{
NSError error;
var sendSuccessfully = validSession.UpdateApplicationContext(NSApplicationContext, out error);
if (sendSuccessfully)
{
Console.WriteLine($"Sent App Context from {Device} \nPayLoad: {NSApplicationContext.ToString()} \n");
#if __IOS__
Logging.Log("Success, payload: " + NSApplicationContext.ToString());
#endif
}
else
{
Console.WriteLine($"Error Updating Application Context: {error.LocalizedDescription}");
#if __IOS__
Logging.Log("error: " + error.LocalizedDescription);
#endif
}
}
#endregion
I solved it. Instead of implementing IWCSessionDelegate, I simply implement WCSessionDelegate instead and override the functions as needed.

Router with remote actors in akka.net

I'm trying to create sample router with two remote actors.
Here's my code
class Program
{
public const string AkkaConfig = #"{
akka {
actor {
provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
deployment {
/remoteecho1 {
remote = ""akka.tcp://Target#my1.lan:8090""
}
/remoteecho2 {
remote = ""akka.tcp://Target#my2.lan:8090""
}
}
remote{
helios.tcp{
port = 0
hostname = localhost
}
}
}
}";
static void Main(string[] args)
{
using (var system = ActorSystem.Create("Deployer", AkkaConfig))
{
var remoteEcho1 = system.ActorOf(Props.Create(() => new EchoActor()), "remoteecho1");
var remoteEcho2 = system.ActorOf(Props.Create(() => new EchoActor()), "remoteecho2");
var roundRobinGroup = new RoundRobinGroup(new[]
{
remoteEcho1,
remoteEcho2
});
var coordinator = system.ActorOf(Props.Empty.WithRouter(roundRobinGroup),"coordinator");
system.ActorOf(Props.Create(() => new HelloActor(coordinator)), "localactor");
system.ActorSelection("/user/localactor").Tell(new SayHelloMessage());
Console.ReadKey();
}
}
}
public class HelloActor:ReceiveActor
{
private readonly IActorRef remoteActor;
public HelloActor(IActorRef remoteActor)
{
this.remoteActor = remoteActor;
Context.Watch(remoteActor);
Receive<HelloMessage>(m =>
{
Console.WriteLine("Pingback from {1}: {0}", m.MessageText, Sender.Path);
});
Receive<SayHelloMessage>(m =>
{
var newMessage = new HelloMessage(Guid.NewGuid().ToString());
remoteActor.Tell(newMessage);
});
}
protected override void PreStart()
{
Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1), Context.Self, new SayHelloMessage(), ActorRefs.NoSender);
}
}
public class SayHelloMessage
{
}
The problem is that remoteActor.Tell(newMessage) in the code below doesn't send message to my actor
Receive<SayHelloMessage>(m =>
{
var newMessage = new HelloMessage(Guid.NewGuid().ToString());
remoteActor.Tell(newMessage);
});
When I pass remoteEcho actor to HelloActor constructor instead of router instance everything works fine. How to make that example work with Router?

AsanaNet error in C#

I am unable to use AsanaNet from C#. When instantiating a new Asana instance, i am unable to pass errorCallback. The error I get is "The errorCallback does not exist in current context". Below is the my code.
class Program
{
private const string _apiKey = "API";
private static Asana _asana;
static void Main(string[] args)
{
Console.WriteLine("Step 1");
_asana = new Asana(_apiKey, AuthenticationType.Basic, errorCallback);
var user = new AsanaUser();
_asana.GetMe(o =>
{
user = o as AsanaUser;
});
Console.WriteLine("Step 2");
_asana.GetWorkspaces(o =>
{
foreach (AsanaWorkspace workspace in o)
{
Console.WriteLine("Workspace Name={0}", workspace.Name);
}
});
Console.WriteLine("Step 3");
_asana.GetWorkspaces(o =>
{
foreach (AsanaWorkspace workspace in o)
{
_asana.GetProjectsInWorkspace(workspace, projects =>
{
foreach (AsanaProject project in projects)
{
Console.WriteLine("Project Name=" + project.Name);
}
}
);
}
});
}
}
According to https://github.com/acron0/AsanaNet/blob/master/AsanaNet/Asana.cs, the constructor has this signature:
public Asana(string apiKeyOrBearerToken, AuthenticationType authType, Action<string, string, string> errorCallback)
So you can declare your error callback method like this:
static void errorCallback(string s1, string s2, string s3)
{
}
Also, if you don't want handling anything, you can just pass an empty lambda into constructor:
_asana = new Asana(_apiKey, AuthenticationType.Basic, (s1, s2, s3) => {});

How to Perform Multiple "Pings" in Parallel using C#

I am trying to calculate the average round-trip time for a collection of servers. In order to speed things up, I would like to perform the pings in parallel. I have written a function called AverageRoundtripTime() and it seems to work, however, since I don't know very much about multi-threading, I am wondering if what I've done is okay. Please take a look at my code and let me know if it's okay or if there's a better way to achieve what I want:
public void Main()
{
// Collection of hosts.
List<String> hosts = new List<String>();
// Add 100 hosts to the collection.
for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");
// Display the average round-trip time for 100 hosts.
Console.WriteLine(AverageRoundtripTime(hosts));
}
public Double AverageRoundtripTime(IEnumerable<String> hosts)
{
// Collection of threads.
List<Thread> threads = new List<Thread>();
// Collection of ping replies.
List<PingReply> pingReplies = new List<PingReply>();
// Loop through all host names.
foreach (var host in hosts)
{
// Create a new thread.
Thread thread = new Thread(() =>
{
// Variable to hold the ping reply.
PingReply reply = null;
// Create a new Ping object and make sure that it's
// disposed after we're finished with it.
using (Ping ping = new Ping())
{
reply = ping.Send(host);
}
// Get exclusive lock on the pingReplies collection.
lock (pingReplies)
{
// Add the ping reply to the collection.
pingReplies.Add(reply);
}
});
// Add the newly created thread to the theads collection.
threads.Add(thread);
// Start the thread.
thread.Start();
}
// Wait for all threads to complete
foreach (Thread thread in threads)
{
thread.Join();
}
// Calculate and return the average round-trip time.
return pingReplies.Average(x => x.RoundtripTime);
}
Update:
Check out a related question that I asked:
Task Parallel Library Code Freezes in a Windows Forms Application - Works fine as a Windows Console Application
The ping class has a method SendAsync. This follows the Event-based Asynchronous Programming (EAP) pattern. Check out this article: http://msdn.microsoft.com/en-us/library/ee622454.aspx.
For a quick example here is a method I have that implements that article in a very basic fashion. You can basically call this as many times as you want and all the pings will be done asychronously.
class Program
{
public static string[] addresses = {"microsoft.com", "yahoo.com", "google.com"};
static void Main(string[] args)
{
List<Task<PingReply>> pingTasks = new List<Task<PingReply>>();
foreach (var address in addresses)
{
pingTasks.Add(PingAsync(address));
}
//Wait for all the tasks to complete
Task.WaitAll(pingTasks.ToArray());
//Now you can iterate over your list of pingTasks
foreach (var pingTask in pingTasks)
{
//pingTask.Result is whatever type T was declared in PingAsync
Console.WriteLine(pingTask.Result.RoundtripTime);
}
Console.ReadLine();
}
static Task<PingReply> PingAsync(string address)
{
var tcs = new TaskCompletionSource<PingReply>();
Ping ping = new Ping();
ping.PingCompleted += (obj, sender) =>
{
tcs.SetResult(sender.Reply);
};
ping.SendAsync(address, new object());
return tcs.Task;
}
}
use the Parallel.For and a ConcurrentBag
static void Main(string[] args)
{
Console.WriteLine(AverageRoundTripTime("www.google.com", 100));
Console.WriteLine(AverageRoundTripTime("www.stackoverflow.com", 100));
Console.ReadKey();
}
static double AverageRoundTripTime(string host, int sampleSize)
{
ConcurrentBag<double> values = new ConcurrentBag<double>();
Parallel.For(1, sampleSize, (x, y) => values.Add(Ping(host)));
return values.Sum(x => x) / sampleSize;
}
static double Ping(string host)
{
var reply = new Ping().Send(host);
if (reply != null)
return reply.RoundtripTime;
throw new Exception("denied");
}
// The solution becomes simpler using LINQ
List<String> hosts = new List<String>();
for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");
var average = hosts.AsParallel().WithDegreeOfParallelism(64).
Select(h => new Ping().Send(h).RoundtripTime).Average();
Console.WriteLine(average)
Maybe using SendPingAsync like this:
using (var ping = new Ping())
{
var replies = await Task.WhenAll(hosts.Select(x => ping.SendPingAsync(x)))
.ConfigureAwait(false);
// false here ^ unless you want to schedule back to sync context
... process replies.
}
A solution:
internal class Utils
{
internal static PingReply Ping (IPAddress address, int timeout = 1000, int ttl = 64)
{
PingReply tpr = null;
var p = new Ping ();
try {
tpr = p.Send (address,
timeout,
Encoding.ASCII.GetBytes ("oooooooooooooooooooooooooooooooo"),
new PingOptions (ttl, true));
} catch (Exception ex) {
tpr = null;
} finally {
if (p != null)
p.Dispose ();
p = null;
}
return tpr;
}
internal static List<PingReply> PingAddresses (List<IPAddress> addresses, int timeout = 1000, int ttl = 64)
{
var ret = addresses
.Select (p => Ping (p, timeout, ttl))
.Where (p => p != null)
.Where (p => p.Status == IPStatus.Success)
.Select (p => p).ToList ();
return ret;
}
internal static Task PingAddressesAsync (List<IPAddress> addresses, Action<Task<List<PingReply>>> endOfPing, int timeout = 1000, int ttl = 64)
{
return Task.Factory.StartNew<List<PingReply>> (() => Utils.PingAddresses (
addresses, timeout, ttl)).ContinueWith (t => endOfPing (t));
}
}
And using:
Console.WriteLine ("start");
Utils.PingAddressesAsync (new List<IPAddress> () {
IPAddress.Parse ("192.168.1.1"),
IPAddress.Parse ("192.168.1.13"),
IPAddress.Parse ("192.168.1.49"),
IPAddress.Parse ("192.168.1.200")
}, delegate(Task<List<PingReply>> tpr) {
var lr = tpr.Result;
Console.WriteLine ("finish with " + lr.Count.ToString () + " machine found");
foreach (var pr in lr) {
Console.WriteLine (pr.Address.ToString ());
}
});
Console.WriteLine ("execute");
Console.ReadLine ();
This is an async worker that can ping multiples endpoints. You can Start() or Stop() the heartbeat worker and suscribe to the following events :
PingUp (edge-triggered when and endpoint gets down)
PingDown (edge-triggered when and endpoint gets up)
PulseStarted
PulseEnded
PingError
-
public class NetworkHeartbeat
{
private static object lockObj = new object();
public bool Running { get; private set; }
public int PingTimeout { get; private set; }
public int HeartbeatDelay { get; private set; }
public IPAddress[] EndPoints { get; private set; }
public int Count => EndPoints.Length;
public PingReply[] PingResults { get; private set; }
private Ping[] Pings { get; set; }
public NetworkHeartbeat(IEnumerable<IPAddress> hosts, int pingTimeout, int heartbeatDelay)
{
PingTimeout = pingTimeout;
HeartbeatDelay = heartbeatDelay;
EndPoints = hosts.ToArray();
PingResults = new PingReply[EndPoints.Length];
Pings = EndPoints.Select(h => new Ping()).ToArray();
}
public async void Start()
{
if (!Running)
{
try
{
Debug.WriteLine("Heartbeat : starting ...");
// set up the tasks
var chrono = new Stopwatch();
var tasks = new Task<PingReply>[Count];
Running = true;
while (Running)
{
// set up and run async ping tasks
OnPulseStarted(DateTime.Now, chrono.Elapsed);
chrono.Restart();
for (int i = 0; i < Count; i++)
{
tasks[i] = PingAndUpdateAsync(Pings[i], EndPoints[i], i);
}
await Task.WhenAll(tasks);
for (int i = 0; i < tasks.Length; i++)
{
var pingResult = tasks[i].Result;
if (pingResult != null)
{
if (PingResults[i] == null)
{
if (pingResult.Status == IPStatus.Success)
OnPingUp(i);
}
else if (pingResult.Status != PingResults[i].Status)
{
if (pingResult.Status == IPStatus.Success)
OnPingUp(i);
else if (PingResults[i].Status == IPStatus.Success)
OnPingDown(i);
}
}
else
{
if (PingResults[i] != null && PingResults[i].Status == IPStatus.Success)
OnPingUp(i);
}
PingResults[i] = tasks[i].Result;
Debug.WriteLine("> Ping [" + PingResults[i].Status.ToString().ToUpper() + "] at " + EndPoints[i] + " in " + PingResults[i].RoundtripTime + " ms");
}
OnPulseEnded(DateTime.Now, chrono.Elapsed);
// heartbeat delay
var delay = Math.Max(0, HeartbeatDelay - (int)chrono.ElapsedMilliseconds);
await Task.Delay(delay);
}
Debug.Write("Heartbeat : stopped");
}
catch (Exception)
{
Debug.Write("Heartbeat : stopped after error");
Running = false;
throw;
}
}
else
{
Debug.WriteLine("Heartbeat : already started ...");
}
}
public void Stop()
{
Debug.WriteLine("Heartbeat : stopping ...");
Running = false;
}
private async Task<PingReply> PingAndUpdateAsync(Ping ping, IPAddress epIP, int epIndex)
{
try
{
return await ping.SendPingAsync(epIP, PingTimeout);
}
catch (Exception ex)
{
Debug.Write("-[" + epIP + "] : error in SendPing()");
OnPingError(epIndex, ex);
return null;
}
}
// Event on ping errors
public event EventHandler<PingErrorEventArgs> PingError;
public class PingErrorEventArgs : EventArgs
{
public int EndPointIndex { get; private set; }
public Exception InnerException { get; private set; }
public PingErrorEventArgs(int epIndex, Exception ex)
{
EndPointIndex = epIndex;
InnerException = ex;
}
}
private void OnPingError(int epIndex, Exception ex) => PingError?.Invoke(this, new PingErrorEventArgs(epIndex, ex));
// Event on ping Down
public event EventHandler<int> PingDown;
private void OnPingDown(int epIndex)
{
Debug.WriteLine("# Ping [DOWN] at " + EndPoints[epIndex]);
PingDown?.Invoke(this, epIndex);
}
// Event on ping Up
public event EventHandler<int> PingUp;
private void OnPingUp(int epIndex)
{
Debug.WriteLine("# Ping [UP] at " + EndPoints[epIndex] );
PingUp?.Invoke(this, epIndex);
}
// Event on pulse started
public event EventHandler<PulseEventArgs> PulseStarted;
public class PulseEventArgs : EventArgs
{
public DateTime TimeStamp { get; private set; }
public TimeSpan Delay { get; private set; }
public PulseEventArgs(DateTime date, TimeSpan delay)
{
TimeStamp = date;
Delay = delay;
}
}
private void OnPulseStarted(DateTime date, TimeSpan delay)
{
Debug.WriteLine("# Heartbeat [PULSE START] after " + (int)delay.TotalMilliseconds + " ms");
PulseStarted?.Invoke(this, new PulseEventArgs(date, delay));
}
// Event on pulse ended
public event EventHandler<PulseEventArgs> PulseEnded;
private void OnPulseEnded(DateTime date, TimeSpan delay)
{
PulseEnded?.Invoke(this, new PulseEventArgs(date, delay));
Debug.WriteLine("# Heartbeat [PULSE END] after " + (int)delay.TotalMilliseconds + " ms");
}
}

Categories

Resources