使用 GetExtendedTcpTable 函数获取活动的 TCP/UDP 连接
本文介绍了如何使用 IP Helper API 中的一些 TCP/UDP 函数来获取活动的连接以及连接所关联的进程。
引言
此库的主要目的是能够监视您 PC 上活动的 UDP/TCP 连接。它是 Axel Charpentier 发布的一个库的扩展版本,“获取活动 TCP/UDP 连接”。
Axel Charpentier 使用了未公开的(已弃用)函数 AllocateAndGetTcpExTableFromStack
和 AllocateAndGetUdpExTableFromStack
来获取活动的 TCP/UDP 连接并获取与连接关联的进程。在查阅 MSDN 后,我发现了两个文档记录完善的函数:GetExtendedTcpTable
和 GetExtendedUdpTable
。
我为 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);
}
}
在库中,我使用了两个额外的类:TCPUDPConnection
和 TCPUDPConnections : IEnumerable<TCPUDPConnection>
。第一个用于存储 TCP/UDP 表的一行,第二个用于存储 TCP/UDP 连接列表。请记住,GetExtendedTcpTable
和 GetExtendedUdpTable
函数仅在 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);
}
}
}
此方法旨在删除所有死连接。该方法扫描列表中的所有连接,并比较连接创建时间和最后一次总刷新时间。所有死连接都会被移除。不幸的是,我现在不知道如何优化这个过程。
TCPUDPConnection
s 中最有趣的方法是 IndexOf
和 Add
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;
}
GetTcpConnections
和 GetUdpConnection
s 方法返回一个排序的 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;
}
Add
与 IndexOf
一起使用,以决定如何操作:在指定位置 **插入** 以满足排序顺序,将项 **添加到** 列表末尾,或 **更改** 连接的属性
/// <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);
}
}
}
因此,使用 GetExtendedTcpTable
和 GetExtendedUdpTable
,我们记录了一种获取活动的 TCP/UDP 连接及其相应 ProcessID 的方法。
如何使用库
我写了一个小的演示程序来测试这个库。它只显示 GetExtendedTcpTable
和 GetExtendedUdpTable
函数的结果(上面列表视图)以及 AllocateAndGetTcpExTableFromStack
和 AllocateAndGetUdpExTableFromStack
函数的结果(下面列表视图)。
问题
存在两个主要问题:内存和 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();
}
就是这样,希望对大家有帮助。