Ping
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 PingReply
s. 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 waiter
.
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 Ping
. SendAsync()
and 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 Send()
.
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 AutoResetEvent
?
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."
PingReply
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 Status
and 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.
PingOptions
In our code, we didn't take advantage of the PingOptions
class. It has only two fields; DontFragment
and 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 Buffer
s 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.