C#写的一个ping程序

来源:互联网 发布:刚开的淘宝卖什么 编辑:程序博客网 时间:2024/06/10 05:20
C#写的一个ping程序

ICMP就是所谓的Internet控制报文协议(Internet Control Message Protocol),在网络中,一般用它来传递差错报文以及其他应注意的信息。ICMP一般被认为是和IP协议同一层的协议,IMCP报文通常被IP层或者更高层的协议(如:TCP或者UDP)使用,ICMP对于互联网以及其他基于IP协议的网络的正常运行起着非常重要的作用。有许多重要的网络程序都是基于ICMP协议上的,最为著名如Ping和Tracert等。本文就来介绍用Visual C#实现基于ICMP协议重要的网络命令Ping的方法。

  Ping命令是可以说是一个"跨平台"程序,这是因为Ping命令不仅存在Windows系统上,在Unix系统上也有Ping命令,其实对其他只要是支持网络的操作系统,一般也都存在该命令。Ping命令的主要作用是检测网络上主机的状态。要是在几年前,我们还可以下如此断言,如果不能Ping通某台主机,那么也就无法Telnet或者FTP到这台主机,但随着互联网的安全意识的增加,出现了访问控制清单的路由器和防火墙,由于ICMP报文是在IP数据包中被传输的,而到达一台主机不仅取决于IP层是否到达,还取决于使用何种协议和端口。譬如金山公司的金山网镖就可以禁止其他机器Ping通这台主机。所以在现在的情况下,即时Ping不通某台机器,但也有可能FTP登陆到这台机器,或者通过HTTP来浏览这台机器上的Web页面。

  一.Ping命令简介

  首先进入Windows系统中的命令提示符,输入"Ping/?"后,单击回车键,您就可以了解Ping命令的各种参数的使用方法。最为常见的使用方法是"Ping 远程计算机名称(或者远程计算机的IP地址)",如果在Ping命令的返回字符中有"Reply from",说明此主机在线。如果返回字符中有"Request timeout",一般情况此主机不在线。

  二.Ping命令、ICMP报文和IP数据包

  Ping命令基于的是TCP/IP协议簇中的ICMP协议,在编写基于ICMP协议的网络应用程序时,应注意下面二点:

  1. ICMP报文是封装在IP数据包中传输的。

  习惯上把IP数据包划分为三个部分:

  (1).IP数据包中的前二十个字节的数据

  (2).选项

  (3).数据

  其中后面二个部分组成的就是ICMP报文。

  2. ICMP协议没有固定的端口号。

  ICMP协议和其他协议不同,其他协议基本都对应固定的端口号,如HTTP协议是通过80端口号来交换数据的。

  了解上面的二点对后面在Visual C#实现Ping命令是非常有用的。因为在下面的在编写Visual C#实现Ping命令的程序中,程序中定义一个名称为"IcmpPacket"类,通过这个类来构造ICMP报文,而定义"IcmpPacket"类依据的就是图03所示的ICMP报文组成结构。同样由于ICMP协议没有对应固定的端口号,这就意味着,编写Visual C#实现Ping命令中可以随意选择端口号,本文选择的端口号是"30"。

  由于ICMP协议是一个复杂的协议,而本文由于篇幅所限,对ICMP的很多细节,就不能一一介绍,如果你对ICMP协议感兴趣或对上面的介绍的仍然感觉有点模糊,那就请参阅探讨ICMP协议的相关书籍,它们一般介绍的都很详细。

三.简介Visual C#实现Ping命令使用的类:

  Visual C#实现Ping命令中涉及到很多的类,其中最重要的是Socket类。这是因为程序中发送含有ICMP报文的IP数据包,接收含有ICMP超时或ICMP会显报文的IP数据包和设定IP数据包中的TTL数值都会使用到Socket类。下面是Socket类中的常用属性和方法及其简要说明。

AddressFamily

获取Socket的地址族。

Available

获取已经从网络接收且可供读取的数据量。

Blocking

获取或设置一个值,该值指示Socket是否处于阻塞模式。

Connected

获取一个值,该值指示Socket是否已连接到远程资源。

Handle

获取Socket的操作系统句柄。

LocalEndPoint

获取本地终结点。

ProtocolType

获取Socket的协议类型。

RemoteEndPoint

获取远程终结点。

SocketType

获取Socket的类型。

Accept

创建新的Socket以处理传入的连接请求。

BeginAccept

开始一个异步请求,以创建新的Socket来接受传入的连接请求。

BeginConnect

开始对网络设备连接的异步请求。

BeginReceive

开始从连接的Socket中异步接收数据。

BeginReceiveFrom

开始从指定网络设备中异步接收数据。

BeginSend

将数据异步发送到连接的

BeginSendTo

向特定远程主机异步发送数据。

Bind

使Socket与一个本地终结点相关联。

Close

强制Socket连接关闭。

Connect

建立到远程设备的连接。

EndAccept

结束异步请求以创建新的Socket来接受传入的连接请求。

EndConnect

结束挂起的异步连接请求。

EndReceive

结束挂起的异步读取。

EndReceiveFrom

结束挂起的、从特定终结点进行异步读取。

EndSend

结束挂起的异步发送。

EndSendTo

结束挂起的、向指定位置进行的异步发送。

GetSocketOption

返回Socket选项的值。

IOControl

为Socket设置低级别操作模式。

Listen

将Socket置于侦听状态。

Poll

确定Socket的状态。

Receive

接收来自连接Socket的数据。

ReceiveFrom

接收数据文报并存储源终结点。

Select

确定一个或多个套接字的状态。

Send

将数据发送到连接的

SendTo

将数据发送到特定终结点。

SetSocketOption

设置Socket选项。

Shutdown

禁用某Socket上的发送和接收。

  其中包含六组异步方法,它们是:

  

  ·BeginAccept和EndAccept

  ·BeginConnect和EndConnect

  ·BeginReceive和EndReceive

  ·BeginReceiveFrom和EndReceiveFrom"

  ·BeginSend和EndSend

  ·BeginSendTo"和"EndSendTo

  其功能分别相当于"Accept"、"Connect"、"Receive"、"ReceiveFrom"、"Send"和"SendTo"方法。

  四.Visual C#实现Ping命令的关键步骤及其解决方法

  根据Ping命令的执行过程,可以把Ping命令分成三个主要的步骤:

  1. 定义ICMP报文。

  2. 客户机发送封装ICMP回显请求报文的IP数据包。

  3. 客户机接收封装ICMP应答报文的IP数据包。

  解决了上述三个步骤,Visual C#实现Ping命令就基本可以完成了。下面是这三个步骤的具体的解决方法。

  1. 定义ICMP报文:

  根据ICMP报文组成结构,定义了一个类--IcmpPacket类。IcmpPacket类通过实例化就能够得到ICMP报文。下面代码是定义IcmpPacket类:


public class IcmpPacket

{

 private Byte _type ;

 // 类型

 private Byte _subCode ;

 // 代码

 private UInt16 _checkSum ;

 // 校验和

 private UInt16 _identifier ;

 // 识别符

 private UInt16 _sequenceNumber ;

 // 序列号

 private Byte [ ] _data ;

 //选项数据

 public IcmpPacket ( Byte type , Byte subCode , UInt16 checkSum , UInt16 identifier , UInt16 sequenceNumber , int dataSize )

 {

  _type = type ;

  _subCode = subCode ;

  _checkSum = checkSum ;

  _identifier = identifier ;

  _sequenceNumber = sequenceNumber ;

  _data=new Byte [ dataSize ] ;

  //在数据中,写入指定的数据大小

  for ( int i = 0 ; i < dataSize ; i++ )

  {

   //由于选项数据在此命令中并不重要,所以你可以改换任何你喜欢的字符

   _data [ i ] = ( byte )'#' ;

  }

 }

 public UInt16 CheckSum

 {

  get

  {

   return _checkSum ;

  }

  set

  {

   _checkSum=value ;

  }

 }

 //初始化ICMP报文

 public int CountByte ( Byte [ ] buffer )

 {

  Byte [ ] b_type = new Byte [ 1 ] { _type } ;

  Byte [ ] b_code = new Byte [ 1 ] { _subCode } ;

  Byte [ ] b_cksum = BitConverter.GetBytes ( _checkSum ) ;

  Byte [ ] b_id = BitConverter.GetBytes ( _identifier ) ;

  Byte [ ] b_seq = BitConverter.GetBytes ( _sequenceNumber ) ;

  int i = 0 ;

  Array.Copy ( b_type , 0 , buffer , i , b_type.Length ) ;

  i+= b_type.Length ;

  Array.Copy ( b_code , 0 , buffer , i , b_code.Length ) ;

  i+= b_code.Length ;

  Array.Copy ( b_cksum , 0 , buffer ,i , b_cksum.Length ) ;

  i+= b_cksum.Length ;

  Array.Copy ( b_id , 0 , buffer , i , b_id.Length ) ;

  i+= b_id.Length ;

  Array.Copy ( b_seq , 0 , buffer , i , b_seq.Length ) ;

  i += b_seq.Length ;

  Array.Copy ( _data , 0 , buffer , i , _data.Length ) ;

  i += _data.Length ;

  return i ;

 }

 //将整个ICMP报文信息和数据转化为Byte数据包

 public static UInt16 SumOfCheck ( UInt16 [ ] buffer )

 {

  int cksum = 0 ;

  for ( int i = 0 ; i < buffer.Length ; i++ )

   cksum += ( int ) buffer [ i ] ;

   cksum = ( cksum >> 16 ) + ( cksum & 0xffff ) ;

   cksum += ( cksum >> 16 ) ;

   return ( UInt16 ) ( ~cksum ) ;

 }

}

下列代码是利用IcmpPacket类来创建ICMP报文:
IcmpPacket packet = new IcmpPacket ( 0 , 0 , 0 , 45 , 0 , 4 ) ;

此代码定义的ICMP报文中的数据段长度为4个字节,所以整个ICMP报文长度为12个字节(即:8+4),而封装此ICMP报文的IP数据包长度就是32个字节(即:8+4+20)。在后面介绍的程序中,从客户端发送的ICMP会显请求报文的数据长度为4个字节,但从服务器介绍到的数据却是32个字节的原因。

  2. 发送封装ICMP回显请求报文的IP数据包:

  发送IP数据包首先要创建一个能够发送封装ICMP回显请求报文的IP数据包Socket实例,然后调用此Socket实例中的"SendTo"方法就可以了。下列代码是创建并初始化一个发送封装ICMP回显请求报文的IP数据包的Socket实例:
Socket socket = new Socket ( AddressFamily.InterNetwork , SocketType.Raw , ProtocolType.Icmp ) ;

  创建初始化Socket实例有三个参数,下面是这些参数的说明:

  第一个参数定义目前网络的寻址方案,目前还是IPV4,所有只有定义为"AddressFamily.InterNetwork"。

  第二个参数定义Socket实例的类型,由于Socket的通讯协议是ICMP,所以选择枚举值"Raw Socket"。

  第三个参数是定义Socket实例有效的协议类型,由于此Socket实例要传送的是ICMP报文,所以选定枚举值为"ProtocolType.Icmp"。

  3.客户机接收封装ICMP应答报文的IP数据包:

  接收服务器端返回的封装ICMP应答报文的IP数据包只需调用Socket实例中的"ReceiveFrom"方法就可以了,

具体可参阅下面的vs2005中程序实现的代码:

using System.Net;
using System.Net.Sockets;
using System;

namespace Ping
{
/// <summary>
/// Class1 的摘要说明。
/// </summary

class Program
{
public class IcmpPacket
{
public Byte Type; //消息类型
public Byte SubCode; //子码类型
public UInt16 CheckSum; //校验和
public UInt16 Identifier; //标识符
public UInt16 SequenceNumber;//序列号
public Byte[] Data; //ICMP数据包内的数据
}

const int SOCKET_ERROR = -1;
const int ICMP_ECHO = 8;

public static void PingHost(string host)
{
//定义保存主机地址的本地变量
IPHostEntry dstHost, srcHost;
int nBytes = 0;
int dwStart = 0, dwStop = 0, elapseTime = 0;
int icmpFailureNum = 0;

//初始化套接字
Socket socket = null;
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

}
catch (SocketException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
return;
}

//阻塞模式在此设置超时时间
socket.ReceiveTimeout = 1000;
try
{
dstHost = Dns.GetHostEntry(host);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
return;
}
IPEndPoint ipepServer = new IPEndPoint(dstHost.AddressList[0], 0);
EndPoint epServer = (ipepServer);
srcHost = Dns.GetHostEntry(Dns.GetHostName());
IPEndPoint ipEndPointFrom = new IPEndPoint(srcHost.AddressList[0], 0);
EndPoint EndPointFrom = (ipEndPointFrom);
int PacketSize = 0;
IcmpPacket packet = new IcmpPacket();

//构造ICMP数据包
packet.Type = ICMP_ECHO;
packet.SubCode = 0;
packet.CheckSum = UInt16.Parse("0");
packet.Identifier = UInt16.Parse("45");
packet.SequenceNumber = UInt16.Parse("0");
int PingData = 32;
packet.Data = new byte[PingData];

//初始化ICMP数据包
for (int i = 0; i < PingData; i++)
{
packet.Data[i] = (byte)'#';
}
PacketSize = PingData + 8;
Byte[] icmp_pkt_buffer = new Byte[PacketSize];
Int32 Index = 0;

//计算ICMP包的大小
Index = Serialize(packet, icmp_pkt_buffer, PacketSize, PingData);

//错误报告
if (Index == -1)
{
Console.WriteLine("Error in making Packet");
return;
}
Double double_length = Convert.ToDouble(Index);
Double dtemp = Math.Ceiling(double_length / 2);
int cksum_buffer_length = Convert.ToInt32(dtemp);
UInt16[] cksum_buffer = new UInt16[cksum_buffer_length];
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;
}

//计算校验和
UInt16 u_cksum = checksum(cksum_buffer, cksum_buffer_length);

//保存校验和
packet.CheckSum = u_cksum;
Byte[] sendbuf = new Byte[PacketSize];

//检查ICMP包的大小
Index = Serialize(packet, sendbuf, PacketSize, PingData);

//错误报告
if (Index == -1)
{
Console.WriteLine("Error in making Packet");
return;
}
for (int i = 0; i < 4; i++)
{
//开始记时
dwStart = System.Environment.TickCount;
//Timer timer = new Timer(new TimerCallback(TimeOut), null, 0, 1000);

//通过套接字发送ICMP数据包
try
{
nBytes = socket.SendTo(sendbuf, PacketSize, SocketFlags.None, epServer);
}
catch (SocketException e)
{
Console.WriteLine("Socket error cannot send Packet");
System.Diagnostics.Debug.WriteLine(e.Message);
}

//初始化缓冲区
Byte[] ReceiveBuffer = new Byte[256];
nBytes = 0;
try
{
//获得字节信息
nBytes = socket.ReceiveFrom(ReceiveBuffer, 256, SocketFlags.None, ref EndPointFrom);

//停止记时
dwStop = System.Environment.TickCount;
elapseTime = dwStop - dwStart;
}
catch (SocketException e)
{
Console.WriteLine("Request timed out!");
icmpFailureNum++;
System.Diagnostics.Debug.WriteLine(e.Message);
}
if (nBytes > 0)
{
//显示相应结果
if (elapseTime == 0)
{
Console.WriteLine("Reply from " + ReceiveBuffer[12] + "." + ReceiveBuffer[13] + "." + ReceiveBuffer[14] + "." + ReceiveBuffer[15]
+ " to " + ReceiveBuffer[16] + "." + ReceiveBuffer[17] + "." + ReceiveBuffer[18] + "." + ReceiveBuffer[19]
+ ": icmp_seq=" + i + " time<1ms bytes=" + packet.Data.Length + " TTL=" + ReceiveBuffer[8].ToString());
}
else
{
Console.WriteLine("Reply from " + ReceiveBuffer[12] + "." + ReceiveBuffer[13] + "." + ReceiveBuffer[14] + "." + ReceiveBuffer[15]
+ " to " + ReceiveBuffer[16] + "." + ReceiveBuffer[17] + "." + ReceiveBuffer[18] + "." + ReceiveBuffer[19]
+ ": icmp_seq=" + i + " time=" + elapseTime + "ms bytes=" + packet.Data.Length + " TTL=" + ReceiveBuffer[8].ToString());
}
}
}
Console.WriteLine("Ping statistics for " + epServer.ToString()+":");
Console.WriteLine("Packets: Sent = 4," + "Received = " + (4 - icmpFailureNum) + ", Lost = " + icmpFailureNum + " (" + (double)((icmpFailureNum) / 4 * 100) + "% loss)");
//关闭套接字
socket.Close();
}

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);
}

public static Int32 Serialize(IcmpPacket packet, Byte[] Buffer,
Int32 PacketSize, Int32 PingData)
{
Int32 cbReturn = 0;
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;
//复制数据
Array.Copy(packet.Data, 0, Buffer, Index, PingData);
Index += PingData;
if (Index != PacketSize)
{
cbReturn = -1;
return cbReturn;
}
cbReturn = Index;
return cbReturn;
}

/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] argv)
{
if (argv.Length == 0)
{
Console.WriteLine("Command format:Ping <hostname> [/r]");
Console.WriteLine("r/ for loop tests.");
}
else if (argv.Length == 1)
{
PingHost(argv[0]);
}
else if (argv.Length == 2)
{ //向目的计算机循环发送ICMP数据包
if (argv[1] == "/r")
{
while (true)
{//调用PingHost方法发送ICMP数据包
PingHost(argv[0]);
}
}
else
{ //向目的计算机发送一次ICMP数据包
PingHost(argv[0]);
}
}
else
{ //错误参数处理
Console.WriteLine("Error in Arguments");
}
}

}
}

本日志转自:http://blog.sina.com.cn/s/blog_4b3419a20100b7vj.html

原创粉丝点击