Have you ever used the
ping utility in Linux, OS X, or Windows before? Well, that's what this is. I wanted to take a little bit more of a relaxed pace for this week and introduce you to the
Ping class. Instead of having to write our own ICMP code, Microsoft was nice enough to provide us with this utility in the
System.Net.NetworkInformation namespace. It operates on the simple basis of sending out pings using the
Ping class, and then processing
PingReplys. You probably won't find much use for this unless you're writing a multi-user application (such as a video game).
Below is an example of synchronous and asynchronous (w/ callbacks) usage of the class. It's dead simple.
When I was testing this under Linux (Ubuntu flavor) I was getting an exception thrown at me when I instantiated the
Ping class (line 68), but that only would happen when I ran the application in Debug mode from the MonoDevelop IDE. It was completely fine if I ran the binary via a terminal window. Frankly, I don't know why this happens but I'm going to assume it is some sort of permission issue.
I had no issues on Windows and OS X when running it through the IDE or the command line.
I've sure discovered some Windows Weirdness along the way when writing these tutorials (all in the network stack). I found a new one today. If you try to make an synchronous Ping call (via
Send()) on a
Ping object that's already in the process of an async Ping, it will throw an error. This doesn't happen Linux or OS X.
If you're running the Microsoft OS, go ahead and try putting the
SendSynchronousPing() call right after the
pinger.SendAsync() call. You'll see it happen.
Printing() is a small function that prints some info about a
PingReply to the console. Note that it will lock the console and asks for a colour. This is done so that the output from the synchronous method and the async one is not interlaced. The colour helps with identifying what is each.
PingCompletedHanlder() is a callback for the
Ping.SendAsync() method. It is attached to the
Ping.PingCompleted event (line 69). It will be called regardless if an ICMP echo response was received or not. When we call
SendAsync() on our
pinger (line 77) we always need to pass in a
userToken. This must be unique among the async callbacks made. In our case we are using an
AutoResetEvent. This is also used to notify the calling
Thread if the async callback has completed or not, so even if the Ping wasn't successful,
Set() must be called on the
SendSynchronousPing() calls Ping.Send() and then print out some info based upon the the returned
PingReply if it was a success or not (checked with the
Status property). There are many overloads of
Send(), but the one we're using has a timeout attached to it (set to 2000 milliseconds).
You might have noticed that there are two different asynchronous methods in
SendPingAsync(). The first one uses callbacks (like in this example). The second will will return a
PingReply encased in a
Task when it's completed; so it's essentially an async version of the synchronous
Personally I prefer callbacks and like the first async method instead.
In the Main() function we poll the user for a domain name to ping. All of the
Ping methods we use will resolve DNS for us. First we try to send the ICMP via an async method, then after it with the synchronous one. Note that the
_timeout for the async method doesn't have to be put in the
WaitOne() call, but can be set in the
SendAsync() call too.
What is the difference between putting the
_timeout in the
SendAsync() call vs. the
WaitOne() of our
In the first case, the timeout will be started when the async call begins. If the async call doesn't finish in that time, it will terminate. But when it's put on the AutoResetEvent it will wait there for the timeout there, instead of when the async call was started. Think about it this way "Make sure the ping finishes within X amount of milliseconds," or "Check if the ping has finished by a certain point within X amount of milliseconds."
There isn't too much to talk about this. It's just a data structure that contains information about our ping. The important fields to look at are
RoundtripTime. That first one can be anything from the
IPStatus enumeration. The second is what's really valuable to us. It is how long (in milliseconds) the Ping took to get to the destination and back to us. By the nature of how networks are structured, the time it took to go to and the time it took to go back are not the same. You can't divide that value by 2 and think "This is how long it takes to send a message one way," that's very, very wrong.
I also want to note that the pings we sent had payloads of 0 bytes (see the
Buffer field). Normally these are stuffed with some data, but it's not always necessary.
In our code, we didn't take advantage of the
PingOptions class. It has only two fields;
Ttl. The former is boolean value that tells our pinger that we should not fragment the ICMP packets. It's default value is false. Along with setting larger
Buffers this can be useful to test the MTUs of devices the ping is sent through. The later stands for "Time To Live," which is fancy talk for "How many times can this Ping bounce from device to device before dying." The default value is 128, but setting it to a lower value is useful to test how many hops on the internet your connection needs to take to get to its destination.