Stefan Steiger
Stefan Steiger

Reputation: 82276

C# Async Ping: How to avoid an out of memory exception?

Question: I want to search the subnet for all computers in it. So I send a ping to all IP addresses in the subnet.

The problem is it works fine if I only scan 192.168.0.". But if I scan 192.168..*", then I get an "Out of memory" exception.

Why ? Do I have to limit the threads, or is the problem the memory consumed by new ping which doesn't get destructed once finished, or do I need to call gc.collect() ?

    static void Main(string[] args)
    { 
        string strFromIP = "192.168.0.1";
        string strToIP = "192.168.255.255";

        Oyster.Math.IntX omiFromIP = 0;
        Oyster.Math.IntX omiToIP = 0;
        IsValidIP(strFromIP, ref omiFromIP);
        IsValidIP(strToIP, ref omiToIP);
        for (Oyster.Math.IntX omiThisIP = omiFromIP; omiThisIP <= omiToIP; ++omiThisIP)
        {
            Console.WriteLine(IPn2IPv4(omiThisIP));
            System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(omiThisIP));
            SendPingAsync(sniIPaddress);
        }

        Console.WriteLine(" --- Press any key to continue --- ");
        Console.ReadKey();
    } // Main


    // http://pberblog.com/post/2009/07/21/Multithreaded-ping-sweeping-in-VBnet.aspx
    // http://www.cyberciti.biz/faq/how-can-ipv6-address-used-with-webbrowser/#comments
    // http://www.kloth.net/services/iplocate.php
    // http://bytes.com/topic/php/answers/829679-convert-ipv4-ipv6
    // http://stackoverflow.com/questions/1434342/ping-class-sendasync-help
    public static void SendPingAsync(System.Net.IPAddress sniIPaddress)
    {
        int iTimeout = 5000;
        System.Net.NetworkInformation.Ping myPing = new System.Net.NetworkInformation.Ping();
        System.Net.NetworkInformation.PingOptions parmPing = new System.Net.NetworkInformation.PingOptions();

        System.Threading.AutoResetEvent waiter = new System.Threading.AutoResetEvent(false);
        myPing.PingCompleted += new System.Net.NetworkInformation.PingCompletedEventHandler(AsyncPingCompleted);
        string data = "ABC";
        byte[] dataBuffer = Encoding.ASCII.GetBytes(data);

        parmPing.DontFragment = true;
        parmPing.Ttl = 32;

        myPing.SendAsync(sniIPaddress, iTimeout, dataBuffer, parmPing, waiter);
        //waiter.WaitOne();
    }


    private static void AsyncPingCompleted(Object sender, System.Net.NetworkInformation.PingCompletedEventArgs e)
    {

        System.Net.NetworkInformation.PingReply reply = e.Reply;
        ((System.Threading.AutoResetEvent)e.UserState).Set();
        if (reply.Status == System.Net.NetworkInformation.IPStatus.Success)
        {
            Console.WriteLine("Address: {0}", reply.Address.ToString());
            Console.WriteLine("Roundtrip time: {0}", reply.RoundtripTime);
        }
    }

Upvotes: 1

Views: 6034

Answers (6)

Josh Gayou
Josh Gayou

Reputation: 11

I did something similar to this. The way I solved the problem on my project was to cast the ping instance to IDisposable:

(myPing as IDisposable).Dispose()

So get a list of say 254 ping instances running asynchronously (X.X.X.1/254) and keep track of when all of them have reported in. When they have, iterate through your list of ping instances, run the above code on each instance, and then dump the list.

Works like a charm.

Upvotes: 1

Stefan Steiger
Stefan Steiger

Reputation: 82276

Finally... No ping requried at all...

http://www.codeproject.com/KB/cs/c__ip_scanner.aspx

All I needed to do is to make it thread-safe for debugging. Changing Add to:

void Add( string m )
{
    Invoke(new MethodInvoker(
        delegate
        {
            add.Items.Add(m);
        }));
    //add.Items.Add( m );
}

 

Private Sub Add(m As String)
    Invoke(New MethodInvoker(Function() Do
        add.Items.Add(m)
    End Function))
    'add.Items.Add(m);'
End Sub

Upvotes: 0

jgauffin
jgauffin

Reputation: 101150

First: Only start like 1000 pings the first time (in the loop in Main)

Second: Move the following parameters to Program class (member variables)

Oyster.Math.IntX omiFromIP = 0; 
Oyster.Math.IntX omiToIP = 0;
Oyster.Math.IntX omiCurrentIp = 0;
object syncLock = new object();

Third: In AsyncPingCompleted do something like this in the bottom:

public void AsyncPingCompleted (bla bla bla)
{
    //[..other code..]

    lock (syncLock) 
    {
        if (omiToIP < omiCurrentIp)
        {
           ++omiCurrentIp;
           System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(omiCurrentIp)); 
           SendPingAsync(sniIPaddress); 
        }
    }
}

Update with complete code example

public class Example
{
    // Number of pings that can be pending at the same time
    private const int InitalRequests = 10000;

    // variables from your Main method
    private Oyster.Math.IntX _omiFromIP = 0;
    private Oyster.Math.IntX _omiToIP = 0;
    private Oyster.Math.IntX _omiCurrentIp = 0;

    // synchronoize so that two threads
    // cannot ping the same IP.
    private object _syncLock = new object();

    static void Main(string[] args)
    {
        string strFromIP = "192.168.0.1";
        string strToIP = "192.168.255.255";

        IsValidIP(strFromIP, ref _omiFromIP);
        IsValidIP(strToIP, ref _omiToIP);
        for (_omiCurrentIp = _omiFromIP; _omiCurrentIp <= _omiFromIP + InitalRequests; ++_omiCurrentIp)
        {
            Console.WriteLine(IPn2IPv4(_omiCurrentIp));
            System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(_omiCurrentIp));
            SendPingAsync(sniIPaddress);
        }

        Console.WriteLine(" --- Press any key to continue --- ");
        Console.ReadKey();
    } // Main


    // http://pberblog.com/post/2009/07/21/Multithreaded-ping-sweeping-in-VBnet.aspx
    // http://www.cyberciti.biz/faq/how-can-ipv6-address-used-with-webbrowser/#comments
    // http://www.kloth.net/services/iplocate.php
    // http://bytes.com/topic/php/answers/829679-convert-ipv4-ipv6
    // http://stackoverflow.com/questions/1434342/ping-class-sendasync-help
    public void SendPingAsync(System.Net.IPAddress sniIPaddress)
    {
        int iTimeout = 5000;
        System.Net.NetworkInformation.Ping myPing = new System.Net.NetworkInformation.Ping();
        System.Net.NetworkInformation.PingOptions parmPing = new System.Net.NetworkInformation.PingOptions();

        System.Threading.AutoResetEvent waiter = new System.Threading.AutoResetEvent(false);
        myPing.PingCompleted += new System.Net.NetworkInformation.PingCompletedEventHandler(AsyncPingCompleted);
        string data = "ABC";
        byte[] dataBuffer = Encoding.ASCII.GetBytes(data);

        parmPing.DontFragment = true;
        parmPing.Ttl = 32;

        myPing.SendAsync(sniIPaddress, iTimeout, dataBuffer, parmPing, waiter);
        //waiter.WaitOne();
    }


    private void AsyncPingCompleted(Object sender, System.Net.NetworkInformation.PingCompletedEventArgs e)
    {

        System.Net.NetworkInformation.PingReply reply = e.Reply;
        ((System.Threading.AutoResetEvent)e.UserState).Set();
        if (reply.Status == System.Net.NetworkInformation.IPStatus.Success)
        {
            Console.WriteLine("Address: {0}", reply.Address.ToString());
            Console.WriteLine("Roundtrip time: {0}", reply.RoundtripTime);
        }


        // Keep starting those async pings until all ips have been invoked.
        lock (_syncLock)
        {
            if (_omiToIP < _omiCurrentIp)
            {
                ++_omiCurrentIp;
                System.Net.IPAddress sniIPaddress = System.Net.IPAddress.Parse(IPn2IPv4(_omiCurrentIp));
                SendPingAsync(sniIPaddress);
            }
        }
    }        
}

Upvotes: 2

Joel Mueller
Joel Mueller

Reputation: 28764

According to this thread, System.Net.NetworkInformation.Ping seems to allocate one thread per async request, and "ping-sweeping a class-B network creates 100's of threads and eventually results in an out-of-memory error."

The workaround that person used was to write their own implementation using raw sockets. You don't have to do that in F#, of course, but there are a number of advantages in doing so.

Upvotes: 2

dbasnett
dbasnett

Reputation: 11773

pseudo-code

do

if pings_running > 100 then
sleep 100ms.
else
start ping
endif

loop while morepings

Upvotes: 0

Peter Lillevold
Peter Lillevold

Reputation: 33930

I guess the problem is that you are spawning roughly 63K ping requests near-simultaneously. Without further memory profiling it is hard to say which parts consume the memory. You are working with network resources, which probably are limited. Throttling the number of active pings will ease the use of local resources, and also network traffic.

Again I would look into the Task Parallel Library, the Parallel.For construct combined with the Task<T> should make it easy for you.

Note: for .Net 3.5 users, there is hope.

Upvotes: 1

Related Questions