|
Some time ago Lance Olson put some code
together in an MSDN Magazine article about Peer- to-Peer in .NET at http://msdn.microsoft.com/msdnmag/issues/01/02/netpeers/netpeers.asp.
This is from February 2001 so the code is obviously from BETA 1, and won't
work in BETA 2 or Visual Studio.NET RTM without some serious revisions.
However, recently
I realized I needed to get up to speed with socket programming in C#,
and so I revisited the particular code portion in the article about doing
a PING. First of all, what is PING? Actually it has taken on several
distinct meanings. One is that Ping is actually an acronym for the words
'Packet INternet Groper'. Another is that it is in fact not an acronym
at all, but a noun that was adopted from a verb the US Navy uses to describe
what its submarines do when looking for objects under the sea. Their subs
send out sonar waves and then wait for a return wave when it bounces off
something, such as another sub, whale, ocean floor etc. This, in turn,
was adopted from bats and dolphins, who navigate in roughly the same way.
This is what a system administrator does when Ping is used. So Ping has
evolved into a verb in the computer industry, and it is used in somewhat
the same manner of the Navy. A Ping utility is essentially a system administrator's
tool that is used to see if a computer is operating and also to see if
network connections are intact. Ping uses the Internet Control Message
Protocol (ICMP) Echo function which is detailed in RFC 792. A small packet
is sent through the network to a particular IP address. This packet contains
64 bytes - 56 data bytes and 8 bytes of protocol reader information. The
computer that sent the packet then waits (or 'listens') for a return packet.
If the connections are good and the target computer is up, a good return
packet will be received.
But PING is also a useful utility for programmers.
Often it is critical to see if an endpoint is "up" before attemping
to send a message. Also, it can be very useful to estimate the response
time from the host. That's why I decided to sit down with Lance's excellent
code sample, revamp it for the Release version of Visual Studio.NET, and
turn it into a class library that can be used, among many other useful
things, in a webservice. It turned out to be an excellent exercise in
understanding socket programming in C# as I hope it will be for you, too.
If you search the web for examples of PING
in C# you will find some examples by a someone (whose name I won't mention)
who basically just "lifted" Olson's code and changed the namespace
and function names, and turned the BETA 1 code into a console application.
Well, we already have a console application called PING. My code comes
from the source, and I am more than happy to give the original author
credit for it. A number of classes and methods have changed considerably
since the original BETA 1 code was written, and I've updated everything
to be 100% Release version compliant.
I'm reproducing my code here inline, and
also making the project available to download at the bottom of this article.
using
System;
namespace .CBS.Util // change this to
yourcompany.yourdivision.whatever
{
using System;
using System.Net;
using System.Net.Sockets;
/// <summary>
/// The Main Ping Class
/// </summary>
public class Ping
{
//Declare some Constant Variables
const int SOCKET_ERROR = -1;
const int ICMP_ECHO = 8;
/// <summary>
/// </summary>
public static void Main()
{
}
/// <summary>
/// This public method takes the "hostname" of the server
/// and then it pings it and shows the response time
/// Data is returned as a string. IP addresses are resolved.
/// Example usage:
/// using .CBS.Util
/// Ping p = new Ping();
/// textBox2.Text = p.PingHost(textBox1.Text);
/// </summary>
public string PingHost(string host)
{
//Declare the IPHostEntry
IPHostEntry serverHE, fromHE;
int nBytes = 0;
int dwStart = 0, dwStop = 0;
//Initialize a Socket of Type ICMP
Socket socket =
new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout,
1000);
// Get the server endpoint
try
{
serverHE = Dns.GetHostByName(host);
}
catch(Exception)
{
return "Host not found"; // uh-oh!
}
// Convert the server IP_EndPoint to an EndPoint
IPEndPoint ipepServer = new IPEndPoint(serverHE.AddressList[0],
0);
EndPoint epServer = (ipepServer);
// Set the receiving endpoint to the client
machine
fromHE = Dns.GetHostByName(Dns.GetHostName());
IPEndPoint ipEndPointFrom = new IPEndPoint(fromHE.AddressList[0],
0);
EndPoint EndPointFrom = (ipEndPointFrom);
int PacketSize = 0;
IcmpPacket packet = new IcmpPacket(); // didn't
know we had this in .Net, eh?
//
(script kiddies: GET LOST!)
// Construct the packet to send
packet.Type = ICMP_ECHO; //8
packet.SubCode = 0;
packet.CheckSum = UInt16.Parse("0");
packet.Identifier = UInt16.Parse("45");
packet.SequenceNumber = UInt16.Parse("0");
int PingData = 32; // sizeof(IcmpPacket) - 8;
packet.Data = new Byte[PingData];
//Initialize the Packet.Data
for (int i = 0; i < PingData; i++)
{
packet.Data[i] = (byte)'#';
}
// Variable to hold the total Packet size
PacketSize = PingData + 8;
Byte [] icmp_pkt_buffer = new Byte[ PacketSize ];
Int32 Index = 0;
// Call Method Serialize which counts
// The total number of Bytes in the Packet
Index = Serialize(
packet,
icmp_pkt_buffer,
PacketSize,
PingData );
//Error in Packet Size
if( Index == -1 )
{
return "Error Creating Packet";
}
// convert into a UInt16 array
//Get the Half size of the Packet
Double double_length = Convert.ToDouble(Index);
Double dtemp = Math.Ceiling( double_length / 2);
int cksum_buffer_length = Convert.ToInt32(dtemp);
//Create a Byte Array
UInt16 [] cksum_buffer = new UInt16[cksum_buffer_length];
//Code to initialize the Uint16 array
int icmp_header_buffer_index = 0;
for( int i = 0; i < cksum_buffer_length; i++ )
{
cksum_buffer[i] =
BitConverter.ToUInt16(icmp_pkt_buffer,icmp_header_buffer_index);
icmp_header_buffer_index += 2;
}
//Call method to return a checksum
UInt16 u_cksum = checksum(cksum_buffer, cksum_buffer_length);
//Save checksum to Packet
packet.CheckSum = u_cksum;
// Now that we have the checksum, serialize
the packet again
Byte [] sendbuf = new Byte[ PacketSize ];
//again check the packet size
Index = Serialize(
packet,
sendbuf,
PacketSize,
PingData );
//if there is an error return it
if( Index == -1 )
{
return "Error Creating Packet";
}
dwStart = System.Environment.TickCount; // Start timing
//send the Packet over the socket
if ((nBytes = socket.SendTo(sendbuf, PacketSize, 0, epServer)) ==
SOCKET_ERROR)
{
return "Socket Error: cannot send Packet";
}
// Initialize the buffers. The receive buffer
is the size of the
// ICMP header plus the IP header (20 bytes)
Byte [] ReceiveBuffer = new Byte[256];
nBytes = 0;
// Receive the bytes
bool recd =false ;
int timeout=0 ;
// loop for checking the time of the server
response
while(!recd)
{
nBytes = socket.ReceiveFrom(ReceiveBuffer, 256, 0, ref EndPointFrom);
if (nBytes == SOCKET_ERROR)
{
return "Host not Responding" ;
}
else if(nBytes>0)
{
dwStop = System.Environment.TickCount - dwStart; // stop timing
return "Reply from "+epServer.ToString()+" in "
+dwStop+"ms. Received: "+nBytes+ " Bytes.";
}
timeout=System.Environment.TickCount - dwStart;
if(timeout>1000)
{
return "Time Out" ;
}
}
// close the socket
socket.Close();
return "";
}
/// <summary>
/// This method get the Packet and calculates the total size
/// of the Pack by converting it to byte array
/// </summary>
public static Int32 Serialize(IcmpPacket packet, Byte[] Buffer,
Int32 PacketSize, Int32 PingData )
{
Int32 cbReturn = 0;
// serialize the struct into the array
int Index=0;
Byte [] b_type = new Byte[1];
b_type[0] = (packet.Type);
Byte [] b_code = new Byte[1];
b_code[0] = (packet.SubCode);
Byte [] b_cksum = BitConverter.GetBytes(packet.CheckSum);
Byte [] b_id = BitConverter.GetBytes(packet.Identifier);
Byte [] b_seq = BitConverter.GetBytes(packet.SequenceNumber);
Array.Copy( b_type, 0, Buffer, Index, b_type.Length );
Index += b_type.Length;
Array.Copy( b_code, 0, Buffer, Index, b_code.Length );
Index += b_code.Length;
Array.Copy( b_cksum, 0, Buffer, Index, b_cksum.Length );
Index += b_cksum.Length;
Array.Copy( b_id, 0, Buffer, Index, b_id.Length );
Index += b_id.Length;
Array.Copy( b_seq, 0, Buffer, Index, b_seq.Length );
Index += b_seq.Length;
// copy the data
Array.Copy( packet.Data, 0, Buffer, Index, PingData );
Index += PingData;
if( Index != PacketSize/* sizeof(IcmpPacket) */)
{
cbReturn = -1;
return cbReturn;
}
cbReturn = Index;
return cbReturn;
}
/// <summary>
/// This Method has the algorithm to make a checksum
/// </summary>
public static UInt16 checksum( UInt16[] buffer, int size )
{
Int32 cksum = 0;
int counter;
counter = 0;
while ( size > 0 )
{
UInt16 val = buffer[counter];
cksum += Convert.ToInt32( buffer[counter] );
counter += 1;
size -= 1;
}
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (UInt16)(~cksum);
}
} // class ping
///
<summary>
/// Class that holds the Packet information
/// </summary>
public class IcmpPacket
{
public Byte Type; // type of message
public Byte SubCode; // type of sub code
public UInt16 CheckSum; // ones complement checksum of struct
public UInt16 Identifier; // identifier
public UInt16 SequenceNumber; // sequence number
public Byte [] Data;
} // class IcmpPacket
}
|
I'm not going into a treatise on socket
programming in C# as that's beyond the scope of this article. But what
this code does is illustrate the creation of sockets, socket types, protocol
types, creating a Packet and checksum, using the SocketOptions class,
and much more. Compile this into a class library, give it a strong name
with the SN utility, set the proper assemblyinfo directives, install in
the GAC, and you can include the library in any of your work that needs
a simple PING utility class.
And just to show how easy it is to use this,
I've gone ahead and created a PING webservice out of it. The following
is from my Service1.asmx.cs file (key WEBMETHOD code is highlighted):
using
System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Web;
using System.Web.Services;
using .CBS.Util;
namespace Ping
{
/// <summary>
/// Summary description for Service1.
/// </summary>
[ WebService(Description="Ping",Namespace="http://www.eggheadcafe.com/webservices")]
public class Service1 : System.Web.Services.WebService
{
public Service1()
{
//CODEGEN: This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}
#region Component Designer generated code
//Required by the Web Services Designer
private IContainer components = null;
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if(disposing && components != null)
{
components.Dispose();
}
base.Dispose(disposing);
}
#endregion
[WebMethod]
public string Ping( string ipOrHostName)
{
.CBS.Util.Ping p = new .CBS.Util.Ping();
return p.PingHost(ipOrHostName);
}
}
}
|
If you'd like to try out the actual live
webservice , GO
HERE.
Dowload
the code that accompanies this article
Peter Bromberg is an independent consultant specializing in distributed .NET solutions
in Orlando and a co-developer of the EggheadCafe.com
developer website. He can be reached at pbromberg@yahoo.com
|