Reputation: 5008
im having difficulties figuring this out for myself hence asking here. i have an application which installs a client on a remote machine and the client reports back through a socket.
im trying to create a hold timer. which means the client will send a hello message every 1 minute to the server.. if the server doesnt get a hello packet from the client within the hold timer the client is removed. i can do it fine with one client... but for multiple i cannot figure it out. i was thinking to create a thread foreach new client that connected to the server. and then inside that thread start a hold timer.. but how do i distinguish clients from one another with the threads and how do i reset the hold timer IF a hello packet is recieved from a client.
i thought of creating a new thread for every hello packet and stopping the old one. but i dont know how cpu intensive this will be
if you dont understand my question, please say so and ill try to explain whatever you dont understand.
which solution is better? Solution A) starting a new thread and stopping the old thread everytime a hello packet arrives? (every 40 secs) - from 100 clients
Solution B) insert much better more scalable solution here.
Solution C) can i access a timer inside a thread i create dynamically? - i have unique threadnames on all dynamically created threads.
public void _clientholdtimer()
{
holdtimer = new Timer();
holdtimer.Interval = SECtoMS(Settings.Default.Hold);
holdtimer.Elapsed += holdtimer_Elapsed;
}
and reset it when i recieve a hello packet from client with same name as thread?
Solution D) store the timers in a dictionary public Dictionary timersDict = new Dictionary(); and the loop find the reference in the dict on hello packet recieved reset that timer.?
foreach(var item in timersDict)
{
if(item.Key == stripip(client.RemoteEndPoint.ToString()))
TimerReset(item.Value);
}
SOLUTION
Setup up the remote host application, to send a "hello" message to the Server every 3 seconds. Create a timer on the server with an elapsed event hit every 1 sec. setup a static value to hold an int which determines, how long before I consider the remote machine dead, I chose 9 seconds.
when the server receives a hello message it stores the timestamp in a list among with the ip address of the client.
on the timer_elapsed event
void holdtimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
if (server.Clients.GetAllItems().Count != 0)
{
foreach (IScsServerClient _client in server.Clients.GetAllItems())
{
string ipaddr = stripip(_client.RemoteEndPoint.ToString());
bool containsentry = _clientlist.Any(item => item.ipadress == ipaddr);
if (containsentry)
{
foreach (Scsserverclient entry in _clientlist.Where(entry => entry.ipadress == ipaddr))
{
if ((DateTime.Now - TimeSpan.FromSeconds(Settings.Default.Hold)) > entry.lasthello &&
entry.isConnect != "Disconnected")
{
entry.client.Disconnect();
}
}
}
}
}
}
catch (Exception ex)
{
}
}
basically i run through a list of connected clients and if the last received hello message is older than 9 seconds, i consider to client to disconnected.
Upvotes: 1
Views: 488
Reputation: 29786
This is a perfect application for Reactive Extensions. Here's a quick consle-app example you can try (add Nuget package Rx-Main). It simulates clients by asking you to input an integer for their ID. After timeToHold has elapsed since a particular ID has been seen, the action in Subscribe is run.
The use of Synchronize is important - as of RX version 2 Subject OnNext is not threadsafe without the Synchronize call.
Thanks to RX, this solution is very efficient in how it uses timers and threads.
Note: I've added a much more detailed explanation for this on my blog here: http://www.zerobugbuild.com/?p=230
public void Main()
{
var clientHeartbeats = new Subject<int>();
var timeToHold = TimeSpan.FromSeconds(5);
var expiredClients = clientHeartbeats
.Synchronize()
.GroupBy(key => key)
.SelectMany(grp => grp.Throttle(timeToHold));
var subscription = expiredClients.Subscribe(
// in here put your disconnect action
i => Console.WriteLine("Disconnect Client: " + i));
while(true)
{
var num = Console.ReadLine();
if (num == "q")
{
break;
}
// call OnNext with the client id each time they send hello
clientHeartbeats.OnNext(int.Parse(num));
}
// if you need to tear down you can do this
subscription.Dispose();
}
Addendum: Oh, if your server is responsible for starting clients, you may want to send an OnNext in from the server when it starts a client in case the client never sends a heartbeat.
Upvotes: 7
Reputation: 39007
What I would do, if I've understood your issue:
You can use a ConcurrentDictionary
for the collection, to easily write thread-safe code.
Upvotes: 2