Pinging multiple Stations as fast as possible

Hello,

i am working on some kind of network monitoring tool for Windows and Mac (not to be released). With this tool i need to ping 800+ stations as fast and as resource saving as possible. The online status of each station is populated in a Listbox.

For this i have a dictionary with ip addresses as keys and the ping results as values. To make it Windows and OS X compatible, i use the Shell and the PING command. But i am open for any other cross platform solution.

I do not want to slow down the GUI of my App, because it performs many other Tasks at the same time. User interactions are in most tasks involved.

That’s why i use a Thread which performs the PINGs and updates the dictionary. Plus a timer which checks each row in my Listbox (one column holds the ip addresses in the cell and the ping result in the celltags) and updates the celltags of the ip addresses column. The cellbackgroundpaint event then colors the rows dependent of the online/offline status.

The following code is a quick and (VERY) dirty solution, i wrote just to get it to work. I am pretty sure it can be done much more elegant and smarter. If someone could please help me “optimizing” this code, i would be very happy. :slight_smile:

[code] RunningThreads.Increase

Dim IPAdressen(-1) As String
Dim d As New Date
Dim theShell1 As New Shell
Dim theShell2 As New Shell
Dim theShell3 As New Shell
Dim theShell4 As New Shell
Dim theShell5 As New Shell
Dim theShell6 As New Shell

#If TargetWin32 Then
theShell1.Timeout = -1
theShell2.Timeout = -1
theShell3.Timeout = -1
theShell4.Timeout = -1
theShell5.Timeout = -1
theShell6.Timeout = -1
#EndIf

theShell1.Mode = 2
theShell2.Mode = 2
theShell3.Mode = 2
theShell4.Mode = 2
theShell5.Mode = 2
theShell6.Mode = 2

PingeVerteilerNummer = PingResult.Count
AnzahlPingTestDurchlaeufeBisher = AnzahlPingTestDurchlaeufeBisher + 1
DauerLetzterDurchlauf = d.TotalSeconds-StartDesDurchlaufs
StartDesDurchlaufs = d.TotalSeconds

For Each Value As Variant In PingResult.Keys
IPAdressen.Append Value.StringValue
Next

Dim x,y As Integer
y = IPAdressen.Ubound

For x = 0 To y Step 6
PingeVerteilerNummer = PingeVerteilerNummer - 6

If TargetWin32 Then
  theShell1.Execute "ping -n 1 -w 500 " + IPAdressen(x)
  If x+1<=y Then theShell2.Execute "ping -n 1 -w 500 " + IPAdressen(x+1)
  If x+2<=y Then theShell3.Execute "ping -n 1 -w 500 " + IPAdressen(x+2)
  If x+3<=y Then theShell4.Execute "ping -n 1 -w 500 " + IPAdressen(x+3)
  If x+4<=y Then theShell5.Execute "ping -n 1 -w 500 " + IPAdressen(x+4)
  If x+5<=y Then theShell6.Execute "ping -n 1 -w 500 " + IPAdressen(x+5)
Else
  theShell1.Execute "ping -c 1 -t 1 " + IPAdressen(x)
  If x+1<=y Then theShell2.Execute "ping -c 1 -t 1 " + IPAdressen(x+1)
  If x+2<=y Then theShell3.Execute "ping -c 1 -t 1 " + IPAdressen(x+2)
  If x+3<=y Then theShell4.Execute "ping -c 1 -t 1 " + IPAdressen(x+3)
  If x+4<=y Then theShell5.Execute "ping -c 1 -t 1 " + IPAdressen(x+4)
  If x+5<=y Then theShell6.Execute "ping -c 1 -t 1 " + IPAdressen(x+5)
End If

Do
  App.DoEvents(40)
Loop Until Not theShell1.IsRunning And Not theShell2.IsRunning And Not theShell3.IsRunning And Not theShell4.IsRunning And Not theShell5.IsRunning And Not theShell6.IsRunning

If theShell1.Errorcode <> 0 Then
  // alert or handle ping failure
  PingResult.Value(IPAdressen(x)) = False
Else
  PingResult.Value(IPAdressen(x)) = True
End If

If x+1<=y Then 
  If theShell2.Errorcode <> 0 Then
    // alert or handle ping failure
    PingResult.Value(IPAdressen(x+1)) = False
  Else
    PingResult.Value(IPAdressen(x+1)) = True
  End If
End If

If x+2<=y Then 
  If theShell3.Errorcode <> 0 Then
    // alert or handle ping failure
    PingResult.Value(IPAdressen(x+2)) = False
  Else
    PingResult.Value(IPAdressen(x+2)) = True
  End If
End If

If x+3<=y Then 
  If theShell4.Errorcode <> 0 Then
    // alert or handle ping failure
    PingResult.Value(IPAdressen(x+3)) = False
  Else
    PingResult.Value(IPAdressen(x+3)) = True
  End If
End If

If x+4<=y Then
  If theShell5.Errorcode <> 0 Then
    // alert or handle ping failure
    PingResult.Value(IPAdressen(x+4)) = False
  Else
    PingResult.Value(IPAdressen(x+4)) = True
  End If
End If

If x+5<=y Then
  If theShell6.Errorcode <> 0 Then
    // alert or handle ping failure
    PingResult.Value(IPAdressen(x+5)) = False
  Else
    PingResult.Value(IPAdressen(x+5)) = True
  End If
End If

If AppIsQuitting Then Exit

Next

RunningThreads.Decrease[/code]

Not because I want to kill your project. Since your are using ping, I just wonder maybe this application might be interesting as well:

SmokePing
http://oss.oetiker.ch/smokeping/

Creates also graph and you can setup email alerts

Demo: http://oss.oetiker.ch/smokeping-demo/?target=Customers.OP

[quote=77854:@John Hansen]Not because I want to kill your project. Since your are using ping, I just wonder maybe this application might be interesting as well:

SmokePing
http://oss.oetiker.ch/smokeping/

Creates also graph and you can setup email alerts

Demo: http://oss.oetiker.ch/smokeping-demo/?target=Customers.OP[/quote]

Thank you John, but the “Network Monitoring” is just a very small part of a bigger Project.
But i will keep your recommendation. Maybe i can use it later for another project. :slight_smile:

Are the IP’s static in the case of the systems or dynamic within a particular range?

Static

Why do you not create an array of shells:

Dim theShell(6) As Shell

For i as Integer = 0 to ubound(theShell)
         theShell(i)  = New Shell
Next

// And the same when you want to execute the shells

For i as Integer = 0 to ubound(theShell)
         theShell(i).execute(Command Array)
Next

I can’t because the amount of stations is unknown until i queried a few Servers. And arrays can only be dimensioned from constants or values, not variables.

Redim it based on the value you get ?

dim i as integer = 123
dim a(i) as integer

i = 656

redim a(i)

Done

Thank you Norman :slight_smile:

But it looks like there’s another issue. If i open 1000 Shells at once and start a Ping, i “loose the connection” to the Shells after i quierried 200 or so shells. Because after a certain amount of

Do ... theShell(i).IsRunning ... Loop Until ...

The Value of Shell(i).IsRunning is always “True” ?

I’d be amazed if you could open 100 terminal windows and ping that many sites all at once at any rate
Never mind trying to do it from within Xojo
I’d guess there’s some resource limits that will impede this effort

I started an example for you here:

http://www.xojodevspot.com/fastping.xojo_binary_project

and uncovered a strange bug in Xojo. Threading barely got started before the bug appeared. You’ll notice upon opening the project all source code has been commented out, and it still will not execute or build. The problem happened after adding a dictionary to the properties (removing the dictionary does not fix the issue). Also noticed that before the problem began I had to add a listbox twice (deleting it the first time), as Xojo stated that there were multiple items with the same name and which one could not be determined (hadn’t even added code or called an instance of the listbox anywhere in the code). First time ever any of these issues have appeared. Can you confirm or does the code work for you?

Will each of the systems being pinged have Xojo applications on them? If so you could save yourself time using the ADController (AutoDiscovery) class, in which case each system will report over the network that it is “alive and well.” It also reports when a system “goes down,” or is turned off using the Error/MemberJoined/MemberLeft events. You could install a small service app on the systems to do the monitoring in this way. Otherwise, using the ping method you will have latency before realizing a system has gone down. Running Threaded shells is bound to keep the CPU active, and ADController is CPU friendly.

Have you thought of just shelling out to nmap? It is cross platform and you can scan a whole subnet at once.

Use a pool of shells and in the Completed event of each shell, grab the next highest IP address and increment the count. Let it all run async. If you want to continue looping indefinitely, as soon as a shell hits the limit, it resets the next high to zero and it all starts over again.

Just add Window1 to the App DefaultWindow Property and it runs fine.

No. The “stations” are WiMAX, nanStation and other Wireless DSL Servers.

Thank you Tim. This is a well working solution. I do not even need a Thread. And the Network “Scan” now runs approx. 10 times faster. I was shocked how fast it is.

I have created a global Class of type Shell and create 10 instances of them. In the Completed event of the global Shell Class, i handle all the stuff like picking the next IP Adress, updating counters and so on.

What about devices that will not response to ICMP…? Not sure that sending RAW ping packets is the way to go.

You could also as an alternative to the often unreliable “ping” method do arping/arp-scan which is very quick.

1024 host arp-scanned in 4.181 seconds :slight_smile: Yep that’s fast ok

Just a thought.

Here is the fastest way I devised over the last 2 days. It involves adding an HTTPSOCKET to your window, then adding the “AuthorizationRequired” event with name and password to the router and uses a timer set at 1000 (1second periods).

In the case of our netgear router, I created a string and using the Get method of the httpsocket, invoking “http://192.168.1.1/DEV_device.htm” which retrieves an HTML rendition of both wired and wireless devices attached to the network, IP’s, stationname, and duration of time the system has been awake/online. I simply parsed out the needed information into a listbox’s columns and set the cell back color by online/offline status. In all, you can make a request once a second and have live second-by-second updates.

[quote=78558:@Matthew Combatti]Here is the fastest way I devised over the last 2 days. It involves adding an HTTPSOCKET to your window, then adding the “AuthorizationRequired” event with name and password to the router and uses a timer set at 1000 (1second periods).

In the case of our netgear router, I created a string and using the Get method of the httpsocket, invoking “http://192.168.1.1/DEV_device.htm” which retrieves an HTML rendition of both wired and wireless devices attached to the network, IP’s, stationname, and duration of time the system has been awake/online. I simply parsed out the needed information into a listbox’s columns and set the cell back color by online/offline status. In all, you can make a request once a second and have live second-by-second updates.[/quote]

Thank you, but only WiMAX and nanoStations have a http component. There are Stations involved which are only reachable via a Winbox System.

Tim’s solution works fine for me. I reduced the Shells to 5 at a time and reduced the processor load to 60% in peak times. Because the machine on which my App runs is not used for much more, it’s ok for me. :slight_smile:

For windows you can also use the WMI Classes with use of OLEObject to ping your target.

Below is an example: (works only from WIndows Vista and upwards)

[code]
// Info
// http://msdn.microsoft.com/en-us/library/aa394350(v=vs.85).aspx
// http://msdn.microsoft.com/en-us/library/aa394595(v=vs.85).aspx

Dim targetHost As String = “www.xojo.com

Dim locator, objWMIService, objsPings, objPingStatus As OLEOBJECT
Dim nobjPings as Integer

// Connect to WMI
locator = new oleObject(“WbemScripting.SWbemlocator”, true)

Dim wmiServiceParams(2) as variant
wmiServiceParams(1) = “.”
wmiServiceParams(2) = “root\cimv2”

objWMIService= locator.invoke(“ConnectServer”, wmiServiceParams)

// Run the WMI query
objsPings = objWMIService.ExecQuery (“Select * From Win32_PingStatus where Address = '”+targetHost+"’")

nobjPings = objsPings.count - 1

For i as integer = 0 to nobjPings
objPingStatus = objsPings.ItemIndex(i)

// ItemIndex() is not supported in Windows XP only from Windows Vista and upwards

if objPingStatus.Value("StatusCode") <> Nil  Then
  
  Dim stringData As String
  
  stringData = "Address: "  + objPingStatus.Value("Address") + EndOfLine
  stringData = stringData + "StatusCode: " + objPingStatus.Value("StatusCode") + EndOfLine
  stringData = stringData + "BufferSize: " + objPingStatus.Value("BufferSize") + EndOfLine
  stringData = stringData + "ProtocolAddress: " + objPingStatus.Value("ProtocolAddress") + EndOfLine
  stringData = stringData + "ReplyInconsistency: " + objPingStatus.Value("ReplyInconsistency") + EndOfLine
  stringData = stringData + "ReplySize: " + objPingStatus.Value("ReplySize") + EndOfLine
  stringData = stringData + "ResolveAddressNames: " + objPingStatus.Value("ResolveAddressNames") + EndOfLine
  stringData = stringData + "ResponseTime: " + objPingStatus.Value("ResponseTime") + EndOfLine
  stringData = stringData + "ResponseTimeToLive: " + objPingStatus.Value("ResponseTimeToLive") + EndOfLine
  stringData = stringData + "Timeout: " + objPingStatus.Value("Timeout") + EndOfLine
  stringData = stringData + "TimestampRoute: " + objPingStatus.Value("TimestampRoute") + EndOfLine
  stringData = stringData + "TimeToLive: " + objPingStatus.Value("TimeToLive") + EndOfLine
  stringData = stringData + "TypeofService: " + objPingStatus.Value("TypeofService") + EndOfLine
  
  msgbox stringData
Else
  Msgbox ("Target : "+objPingStatus.Value("Address")+EndOfLine + "did not respond")
End If

Next

locator = Nil

exception err as oleexception
msgbox err.message[/code]