I have to listen to several udp sockets and receive multicast datagrams. After receiving from some socket, data has to be sent to the TransformBlock. I don't want to copy received data for each received packet, so data buffer is sent to the TransformBlock by reference. And that's why each particular socket has to wait for the TransformBlock to finish processing before it can begin reading to it's buffer again.
I used code from this article for async operations with sockets.
Also I need an ability to add or delete sockets which to listen during the program execution.
My multicast listener class:
using ChannelWithDecoder = Tuple<FastChannel, TransformBlock<SocketAsyncEventArgs, List<Message>>>;
class MulticastReceiveManager
{
const int MaxPacketSize = 1400;
readonly ConcurrentDictionary<int, SocketReadData> socketDataByPort = new ConcurrentDictionary<int, SocketReadData>();
public MulticastReceiveManager(IEnumerable<ChannelWithDecoder> channelsWithDecoders)
{
foreach (ChannelWithDecoder tuple in channelsWithDecoders) AddChannel(tuple.Item1, tuple.Item2);
}
static Socket CreateSocket(FastChannel channel)
{
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
var end = new IPEndPoint(IPAddress.Any, channel.Port);
socket.Bind(end);
//подписываемся на source specific multicast
var membershipAddresses = new byte[12]; // 3 IPs * 4 bytes (IPv4)
IPAddress mcastGroup = IPAddress.Parse(channel.IP);
IPAddress mcastSource = IPAddress.Parse(channel.SourceIP);
Buffer.BlockCopy(mcastGroup.GetAddressBytes(), 0, membershipAddresses, 0, 4);
Buffer.BlockCopy(mcastSource.GetAddressBytes(), 0, membershipAddresses, 4, 4);
Buffer.BlockCopy(IPAddress.Any.GetAddressBytes(), 0, membershipAddresses, 8, 4);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddSourceMembership, membershipAddresses);
return socket;
}
public void AddChannel(FastChannel channel, TransformBlock<SocketAsyncEventArgs, List<Message>> decoderTransformBlock)
{
var args = new SocketAsyncEventArgs();
args.SetBuffer(new byte[MaxPacketSize], 0, MaxPacketSize);
var socketAwaitable = new ReceiveSocketAwaitable(args);
Socket socket = CreateSocket(channel);
var socketReadData = new SocketReadData(socket, socketAwaitable, decoderTransformBlock);
if (!socketDataByPort.TryAdd(channel.Port, socketReadData))
throw new ArgumentException("Channel with port number = " + channel.Port + " already exists in dictionary. IP = " + channel.IP);
}
public void DeleteChannel(FastChannel channel)
{
SocketReadData socketReadData;
if (!socketDataByPort.TryRemove(channel.Port, out socketReadData))
throw new ArgumentException("Channel with port number = " + channel.Port + " could not be removed from dictionary. IP = " + channel.IP);
}
public async Task StartListen(CancellationToken token)
{
SocketReadData socketReadData = socketDataByPort.Values.First();
while (!token.IsCancellationRequested)
{
await socketReadData.Socket.ReceiveAsync(socketReadData.Awaitable);
SocketAsyncEventArgs args = socketReadData.Awaitable.EventArgs;
if (args.BytesTransferred > 0) await socketReadData.DecoderTransformBlock.SendAsync(args, token);
}
}
}
Socket data:
class SocketReadData
{
public Socket Socket { get; }
public ReceiveSocketAwaitable Awaitable { get; }
public TransformBlock<SocketAsyncEventArgs, List<Message>> DecoderTransformBlock { get; }
public SocketReadData(Socket socket, ReceiveSocketAwaitable awaitable, TransformBlock<SocketAsyncEventArgs, List<Message>> decoderTransformBlock)
{
Socket = socket;
Awaitable = awaitable;
DecoderTransformBlock = decoderTransformBlock;
}
}
And just in case, the code from the article for socket awaitable:
class ReceiveSocketAwaitable : INotifyCompletion
{
static readonly Action Sentinel = () => { };
Action mContinuation;
public bool WasCompleted { get; set; }
public SocketAsyncEventArgs EventArgs { get; }
public bool IsCompleted => WasCompleted;
public ReceiveSocketAwaitable(SocketAsyncEventArgs eventArgs)
{
Contract.Requires<ArgumentNullException>(eventArgs != null, "eventArgs can't be null");
EventArgs = eventArgs;
eventArgs.Completed += delegate
{
Action prev = mContinuation ?? Interlocked.CompareExchange(ref mContinuation, Sentinel, null);
prev?.Invoke();
};
}
public void OnCompleted(Action continuation)
{
if (mContinuation == Sentinel || Interlocked.CompareExchange(ref mContinuation, continuation, null) == Sentinel)
{
Task.Run(continuation);
}
}
public void Reset()
{
WasCompleted = false;
mContinuation = null;
}
public ReceiveSocketAwaitable GetAwaiter()
{
return this;
}
public void GetResult()
{
if (EventArgs.SocketError != SocketError.Success) throw new SocketException((int)EventArgs.SocketError);
//return EventArgs.BytesTransferred;
}
}
And extensions:
static class SocketExtensions
{
public static ReceiveSocketAwaitable ReceiveAsync(this Socket socket, ReceiveSocketAwaitable awaitable)
{
awaitable.Reset();
if (!socket.ReceiveAsync(awaitable.EventArgs)) awaitable.WasCompleted = true;
return awaitable;
}
public static ReceiveSocketAwaitable SendAsync(this Socket socket, ReceiveSocketAwaitable awaitable)
{
awaitable.Reset();
if (!socket.SendAsync(awaitable.EventArgs)) awaitable.WasCompleted = true;
return awaitable;
}
}
As you can see, this code achieves my goal for a single socket. In a loop it awaits for data and then await for transformblock to accept the packet. And only after that it can read data from a socket to the buffer again.
So the question is: how can i achieve this behavior for many sockets using async/await pattern, without creating threads and copying data for each packet?
Related
I have list of ipaddress. I am using udp protocol to send request to these addressses asynchronously to an snmp agent. The available snmp agent on these address reply. When I call BeginSendTo with these addresses, devices are replying in random order. When ReceiveBeginReceiveFrom is called and stateobject object is constructed. The RemoteIPEndPoint doesn't belong to the machine that replied instead it belongs to other machine.
Is my approach of calling BeginSendTo is correct?
If Yes, y stateobject instance doesn't give me correct remoteendpoint that replied?
I am posting my code. Please correct if case something is missing.
public class Test
{
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
class StateObject
{
public Socket Socket { get; set; }
public byte[] DataBuff { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
//validation are omited
public static StateObject Create(Socket s,byte[] dataBuf,IPEndPoint endpoint)
{
return new StateObject() { Socket = s, DataBuff = dataBuf, RemoteEndPoint = endpoint };
}
public static StateObject Transform(object objectstate)
{
return objectstate as StateObject;
}
}
public void Fx()
{
//list of ip address from 190.188.191.1 - 190.188.191.100
var address = new[] {
IPAddress.Parse("190.188.191.1"),
IPAddress.Parse("190.188.191.100")
};
byte[] dataGram = new byte[1024];
foreach (IPAddress item in address)
{
udpSocket.BeginSendTo(dataGram,
0,
dataGram.Length,
SocketFlags.None,
new IPEndPoint(item, 161),
SendComplete,
StateObject.Create(udpSocket, null, new IPEndPoint(item, 161))
);
}
}
private void SendComplete(IAsyncResult ar)
{
StateObject obj = StateObject.Transform(ar.AsyncState);
obj.Socket.EndSendTo(ar);//ignore the no of bytes send for now
byte[] receivedBytes = new byte[1024 * 4];//assume 4kb is enough for response
var ep = obj.RemoteEndPoint as EndPoint;
obj.Socket.BeginReceiveFrom(receivedBytes,
0,
receivedBytes.Length,
SocketFlags.None,
ref ep,
ReceivedData,
StateObject.Create(obj.Socket, receivedBytes, obj.RemoteEndPoint)
);
}
private void ReceivedData(IAsyncResult ar)
{
StateObject obj = StateObject.Transform(ar.AsyncState);
var ep = obj.RemoteEndPoint as EndPoint;
//response received from ip 190.188.191.2 but in stateobject.remoteendpoint will give 190.188.191.1
var bytesReceived = obj.Socket.EndReceiveFrom(ar,ref ep);
byte[] data = new byte[bytesReceived];
Array.Copy(obj.DataBuff,data, bytesReceived);
}
}
Finally i figured it out to solve this issue of fanning out multiple udp requests. New field introduced in State Type called "ID". When generating ip address a unique identifier is generated. This identifier is assigned to State Type instance and cached. Also this ID is sent in request inorder to identify the response from a specific IP address and retrieve its state value from its cache.
Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
ProtocolType.Udp);
Dictionary<int, StateObject> _Cache = new Dictionary<int, StateObject>();
class StateObject
{
public int ID { get; set; }//uniquely identify the response
public Socket Socket { get; set; }
public byte[] DataBuff { get; set; }
public IPEndPoint RemoteEndPoint { get; set; }
//validation are omited
public static StateObject Create(Socket s, byte[] dataBuf, IPEndPoint endpoint)
{
return new StateObject() { Socket = s, DataBuff = dataBuf, RemoteEndPoint = endpoint };
}
public static StateObject Transform(object objectstate)
{
return objectstate as StateObject;
}
}
//This function is used to generate range of address and corresponding identifiers
IEnumerable<KeyValuePair<IPAddress, int>> GenerateMaps(IPAddress start, IPAddress end)
{
int count = -1;
yield return new KeyValuePair<IPAddress, int>(start, ++count);
//other address in the range
yield return new KeyValuePair<IPAddress, int>(end, ++count);
}
Also instead of receiving from particular host BeginReceiveFrom, instead i replaced with BeginReceive function that is capable of receiving and host. Mapping of identifier helped solve the issue in a cache.
I'm trying to send an UDP Broadcast with Windows Phone 8.1. Therefore, I created a UDP Server (a console app) which runs on my laptop in the same Wifi Network as my WP8.1.
This is the test server:
static void Main(string[] args)
{
var client = new UdpClient(12345);
client.MulticastLoopback = false;
var isRunning = true;
var thread = new Thread(() =>
{
while (isRunning)
{
var endpoint = new IPEndPoint(IPAddress.Any, 0);
var data = client.Receive(ref endpoint);
Console.WriteLine("Received message from {0}:{1} with length={2}", endpoint.Address, endpoint.Port, data.Length);
client.Send(data, data.Length, endpoint);
}
});
thread.Start();
Console.ReadLine();
isRunning = false;
}
When I create another console app which sends some data as UDP Broadcast, I will get the same data as response from the server. So the server is working fine. With Windows Phone 8.1 I have to use DatagramSocket. This is my code:
var socket = new DatagramSocket();
socket.MessageReceived += HandleIncomingMessages;
using (var stream = await socket.GetOutputStreamAsync(new HostName("255.255.255.255"), "12345"))
{
await stream.WriteAsync(data.AsBuffer());
await stream.FlushAsync();
}
I also tried it with an additional await _socket.ConnectAsync(new HostName("255.255.255.255"), "12345"); after creating the socket object.
Unfortunately, I'll never get an answer.
This is my implementation for a UdpClient which supports broadcast.
public class UdpClient : IDisposable
{
private readonly DatagramSocket _socket;
private readonly BlockingCollection<Tuple<IpAddress, byte[]>> _incomingMessages;
private readonly IpAddress _ipAddress;
private readonly object _lock = new object();
private bool _isBound;
private bool _isBinding;
public UdpClient(IpAddress ipAddress)
{
_ipAddress = ipAddress;
_socket = new DatagramSocket();
_socket.Control.DontFragment = true;
_incomingMessages = new BlockingCollection<Tuple<IpAddress, byte[]>>();
_socket.MessageReceived += HandleIncomingMessages;
}
public async Task SendAsync(byte[] data)
{
try
{
await Connect();
using (var stream = await _socket.GetOutputStreamAsync(_ipAddress.Host, _ipAddress.ServiceName))
{
await stream.WriteAsync(data.AsBuffer(0, data.Length));
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
public bool TryGetIncomingMessage(out Tuple<IpAddress, byte[]> message)
{
return _incomingMessages.TryTake(out message, TimeSpan.FromMilliseconds(20));
}
public void Dispose()
{
_socket.Dispose();
}
private async Task Connect()
{
if (_isBound || _isBinding)
{
return;
}
lock (_lock)
{
if (_isBound || _isBinding)
{
return;
}
_isBinding = true;
}
var possibleConnectionProfiles = NetworkInformation.GetConnectionProfiles()
.Where(p => p.IsWlanConnectionProfile && p.GetNetworkConnectivityLevel() != NetworkConnectivityLevel.None)
.ToList();
var connectionProfile = possibleConnectionProfiles.FirstOrDefault();
if (connectionProfile != null)
{
await _socket.BindServiceNameAsync(_ipAddress.ServiceName, connectionProfile.NetworkAdapter);
}
_isBound = true;
_isBinding = false;
}
private void HandleIncomingMessages(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs e)
{
var address = e.RemoteAddress.ToString();
var port = int.Parse(e.RemotePort);
using (var reader = e.GetDataReader())
{
var data = reader.DetachBuffer().ToArray();
_incomingMessages.Add(Tuple.Create(new IpAddress(address, port), data));
}
}
}
public struct IpAddress
{
public static readonly IpAddress Broadcast = new IpAddress("255.255.255.255");
public readonly string Address;
public readonly int Port;
public readonly HostName Host;
public readonly string ServiceName;
public IpAddress(string address, int port)
{
Address = address;
Port = port;
Host = new HostName(address);
ServiceName = port.ToString(CultureInfo.InvariantCulture);
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}:{1}", Address, Port);
}
public bool Equals(IpAddress other)
{
return string.Equals(Address, other.Address) && Port == other.Port;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
return obj is IpAddress && Equals((IpAddress)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((Address != null ? Address.GetHashCode() : 0) * 397) ^ Port;
}
}
}
I have read that it's not a good habit to broadcast messages to 255.255.255.255. Instead one should send to the local broadcast address like (i.e. 192.168.1.255)
For this I wrote this IpAddressProvider:
public static class IpProvider
{
private static readonly Lazy<string> _localAddress;
private static readonly Lazy<string> _broadcastAddress;
static IpProvider()
{
_localAddress = new Lazy<string>(GetLocalAddress);
_broadcastAddress = new Lazy<string>(GetBroadcastAddress);
}
public static string LocalAddress { get { return _localAddress.Value; } }
public static string BroadcastAddress { get { return _broadcastAddress.Value; } }
private static string GetLocalAddress()
{
var hostnames = NetworkInformation.GetHostNames();
foreach (var hn in hostnames)
{
//IanaInterfaceType == 71 => Wifi
//IanaInterfaceType == 6 => Ethernet (Emulator)
if (hn.IPInformation != null &&
(hn.IPInformation.NetworkAdapter.IanaInterfaceType == 71
|| hn.IPInformation.NetworkAdapter.IanaInterfaceType == 6))
{
return hn.DisplayName;
}
}
return IpAddress.Broadcast.Address;
}
private static string GetBroadcastAddress()
{
var parts = _localAddress.Value.Split(new[] { '.' }).Take(3);
return string.Join(".", parts) + ".255";
}
}
And now I use them together to send broadcast messages:
var gatewayAddress = new IpAddress(IpProvider.BroadcastAddress);
var udpClient = new UdpClient(gatewayAddress);
await udpClient.SendAsync(data);
Tuple<IpAddress, byte[]> message;
while (udpClient.TryGetIncomingMessage(out message))
{
if (message.Item1.Address == IpProvider.LocalAddress)
{
continue;
}
HandleIncomingPacket(message);
}
Ok so I'm working on a simple data entry software for work, using a 3-tier architecture. What I'm doing is basically using a common dll file to store classes with fields that are filled out when a request to the server is initialized. I have various user controls set up to take care of different ui forms, this works just fine if I open a connection and send the object directly from the form itself, but that creates a lot of redundant code. So I thought, lets make a MasterControl and create a public Connect_To_Server method to send objects over and then process the information sent back. Only problem is it keeps freezing my server.
Here is the code for my MasterControl:
public void Connect_To_Server(object obj)
{
using (TcpClient tcpClient = new TcpClient())
{
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("10.20.51.251"), 57657);
try
{
tcpClient.Connect(endPoint);
NetworkStream clientStream = tcpClient.GetStream();
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(clientStream, obj);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + " - " + ex.StackTrace);
}
}
}
Of course it's going to be a bit more complex later on but just for testing I've made it simple. Then from one of my ControlForms I create an object from one of the classes listed in the Common.dll, assign it it's values and then pass it to the Connect_To_Server method.
For instance a couple classes from my Common.dll
[Serializable]
public class Switch_Request // Switch class contains an integer describing a boolean request
{
public int switchID { get; set; }
}
[Serializable]
public class Send_String_List // Returns a list of strings
{
public int switchID { get; set; }
public List<string> stringsList { get; set; }
}
On my Server's side:
private void bt_StartServer_Click(object sender, EventArgs e) // Starts the server listening for clients
{
Set_String("System: Starting Services.");
tm_ActiveTime.Start();
serverListener = new TcpListener(IPAddress.Any, port);
serverThread = new Thread(new ThreadStart(Listen_For_Clients));
serverThread.Start();
}
private void Listen_For_Clients() // Listens for incoming client connections then redirects
{
serverListener.Start();
Set_String("System: Listening for connections...");
while (true)
{
tcpClient = serverListener.AcceptTcpClient();
clientThread = new Thread(new ParameterizedThreadStart(Handle_Client_Communication));
clientThread.Start(tcpClient);
}
}
private void Handle_Client_Communication(object client) // Handles incoming client communications
{
TcpClient tempClient = (TcpClient)client;
clientStream = tempClient.GetStream();
object sentObjet = formatter.Deserialize(clientStream);
Type objectType = sentObjet.GetType();
else if(objectType == typeof(Switch_Request)) // Handle - Switch Request
{
Switch_Request switchRequest = (Switch_Request)sentObjet;
object obj = new object();
if (switchRequest.switchID == 1)
{
}
else if (switchRequest.switchID == 2)
{
}
else if (switchRequest.switchID == 3)
{
}
else if (switchRequest.switchID == 4)
{
}
else if (switchRequest.switchID == 5)
{
}
else if (switchRequest.switchID == 6)
{
Send_String_List sendStringList = new Send_String_List();
sendStringList.switchID = 6;
sendStringList.stringsList = Get_Users();
obj = sendStringList;
}
else if (switchRequest.switchID == 7)
{
}
else if (switchRequest.switchID == 8)
{
}
else if (switchRequest.switchID == 9)
{
}
else if (switchRequest.switchID == 10)
{
}
formatter.Serialize(clientStream, obj);
Now I know that the object is being sent and received on the other end, as I set a little text display to popup when the object arrives on the other end, and it displays the correct object and it's type just before the server freezes. I'm just not sure why it keeps seizing up....
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");
}
}
I need a simple JSON-RPC 1.0 client in C#, preferably using .NET 2.0 or later.
I checked out JRock 0.9
They have several samples including Yahoo reader, but the samples demo JSON, not JSON-RPC.
I understand I could implement RPC part using any of the available JSON parsers, like JRock or two from Microsoft. I would prefer a ready sample.
2 Samples here
There are two different implementations. Read the whole thread + check the attachments
The samples above work with HTTP requests. Here's a variant that works with TCP (the Nil class is just an empty class used for requests with no return value):
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Newtonsoft.Json.Linq;
using AustinHarris.JsonRpc;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reactive.Concurrency;
using System.Net.Sockets;
using System.Text;
namespace JsonRpc
{
public class JsonRpcClient
{
private static object idLock = new object();
private static int id = 0;
public Encoding encoding { get; set; }
public JsonRpcClient(IPEndPoint serviceEndpoint, Encoding encoding)
{
this.serviceEndPoint = serviceEndpoint;
this.encoding = encoding;
}
private static Stream CopyAndClose(Stream inputStream)
{
const int readSize = 256;
byte[] buffer = new byte[readSize];
MemoryStream ms = new MemoryStream();
int count = inputStream.Read(buffer, 0, readSize);
while (count > 0)
{
ms.Write(buffer, 0, count);
count = inputStream.Read(buffer, 0, readSize);
}
ms.Position = 0;
inputStream.Close();
return ms;
}
public IObservable<JsonResponse<T>> InvokeWithScheduler<T>(string method, object arg, IScheduler scheduler)
{
var req = new AustinHarris.JsonRpc.JsonRequest()
{
Method = method,
Params = new object[] { arg }
};
return InvokeRequestWithScheduler<T>(req, scheduler);
}
public IObservable<JsonResponse<T>> InvokeSingleArgument<T>(string method, object arg)
{
var req = new AustinHarris.JsonRpc.JsonRequest()
{
Method = method,
Params = new object[] { arg }
};
return InvokeRequest<T>(req);
}
public IObservable<JsonResponse<T>> InvokeWithScheduler<T>(string method, object[] args, IScheduler scheduler)
{
var req = new AustinHarris.JsonRpc.JsonRequest()
{
Method = method,
Params = args
};
return InvokeRequestWithScheduler<T>(req, scheduler);
}
public IObservable<JsonResponse<T>> Invoke<T>(string method, object[] args)
{
var req = new AustinHarris.JsonRpc.JsonRequest()
{
Method = method,
Params = args
};
return InvokeRequest<T>(req);
}
public IObservable<JsonResponse<T>> InvokeRequestWithScheduler<T>(JsonRequest jsonRpc, IScheduler scheduler)
{
var res = Observable.Create<JsonResponse<T>>((obs) =>
scheduler.Schedule(()=>{
makeRequest<T>(jsonRpc, obs);
}));
return res;
}
public IObservable<JsonResponse<T>> InvokeRequest<T>(JsonRequest jsonRpc)
{
return InvokeRequestWithScheduler<T>(jsonRpc, ImmediateScheduler.Instance);
}
private string sendAndReceive(string messageToSend) {
string res = null;
// Data buffer for incoming data.
byte[] bytes = new byte[1024];
// Connect to a remote device.
try {
// Create a TCP/IP socket.
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp );
// Connect the socket to the remote endpoint. Catch any errors.
try {
socket.Connect(this.serviceEndPoint);
Console.Write("Socket connected to "+socket.RemoteEndPoint.ToString());
// Encode the data string into a byte array.
byte[] msg = encoding.GetBytes(messageToSend);
// Send the data through the socket.
int bytesSent = socket.Send(msg);
// Receive the response from the remote device.
int bytesRec = socket.Receive(bytes);
res = encoding.GetString(bytes,0,bytesRec);
Console.Write("Server response = "+res);
// Release the socket.
socket.Shutdown(SocketShutdown.Both);
socket.Close();
} catch (ArgumentNullException ane) {
Console.Write("ArgumentNullException : "+ane.ToString());
} catch (SocketException se) {
Console.Write("SocketException : " + se.ToString());
} catch (Exception e) {
Console.Write("Unexpected exception : " + e.ToString());
}
} catch (Exception e) {
Console.Write(e.ToString());
}
return res;
}
private void makeRequest<T>(JsonRequest jsonRpc, IObserver<JsonResponse<T>> obs)
{
JsonResponse<T> rjson = null;
string sstream = "";
try
{
int myId;
lock (idLock)
{
myId = ++id;
}
jsonRpc.Id = myId.ToString();
}
catch (Exception ex)
{
obs.OnError(ex);
}
try
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(jsonRpc)+"\r\n";
if (typeof(T).Equals(typeof(Nil)))
{
sendAndReceive(json);
rjson = new JsonResponse<T>();
}
else
{
sstream = sendAndReceive(json);
rjson = Newtonsoft.Json.JsonConvert.DeserializeObject<JsonResponse<T>>(sstream);
}
}
catch (Exception ex)
{
obs.OnError(ex);
}
if (rjson == null)
{
string exceptionMessage = "";
try
{
JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(sstream) as JObject;
exceptionMessage = jo["Error"].ToString();
}
catch(Exception ex){
exceptionMessage = sstream+"\r\n"+ex.Message;
}
obs.OnError(new Exception(exceptionMessage));
}
else
{
obs.OnNext(rjson);
}
obs.OnCompleted();
}
public IPEndPoint serviceEndPoint { get; set; }
}
}
Here is an example of a .net4 client exposed through Observables (Rx).
http://jsonrpc2.codeplex.com/SourceControl/changeset/view/13061#63133
Here is an almost identical wp7 client that is also exposed through Rx.
http://jsonrpc2.codeplex.com/SourceControl/changeset/view/13061#282775
Both of those examples do their work asynchronously so they may be more complicated then you are looking for, unless of course you wanted examples that were asynchronous. :)
Good Luck!