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",说明此主机在线,具体如图01:
![]() 图01:Ping通主机时的运行界面 |
![]() 图02:Ping不通主机时的运行界面 |
![]() 图03:IP数据包的组成结构图 |
![]() 图04:ICMP报文组成结构图 |
属性 | 说明 |
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上的发送和接收。 |
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 packet = new IcmpPacket ( 0 , 0 , 0 , 45 , 0 , 4 ) ; |
Socket socket = new Socket ( AddressFamily.InterNetwork , SocketType.Raw , ProtocolType.Icmp ) ; |
六.Visual C#实现Ping命令的实现步骤:
下面是Visual C#实现Ping命令的具体实现步骤:
1. 启动Visual Studio .Net。
2. 选择菜单【文件】|【新建】|【项目】后,弹出【新建项目】对话框。
3. 将【项目类型】设置为【Visual C#项目】。
4. 将【模板】设置为【Windows应用程序】。
5. 在【名称】文本框中输入【Visual C#实现Ping命令】。
6. 在【位置】的文本框中输入【E:/VS.NET项目】,然后单击【确定】按钮,具体如图05所示:
图05:【Visual C#实现Ping命令】项目的【新建项目】对话框
7. 【解决方案资源管理器】窗口中,双击Form1.cs文件,进入Form1.cs文件的编辑界面。
8. 在Form1.cs文件的开头的导入命名空间的代码区,添加下列代码,下列代码是导入下面程序中使用到的类所在的命名空间:
using System.Net ; using System.Net.Sockets ; |
private void InitializeComponent ( ) { this.textBox1 = new System.Windows.Forms.TextBox ( ) ; this.label1 = new System.Windows.Forms.Label ( ) ; this.listBox1 = new System.Windows.Forms.ListBox ( ) ; this.button1 = new System.Windows.Forms.Button ( ) ; this.SuspendLayout ( ) ; this.textBox1.Location = new System.Drawing.Point ( 116 , 14 ) ; this.textBox1.Name = "textBox1" ; this.textBox1.Size = new System.Drawing.Size ( 148 , 21 ) ; this.textBox1.TabIndex = 0 ; this.textBox1.Text = "" ; this.textBox1.TextChanged += new System.EventHandler ( this.textBox1_TextChanged ) ; this.label1.Location = new System.Drawing.Point ( 12 , 14 ) ; this.label1.Name = "label1" ; this.label1.TabIndex = 1 ; this.label1.Text = "请输入主机名:" ; this.listBox1.BackColor = System.Drawing.SystemColors.WindowText ; this.listBox1.ForeColor = System.Drawing.SystemColors.Window ; this.listBox1.ItemHeight = 12 ; this.listBox1.Location = new System.Drawing.Point ( 6 , 42 ) ; this.listBox1.Name = "listBox1" ; this.listBox1.Size = new System.Drawing.Size ( 400 , 280 ) ; this.listBox1.TabIndex = 2 ; this.button1.Location = new System.Drawing.Point ( 274 , 12 ) ; this.button1.Name = "button1" ; this.button1.TabIndex = 3 ; this.button1.Text = "Ping" ; this.button1.Click += new System.EventHandler ( this.button1_Click ) ; this.AutoScaleBaseSize = new System.Drawing.Size ( 6 , 14 ) ; this.ClientSize = new System.Drawing.Size ( 410 , 333 ) ; this.Controls.AddRange ( new System.Windows.Forms.Control[ ] { this.button1 , this.listBox1 , this.label1 , this.textBox1 } ) ; this.MaximizeBox = false ; this.Name = "Form1" ; this.Text = "Visual C#实现Ping" ; this.ResumeLayout ( false ) ; } |
![]() 图06:【Visual C#实现Ping命令】项目的设计界面 |
Private Void Button1_click ( Object Sender , System.eventargs E ) { Listbox1.items.clear ( ) ; String Hostclient = Textbox1.text ; Int K ; For ( K = 0 ; K < 3 ; K++ ) { Socket Socket = New Socket ( Addressfamily.internetwork , Sockettype.raw , Protocoltype.icmp ) ; Iphostentry Hostinfo ; Try { //解析主机ip入口 Hostinfo = Dns.gethostbyname ( Hostclient ) ; } Catch ( Exception ) { //解析主机名错误。 Listbox1.items.add ( "没有发现此主机!" ) ; Return ; } // 取服务器端主机的30号端口 Endpoint Hostpoint = ( Endpoint ) New Ipendpoint ( Hostinfo.addresslist[ 0 ] , 30 ) ; Iphostentry Clientinfo ; Clientinfo = Dns.gethostbyname ( Hostclient ) ; // 取客户机端主机的30端口 Endpoint Clientpoint = ( Endpoint ) New Ipendpoint ( Clientinfo.addresslist[ 0 ] , 30 ) ; //设置icmp报文 Int Datasize = 4 ; // Icmp数据包大小 ; Int Packetsize = Datasize + 8 ;//总报文长度 Const Int Icmp_echo = 8 ; Icmppacket Packet = New Icmppacket ( Icmp_echo , 0 , 0 , 45 , 0 , Datasize ) ; Byte [ ] Buffer = New Byte [ Packetsize ] ; Int Index = Packet.countbyte ( Buffer ) ; //报文出错 If ( Index != Packetsize ) { Listbox1.items.add ( "报文出现问题!" ) ; Return ; } Int Cksum_buffer_length = ( Int ) Math.ceiling ( ( ( Double )index )/ 2 ) ; Uint16 [ ] Cksum_buffer = New Uint16 [ Cksum_buffer_length ] ; Int Icmp_header_buffer_index = 0 ; For ( Int I = 0 ; I < Cksum_buffer_length ; I++ ) { //将两个byte转化为一个uint16 Cksum_buffer[ I ] = Bitconverter.touint16 ( Buffer , Icmp_header_buffer_index ) ; Icmp_header_buffer_index += 2 ; } //将校验和保存至报文里 Packet.checksum = Icmppacket.sumofcheck ( Cksum_buffer ) ; // 保存校验和后,再次将报文转化为数据包 Byte [ ] Senddata = New Byte [ Packetsize ] ; Index = Packet.countbyte ( Senddata ) ; //报文出错 If ( Index != Packetsize ) { Listbox1.items.add ( "报文出现问题!" ) ; Return ; } Int Nbytes = 0 ; //系统计时开始 Int Starttime = Environment.tickcount ; //发送数据包 If ( ( Nbytes = Socket.sendto ( Senddata , Packetsize , Socketflags.none , Hostpoint ) ) == -1 ) { Listbox1.items.add ( "无法传送报文!" ) ; } Byte [ ] Receivedata = New Byte[ 256 ] ; //接收数据 Nbytes = 0 ; Int Timeout = 0 ; Int Timeconsume = 0 ; While ( True ) { Nbytes = Socket.receivefrom ( Receivedata , 256 , Socketflags.none , Ref Clientpoint ) ; If ( Nbytes == -1 ) { Listbox1.items.add ( "主机没有响应!" ) ; Break ; } Else If ( Nbytes > 0 ) { Timeconsume = System.environment.tickcount - Starttime ; //得到发送报文到接收报文之间花费的时间 Listbox1.items.add ( "reply From " + Hostinfo.addresslist[ 0 ].tostring ( ) + " In " + Timeconsume + "ms :bytes Received " + Nbytes ) ; Break ; } Timeconsume = Environment.tickcount - Starttime ; If ( Timeout > 1000 ) { Listbox1.items.add ( "time Out" ) ; Break ; } } //关闭套接字 Socket.close ( ) ; } } |
{ 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 ) ; } } |
![]() 图06:【Visual C#实现Ping命令】的运行界面 |