65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 .NET Framework 2.0 的 Tracert 组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (23投票s)

2006年6月25日

CPOL

7分钟阅读

viewsIcon

144605

downloadIcon

5774

本文基于 .NET Framework 2.0 中提供的 Ping 类实现了一个 tracert 组件。

Screen shot of the tool

引言

Tracert 是 Windows 中的一个命令行实用程序,它显示网络数据包在从源传输到目标主机时遇到的节点的 IP 地址。在 CP 上有一些文章展示了如何实现 tracert 功能。

  1. CTraceRoute v1.0 作者:P. J. Naughter。
  2. 使用原始套接字进行路由跟踪 作者:Babar Qaisrani

上述文章使用了 C++ 代码,而我需要一个适用于 .NET Framework 2.0 的 C# 版本。幸运的是,.NET Framework 2.0 有一个 Ping 组件,本文提供的代码就是利用这个组件来实现 tracert 功能的。

使用 Tracert 组件?

在项目中使用的最简单的方法是将该组件添加到 VS.NET 工具箱中,然后将其拖放到适当的设计器表面。如果您将 tracert 项目添加为另一个项目的引用,那么 Tracert 组件将自动出现在 VS.NET 工具箱中,如下面的屏幕截图所示。同时还展示了组件在设计器表面上放置后的样子。

VS.NET Toolbox

将组件添加到设计器表面后,您需要设置该组件的属性。您需要设置的最重要的属性是 HostNameOrAddress 属性,该属性可以设置为主机名或 IP 地址。

tracert.HostNameOrAddress = "www.codeproject.com"; //Use the host name
tracert.HostNameOrAddress = "208.90.12.1"; //Use the IPAddress

接下来,您需要为该组件公开的事件分配事件处理程序。这是因为该组件是异步工作的。您可以使用两个事件:RouteNodeFoundDone。您可以处理 RouteNodeFound 事件来查找中间节点(当它们被找到时)。

tracert.RouteNodeFound += 
   new EventHandler<RouteNodeFoundEventArgs>(tracert_RouteNodeFound);

在下面的代码片段中,节点 IP 地址在找到时被添加到列表视图中。

private void tracert_RouteNodeFound(object sender, RouteNodeFoundEventArgs e)
{
    ListViewItem item = routeList.Items.Add(e.Node.Address.ToString());
    .
    .
}

最后,您需要处理 Done 事件来查找跟踪过程是否已完成。

tracert.Done += new System.EventHandler(this.tracert_Done);

private void tracert_Done(object sender, EventArgs e)
{
   //Access all the nodes using tracert.Nodes proerty
   .
   .
   //IP Address of the ith node
   IPAddress address = tracert.Nodes[i].Address;   
}

最后,可以通过调用 Trace 方法来启动实际的跟踪。

tracert.Trace();

这是使用该组件最简单、最常见的方式。您可以根据下一节中的描述设置其他属性来进一步控制组件的行为。

Tracert 组件参考

该组件具有如下公共属性。

属性名称 类型 描述
MaxHops int 到达目标所需的最大网络跃数。即使未到达目标,tracert 进程也会在达到最大跃数后中止。默认值为 30。
节点 Tracert 节点数组 返回路径中的所有节点。
HostNameOrAddress 字符串 主机名,例如 www.codeproject.com/,或 IP 地址字符串,例如 209.12.1.99。
Timeout int 这是在移至路径中的下一个节点之前,等待特定节点响应的时间(以毫秒为单位)。默认值为 5000。
IsDone bool 如果为 true,则表示已到达目标,或已达到最大跃数。

此控件的方法如下:

方法名称 返回值 描述
Trace void 此方法启动实际的路由跟踪。调用此方法之前,必须设置 HostNameOrAddress 属性。此方法是异步工作的。

事件如下:

事件名称 描述
完成 当路由跟踪完成时,会触发此事件。这可能发生在到达目标主机时,或者达到最大跃数时。
RouteNodeFound 当在路径中找到中间节点时,会触发此事件。提供给事件处理程序的 RouteNodeFoundEventArgs 参数包含有关该节点的其他信息。

RouteNodeFoundEventArgs 参考

类型为 RouteNodeFoundEventArgs 的参数会传递给 RouteNodeFound 事件的事件处理程序。该类的属性如下:

属性名称 类型 描述
节点 TracertNode 表示在迭代中找到的节点。

TracertNode 参考

TracertNode 类用于封装路径中的一个节点;它具有以下属性:

属性名称 类型 描述
地址 IPAddress 表示节点的 IP 地址。如果发生错误或超时,地址将等于 IPAddress.Any (0.0.0.0)。
RoundTripTime int 从将数据包发送到节点到发送方收到确认之间的时间间隔(以毫秒为单位)。
状态 IPStatus 发送到节点的 ping 请求的状态。可以是 IPStatus.SuccessIPStatus.Timeout

Tracert 如何工作?

当您向目标发送 Ping 请求时,可以选择指定 TTL(生存时间) 值。当网络数据包从一个中间主机传输到另一个主机时,其 TTL 值会减一。当 TTL 值变为零时,发送方会收到一个错误确认 - TTL 已超出。发送方可以从收到该确认的主机中找出其 IP 地址。因此,如果您将 TTL 值设置为 1 并向目标发送数据包,则数据包将由路径中的第一个节点返回。然后,如果您将 TTL 值设置为 2 并再次发送数据包,则它将由路径中的第二个主机返回,依此类推。您可以继续此递增 TTL 值的过程,直到到达目标或达到最大跃数。下图显示了一个示例网络路径和网络数据包的行为。

Sample Newtrok Path

图中的箭头显示了数据包的方向。粗线显示了数据包遍历的路径。虚线表示如果 TTL 没有过期,网络数据包可能会遍历该路径。现在我们已经对 Tracert 的工作原理有了简要的了解,让我们看看如何在代码中实现它。

在代码中实现 Tracert

我们将使用 .NET Framework 的 Ping 类来实现 tracert 功能。让我们直接从代码片段开始。

_ping = new Ping(); 
_ping.PingCompleted += new PingCompletedEventHandler(OnPingCompleted); 
_options = new PingOptions(1, true); 
_ping.SendAsync(_destination, _timeout, Tracert.Buffer, _options, null);

Ping 类可以使用 Send 方法同步发送 ping 请求,或者使用 SendAsync 方法异步发送。当使用 SendAsync 方法时,会触发 PingCompleted 事件来通知请求的完成。SendAsync 方法有许多不同的重载。在上述代码使用的重载中,参数如下:

参数 描述
_destination 这是最终目标的 IP 地址。
_timeout 等待网络请求完成的时间(以毫秒为单位)。
Tracert.Buffer 要作为请求发送的字节缓冲区。这取自一个静态变量。在我们的示例中,该缓冲区是一个 32 字节数组,填充了字符 A 的 ASCII 码。
_options

一个 PingOptions 类型的对象,它指示 TTL 值以及一个布尔值,该值指示缓冲区是否应被分片。对于初始调用,我们选择 TTL 值为 1。我们还选择不分片缓冲区(在我的测试中,我没有看到此值对结果有任何影响)。

null(最后一个参数)

最后一个参数是一个用户定义的对象,该对象在 PingCompleted 事件处理程序中提供。我们不使用它,所以传递一个 null 引用。

一旦请求完成(无论成功还是不成功),就会调用 PingCompleted 事件的处理程序。在本例中,它是 OnPingCompleted 方法。

void OnPingCompleted(object sender, PingCompletedEventArgs e)
{
  ProcessNode(e.Reply.Address, e.Reply.Status);

  if (!this.IsDone)
  {
    lock (this)
    {
        //If the object is disposed the _ping 
        //member variable is set to null
        if (_ping == null)
        {
            ProcessNode(_destination, IPStatus.Unknown);
        }
        else
        {
            _options.Ttl += 1;
            ping.SendAsync(_destination, _timeout, 
                           Tracert.Buffer, _options, null);
        }
    }
  }
}

OnPingCompleted 函数带有两个参数。第一个参数是 sender 对象,它将是调用 SendAsync 方法的原始 Ping 对象。参数 ePingCompletedEventArgs 类型,提供了有关事件的更多信息。PingCompletedEventArgs 的重要成员是 Reply 成员。此成员提供了数据包超时的节点 IP 地址和状态值。当数据包未成功到达目标时,状态值设置为 IPStatus.TtlExpired;否则,它将设置为 IPStatus.Success。我们在 ProcessNode 方法中处理中间节点。

protected void ProcessNode(IPAddress address, 
                           IPStatus status)
{
    long roundTripTime = 0;

    if (status == IPStatus.TtlExpired || 
        status == IPStatus.Success)
    {
        Ping pingIntermediate = new Ping(); 
        
        try
        {
            //Compute roundtrip time to the address by pinging it
            PingReply reply = pingIntermediate.Send(
                                                address,
                                                _timeout);
                                         
            roundTripTime = reply.RoundtripTime;
            status = reply.Status;
        }
        catch (PingException e)
        {
            //Do nothing
            System.Diagnostics.Trace.WriteLine(e);
        }
        finally
        {
            pingIntermediate.Dispose();
        }
    }

    TracertNode node = new TracertNode(address, roundTripTime, 
                                           status);

    lock (_nodes)
    {
        _nodes.Add(node);
    }

    if (RouteNodeFound != null)
        RouteNodeFound(this, 
                new RouteNodeFoundEventArgs(node, this.IsDone));
    
    this.IsDone = address.Equals(_destination);
    
    lock (_nodes)
    {
        if (!this.IsDone && _nodes.Count >= _maxHops - 1)
            ProcessNode(_destination, IPStatus.Success);
    }
}

ProcessNode 方法中,我们再次 ping 该节点。这是因为我们想获取到目标的往返时间。PingReply 类确实返回往返时间,但仅当数据包成功到达目标时才会返回。一旦获得往返时间,我们就会触发 RouteNodeFound 事件。接下来,我们将目标节点添加到节点列表中。最后,我们检查是否已到达最终目标。如果为真,我们将 IsDone 属性设置为 true。简而言之,这就是该组件的工作方式。

历史

  • 2006 年 6 月 25 日:初始提交。
© . All rights reserved.