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

使用 GetExtendedTcpTable 函数获取活动的 TCP/UDP 连接

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (10投票s)

2006年6月12日

CPOL

3分钟阅读

viewsIcon

149379

downloadIcon

10265

本文介绍了如何使用 IP Helper API 中的一些 TCP/UDP 函数来获取活动的连接以及连接所关联的进程。

Sample Image - iphlpapi2.gif

引言

此库的主要目的是能够监视您 PC 上活动的 UDP/TCP 连接。它是 Axel Charpentier 发布的一个库的扩展版本,“获取活动 TCP/UDP 连接”。

Axel Charpentier 使用了未公开的(已弃用)函数 AllocateAndGetTcpExTableFromStackAllocateAndGetUdpExTableFromStack 来获取活动的 TCP/UDP 连接并获取与连接关联的进程。在查阅 MSDN 后,我发现了两个文档记录完善的函数:GetExtendedTcpTableGetExtendedUdpTable

我为 IpHelperApi.dll 创建了一个包装器,并实现了这两个函数,它们可以获取 UDP 和 TCP 连接的表,并获取与连接关联的 processID

包装 IpHlpAPI.dll

该库名为 IPHelper,它只是 IPHlpAPI32.cs 文件中 IpHelperAPI.dll 的一个包装器,使用了 .NET 框架的 P/Invoke 机制。其中包含了 IPHlpApi.dll 中所有函数和结构体的声明;它使用了 System.Runtime.InteropServices 命名空间中的标准属性。

[DllImport("iphlpapi.dll", SetLastError=true)]

public static extern int GetExtendedTcpTable(byte[] pTcpTable, 
       out int dwOutBufLen, bool sort, int ipVersion, 
       TCP_TABLE_CLASS tblClass, int reserved);
[DllImport("iphlpapi.dll", SetLastError=true)]

public static extern int GetExtendedUdpTable(byte[] pUdpTable, 
       out int dwOutBufLen, bool sort, int ipVersion, 
       UDP_TABLE_CLASS tblClass, int reserved);

IpHelperApi 函数 GetExtendedTcpTable 和 GetExtendedUdpTable

为了从 GetExtendedTcpTable 获取信息,我使用了一段简单的代码

public void GetTcpConnections()
{
  int AF_INET = 2; // IP_v4
  int res = IPHlpAPI32Wrapper.GetExtendedTcpTable(buffer, out buffSize, 
            true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
  if (res != Utils.NO_ERROR) //If there is no enouth memory to execute function
  {
    buffer = new byte;
    res = IPHlpAPI32Wrapper.GetExtendedTcpTable(buffer, out buffSize, 
          true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
    if (res != Utils.NO_ERROR)
    {
      return;
    }
  }
  int nOffset = 0;
  int NumEntries = Convert.ToInt32(buffer[nOffset]);
  nOffset += 4;
  for (int i = 0; i < NumEntries; i++)
  {
    TCPUDPConnection row = new TCPUDPConnection();
    // state
    int st = Convert.ToInt32(buffer[nOffset]);
    // state by ID
    row.iState = st;
    nOffset += 4;
    row.Protocol = Protocol.TCP;
    row.Local = Utils.BufferToIPEndPoint(buffer, ref nOffset, false);
    row.Remote = Utils.BufferToIPEndPoint(buffer, ref nOffset, true);
    row.PID = Utils.BufferToInt(buffer, ref nOffset);
    this.Add(row);
  }
}

以及一个类似的用于 GetExtendedUdpTable 的代码

public void GetUdpConnections()
{
  int AF_INET = 2; // IP_v4
  int res = IPHlpAPI32Wrapper.GetExtendedUdpTable(buffer, out buffSize, 
            true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
  if (res != Utils.NO_ERROR)
  {
    buffer = new byte;
    res = IPHlpAPI32Wrapper.GetExtendedUdpTable(buffer, 
          out buffSize, true, AF_INET, 
          UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
    if (res != Utils.NO_ERROR)
    {
      return;
    }
  }
  int nOffset = 0;
  int NumEntries = Convert.ToInt32(buffer[nOffset]);
  nOffset += 4;
  for (int i = 0; i < NumEntries; i++)
  {
    TCPUDPConnection row = new TCPUDPConnection();
    row.Protocol = Protocol.UDP;
    row.Local = Utils.BufferToIPEndPoint(buffer, ref nOffset, false);
    row.PID = Utils.BufferToInt(buffer, ref nOffset);
    this.Add(row);
  }
}

在库中,我使用了两个额外的类:TCPUDPConnectionTCPUDPConnections : IEnumerable<TCPUDPConnection>。第一个用于存储 TCP/UDP 表的一行,第二个用于存储 TCP/UDP 连接列表。请记住,GetExtendedTcpTableGetExtendedUdpTable 函数仅在 WinXP SP2 或 Win2003 SP1 上可用。

TCPUDPConnections

不幸的是,我找不到任何方法可以立即获取有关新进程和端口的信息,因此我使用 System.Timers.Timer 类定期(每秒)刷新 TCP/UDP 连接列表。

/// <summary>
/// Store information concerning TCP/UDP connections
/// </summary>
public class TCPUDPConnections : IEnumerable<TCPUDPConnection> 
{
    private List<TCPUDPConnection> _list;
    System.Timers.Timer _timer = null;
    private int _DeadConnsMultiplier = 10; //Collect dead connections each 5 sec.
    private int _TimerCounter = -1;
    public TCPUDPConnections()
    {
        _LocalHostName = Utils.GetLocalHostName();
        _list = new List<TCPUDPConnection>();
        _timer = new System.Timers.Timer();
        _timer.Interval = 1000; // Refresh list every 1 sec.
        _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
        _timer.Start();
    }
...
}

类的构造函数非常简单。首先,我创建了一个内部的 `_list` 变量,用于存储 TCPUDPConnection 的通用集合。在构造函数中,我还初始化了一个计时器,该计时器会定期调用 Elapsed 事件来刷新 TCP/UDP 集合列表。

void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    this.Refresh();
}

Refresh 方法相当简单

public void Refresh()
{
    lock (this)
    {
        this._LastRefreshDateTime = DateTime.Now;
        this.GetTcpConnections();
        this.GetUdpConnections();
        _TimerCounter++;
        if (_DeadConnsMultiplier == _TimerCounter)
        {
            this.CheckForClosedConnections();
            _TimerCounter = -1;
        }
    }
}

我在运行 GetTcpConnections()GetUdpConnections() 之前,将当前的 DateTime 存储在一个变量 this._LastRefreshDateTime 中,以便将来确定死连接。然后,调用 GetTcpConnections()GetUdpConnections() 来刷新活动的 TCP/UDP 连接列表。此外,在某些 _timer 周期中,还会调用 this.CheckForClosedConnections() 方法。

/// <summary>
/// Method detect and remove from list all dead connections.
/// </summary>
public void CheckForClosedConnections()
{
    int interval = (int)_timer.Interval * this._DeadConnsMultiplier;
    //Remove item from the end of the list
    for (int index = _list.Count - 1; index >= 0; index--)
    {
        TCPUDPConnection conn = this[index];
        TimeSpan diff = (this._LastRefreshDateTime - conn.WasActiveAt);
        int interval1 = Math.Abs((int)diff.TotalMilliseconds);
        if (interval1 > interval)
        {
            this.Remove(index);
        }
    }
}

此方法旨在删除所有死连接。该方法扫描列表中的所有连接,并比较连接创建时间和最后一次总刷新时间。所有死连接都会被移除。不幸的是,我现在不知道如何优化这个过程。

TCPUDPConnections 中最有趣的方法是 IndexOfAdd

public TCPUDPConnection IndexOf(TCPUDPConnection item, out int Pos)
{
    int index = -1;
    foreach (TCPUDPConnection conn in _list)
    {
        index++;
        int i = _connComp.CompareConnections(item, conn);
        if (i == 0)
        {
            Pos = index;
            return conn;
        }
        if (i > 0) // If current an item more then conn,
                      // try to compare with next one until finding equal or less.
        {
            continue; //Skip
        }
        if (i < 0) // If there is an item in list with row less
                      // then current, insert current before this one.
        {
            Pos = index;
            return null;
        }
    }
    Pos = -1;
    return null;
}

GetTcpConnectionsGetUdpConnections 方法返回一个排序的 TCPUDPConnection 列表。它们按本地地址、本地端口、远程地址,然后是远程端口排序。因此,为了尽量减少查找现有项的循环次数,或检测项是否不存在于列表中,我使用了关于排序顺序的信息。为此,我使用了这个方法

public virtual int CompareConnections(TCPUDPConnection first, TCPUDPConnection second)
{
    int i;
    i = Utils.CompareIPEndPoints(first.Local, second.Local);
    if (i != 0)
        return i;
    if (first.Protocol == Protocol.TCP &&
        second.Protocol == Protocol.TCP)
    {
        i = Utils.CompareIPEndPoints(first.Remote, second.Remote);
        if (i != 0)
            return i;
    }
    i = first.PID - second.PID;
    if (i != 0)
        return i;
    if (first.Protocol == second.Protocol)
        return 0;
    if (first.Protocol == Protocol.TCP)
        return -1;
    else
        return 1;
}

AddIndexOf 一起使用,以决定如何操作:在指定位置 **插入** 以满足排序顺序,将项 **添加到** 列表末尾,或 **更改** 连接的属性

/// <summary>
/// Add new <seealso cref="TCPUDPConnection"/> connection.
/// </summary>
/// <param name="item"></param>
public void Add(TCPUDPConnection item)
{
    int Pos = 0;
    TCPUDPConnection conn = IndexOf(item, out Pos);
    if (conn == null)
    {
        item.WasActiveAt = DateTime.Now;
        if (Pos > -1)
        {
            this.Insert(Pos, item);
        }
        else
        {
            _list.Add(item);
            ItemAddedEventHandler(item);
        }
    }
    else
    {
        _list[Pos].WasActiveAt = DateTime.Now;
        if (conn.iState != item.iState ||
        conn.PID != item.PID)
        {    
            conn.iSta    te = item.iState;
            conn.PID = item.PID;
            ItemChangedEventHandler(conn, Pos);
        }
    }
}

因此,使用 GetExtendedTcpTableGetExtendedUdpTable,我们记录了一种获取活动的 TCP/UDP 连接及其相应 ProcessID 的方法。

如何使用库

我写了一个小的演示程序来测试这个库。它只显示 GetExtendedTcpTableGetExtendedUdpTable 函数的结果(上面列表视图)以及 AllocateAndGetTcpExTableFromStackAllocateAndGetUdpExTableFromStack 函数的结果(下面列表视图)。

问题

存在两个主要问题:内存和 CPU 消耗。对于第一个问题,我决定插入一小段代码

public Form1()
{
    try
    {
        Process loProcess = Process.GetCurrentProcess();
        loProcess.MaxWorkingSet = loProcess.MaxWorkingSet;
        loProcess.Dispose();
    }
    catch { }
    System.Timers.Timer ShrinkTimer = new System.Timers.Timer();
    ShrinkTimer.Interval = 60000;
    ShrinkTimer.Elapsed += new System.Timers.ElapsedEventHandler(ShrinkTimer_Elapsed);
    ShrinkTimer.Start();
}
 
void ShrinkTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    try
    {
        Process loProcess = Process.GetCurrentProcess();
        loProcess.MaxWorkingSet = loProcess.MaxWorkingSet;
        loProcess.Dispose();
    }
    catch { }
}

该方法不够准确,但我不知道其他更好的方法。经过硬编码优化后,我将最大 CPU 消耗从 22% 降低到了 2%。可能有人知道如何进一步最小化?

餐后甜点

至于餐后甜点,提供一些有用的函数来获取 TCP/UDP 连接,但不包含 ProcessID

public TcpConnectionInformation[] GetTcpConnectionsNative()
{
  IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties();
  return properties.GetActiveTcpConnections();
}
public IPEndPoint[] GetUdpListeners()
{
  return IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners();
}
public IPEndPoint[] GetTcpListeners()
{
  return IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners();
}

就是这样,希望对大家有帮助。

参考文献

  1. Axel Charpentier,“获取活动 TCP/UDP 连接”。
© . All rights reserved.