核心思想是调用 WinAPI 中的 GetExtendedTcpTable 方法来获取所有活动的 TCP 连接的信息,包括进程ID等等,主要实现如下:
TcpConnectionTableHelper.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace TcpConnectionMonitor
{
public class TcpConnectionTableHelper
{
[DllImport("Ws2_32.dll")]
static extern ushort ntohs(ushort netshort);
[DllImport("iphlpapi.dll", SetLastError = true)]
static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, TCP_TABLE_TYPE tblClass, int reserved);
[StructLayout(LayoutKind.Sequential)]
public struct MIB_TCPROW_OWNER_PID
{
public uint state;
public uint localAddr;
public byte localPort1;
public byte localPort2;
public byte localPort3;
public byte localPort4;
public uint remoteAddr;
public byte remotePort1;
public byte remotePort2;
public byte remotePort3;
public byte remotePort4;
public int owningPid;
public ushort LocalPort
{
get
{
return BitConverter.ToUInt16(new byte[2] { localPort2, localPort1 }, 0);
}
}
public ushort RemotePort
{
get
{
return BitConverter.ToUInt16(new byte[2] { remotePort2, remotePort1 }, 0);
}
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MIB_TCPTABLE_OWNER_PID
{
public uint dwNumEntries;
MIB_TCPROW_OWNER_PID table;
}
public static string GetIpAddress(long ipAddrs)
{
try
{
System.Net.IPAddress ipAddress = new System.Net.IPAddress(ipAddrs);
return ipAddress.ToString();
}
catch { return ipAddrs.ToString(); }
}
public static ushort GetTcpPort(int tcpPort)
{
return ntohs((ushort)tcpPort);
}
public static MIB_TCPROW_OWNER_PID[] GetAllTcpConnections()
{
MIB_TCPROW_OWNER_PID[] tcpConnectionRows;
int AF_INET = 2;
int buffSize = 0;
uint ret = GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, AF_INET, TCP_TABLE_TYPE.TCP_TABLE_OWNER_PID_ALL, 0);
if (ret != 0 && ret != 122)
{
throw new Exception("Error occurred when trying to query tcp table, return code: " + ret);
}
IntPtr buffTable = Marshal.AllocHGlobal(buffSize);
try
{
ret = GetExtendedTcpTable(buffTable, ref buffSize, true, AF_INET, TCP_TABLE_TYPE.TCP_TABLE_OWNER_PID_ALL, 0);
if (ret != 0)
{
throw new Exception("Error occurred when trying to query tcp table, return code: " + ret);
}
MIB_TCPTABLE_OWNER_PID table = (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));
IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(table.dwNumEntries));
tcpConnectionRows = new MIB_TCPROW_OWNER_PID[table.dwNumEntries];
for (int i = 0; i < table.dwNumEntries; i++)
{
MIB_TCPROW_OWNER_PID tcpRow = (MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));
tcpConnectionRows[i] = tcpRow;
rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow));
}
}
finally
{
Marshal.FreeHGlobal(buffTable);
}
return tcpConnectionRows;
}
}
}
public enum TCP_TABLE_TYPE : int
{
TCP_TABLE_BASIC_LISTENER,
TCP_TABLE_BASIC_CONNECTIONS,
TCP_TABLE_BASIC_ALL,
TCP_TABLE_OWNER_PID_LISTENER,
TCP_TABLE_OWNER_PID_CONNECTIONS,
TCP_TABLE_OWNER_PID_ALL,
TCP_TABLE_OWNER_MODULE_LISTENER,
TCP_TABLE_OWNER_MODULE_CONNECTIONS,
TCP_TABLE_OWNER_MODULE_ALL
}
public enum TCP_CONNECTION_STATE : int
{
CLOSED = 1,
LISTENING,
SYN_SENT,
SYN_RCVD,
ESTABLISHED,
FIN_WAIT_1,
FIN_WAIT_2,
CLOSE_WAIT,
CLOSING,
LAST_ACK,
TIME_WAIT,
delete_TCP
};
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TcpConnectionMonitor
{
class Program
{
static void Main(string[] args)
{
MonitorTcpConnections();
}
static void MonitorTcpConnections()
{
Console.WriteLine("Proto Local Address Foreign Address State PID");
List<String> rows = new List<string>();
while (true)
{
int windowTop = Console.WindowTop;
TcpConnectionTableHelper.MIB_TCPROW_OWNER_PID[] tcpProgressInfoTable = TcpConnectionTableHelper.GetAllTcpConnections();
int tableRowCount = tcpProgressInfoTable.Length;
for (int i = 0; i < tableRowCount; i++)
{
TcpConnectionTableHelper.MIB_TCPROW_OWNER_PID row = tcpProgressInfoTable[i];
string source = string.Format("{0}:{1}", TcpConnectionTableHelper.GetIpAddress(row.localAddr), row.LocalPort);
string dest = string.Format("{0}:{1}", TcpConnectionTableHelper.GetIpAddress(row.remoteAddr), row.RemotePort);
string outputRow = string.Format("{0, -7}{1, -23}{2, -23}{3, -16}{4}", "TCP", source, dest, (TCP_CONNECTION_STATE)row.state, row.owningPid);
if (rows.Count < i + 1)
{
Console.SetCursorPosition(0, i + 1);
Console.WriteLine("{0, -80}", outputRow);
rows.Add(outputRow);
}
else if (rows[i] != outputRow)
{
rows[i] = outputRow;
Console.SetCursorPosition(0, i + 1);
Console.WriteLine("{0, -80}", outputRow);
}
}
if (rows.Count > tableRowCount)
{
int linesToBeCleared = rows.Count - tableRowCount;
rows.RemoveRange(tableRowCount, linesToBeCleared);
for (int i = 0; i < linesToBeCleared + 1; i++)
{
Console.WriteLine("{0, -80}", " ");
}
}
Console.SetWindowPosition(0, windowTop);
Thread.Sleep(100);
}
}
}
}
实现的效果是每 100ms 获取一次活跃 TCP 连接的状态,也就是说每秒大概会刷新10次,为了避免由于刷新频率过快导致闪烁的问题,通过调用 Console.SetCursorPosition 来对变化的数据进行部分刷新,同时为了避免滚动条存在时,Console 指针引起滚动条自动滚动到最后一行,使用 SetWindowPosition 来固定滚动条的位置。
输出结果举例:


该文章在 2022/11/17 10:05:16 编辑过