最近老板接了一个中船重工的项目,需要做一个有关海军软件系统的组件评估项目,项目中有一个子项目需要获取特定进程的各种系统参数,项目使用.NET平台。在获取特定进程各种系统参数时,其它诸如进程ID,进程名,进程私有工作集,进程工作集,进程IO吞吐量,进程CPU占用率等都可以直接使用.NET中的相关API直接获取,例如使用PerformanceCounter对象可以获取进程私有工作集、进程工作集、进程IO吞吐量等,但是对于特定进程的网络上行流量和下行流量却没有办法直接使用.NET中API获取,网上也找了很多资料,了解到只能通过对特定进程使用的端口号进行抓包来获取进程网络流量,具体实现时自己也想过使用C#调用winpcap库,但由于实现比较复杂,所以就放弃了使用该方法。后来在网上找到了一个国外使用C#实现的winpcap库SharpPcap库,于是使用SharpPcap库实现了获取特定进程网络流量功能。准备工作是需要将SharpPcap库下载到计算机,然后再项目中引用PacketDotNet.dll和SharpPcap.dll,然后添加引用:
using SharpPcap; using PacketDotNet;
具体实现比较复杂,以下介绍一下实现中的核心部分:
首先定义ProcessPerformanceInfo类,用来记录进程相关信息,ProcessPerformanceInfo定义如下
//记录特定进程性能信息的类 public class ProcessPerformanceInfo : IDisposable { public int ProcessID { get; set; }//进程ID public string ProcessName { get; set; }//进程名 public float PrivateWorkingSet { get; set; }//私有工作集(KB) public float WorkingSet { get; set; }//工作集(KB) public float CpuTime { get; set; }//CPU占用率(%) public float IOOtherBytes { get; set; }//每秒IO操作(不包含控制操作)读写数据的字节数(KB) public int IOOtherOperations { get; set; }//每秒IO操作数(不包括读写)(个数) public long NetSendBytes { get; set; }//网络发送数据字节数 public long NetRecvBytes { get; set; }//网络接收数据字节数 public long NetTotalBytes { get; set; }//网络数据总字节数 public List<ICaptureDevice> dev = new List<ICaptureDevice>(); /// <summary> /// 实现IDisposable的方法 /// </summary> public void Dispose() { foreach (ICaptureDevice d in dev) { d.StopCapture(); d.Close(); } } }
定义一个ProcessPerformanceInfo类型的属性ProcInfo:
public ProcessPerformanceInfo ProcInfo { get; set; }
第一步:获取指定进程使用的所有端口号
由于一个进程可能使用多个端口号,因此监视一个进程流量时必须监视该进程使用的所有端口号,具体可以通过cmd执行命令netstat -ano并对结果进行分析。代码如下:
//进程id int pid = ProcInfo.ProcessID; //存放进程使用的端口号链表 List<int> ports = new List<int>(); #region 获取指定进程对应端口号 Process pro = new Process(); pro.StartInfo.FileName = "cmd.exe"; pro.StartInfo.UseShellExecute = false; pro.StartInfo.RedirectStandardInput = true; pro.StartInfo.RedirectStandardOutput = true; pro.StartInfo.RedirectStandardError = true; pro.StartInfo.CreateNoWindow = true; pro.Start(); pro.StandardInput.WriteLine("netstat -ano"); pro.StandardInput.WriteLine("exit"); Regex reg = new Regex("\\s+", RegexOptions.Compiled); string line = null; ports.Clear(); while ((line = pro.StandardOutput.ReadLine()) != null) { line = line.Trim(); if (line.StartsWith("TCP", StringComparison.OrdinalIgnoreCase)) { line = reg.Replace(line, ","); string[] arr = line.Split(','); ] == pid.ToString()) { ]; int pos = soc.LastIndexOf(':'); )); ports.Add(pot); } } else if (line.StartsWith("UDP", StringComparison.OrdinalIgnoreCase)) { line = reg.Replace(line, ","); string[] arr = line.Split(','); ] == pid.ToString()) { ]; int pos = soc.LastIndexOf(':'); )); ports.Add(pot); } } } pro.Close(); #endregion
所获取的端口号都存放在ports里。
第二步:获取本机IP地址和本机网络设备(即网卡)
//获取本机IP地址 IPAddress[] addrList = Dns.GetHostByName(Dns.GetHostName()).AddressList; ].ToString(); //获取本机网络设备 var devices = CaptureDeviceList.Instance; int count = devices.Count; ) { Console.WriteLine("No device found on this machine"); return; }
第三步:开始抓包,至于sharppcap库的使用方法,在官方有很详细的介绍:http://www.codeproject.com/Articles/12458/SharpPcap-A-Packet-Capture-Framework-for-NET
实现代码如下:
//开始抓包 ; i < count; ++i) { ; j < ports.Count; ++j) { CaptureFlowRecv(IP, ports[j], i); CaptureFlowSend(IP, ports[j], i); } }
CaptureFlowRecv和CaptureFlowSend函数定义如下:
public void CaptureFlowSend(string IP, int portID, int deviceID) { ICaptureDevice device = (ICaptureDevice)CaptureDeviceList.New()[deviceID]; device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrivalSend); ; device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds); string filter = "src host " + IP + " and src port " + portID; device.Filter = filter; device.StartCapture(); ProcInfo.dev.Add(device); } public void CaptureFlowRecv(string IP, int portID, int deviceID) { ICaptureDevice device = CaptureDeviceList.New()[deviceID]; device.OnPacketArrival += new PacketArrivalEventHandler(device_OnPacketArrivalRecv); ; device.Open(DeviceMode.Promiscuous, readTimeoutMilliseconds); string filter = "dst host " + IP + " and dst port " + portID; device.Filter = filter; device.StartCapture(); ProcInfo.dev.Add(device); }
private void device_OnPacketArrivalSend(object sender, CaptureEventArgs e) { var len = e.Packet.Data.Length; ProcInfo.NetSendBytes += len; } private void device_OnPacketArrivalRecv(object sender, CaptureEventArgs e) { var len = e.Packet.Data.Length; ProcInfo.NetRecvBytes += len; }
第四步:设置每秒刷新上行下行流量
/// <summary> /// 实时刷新性能参数 /// </summary> public void RefershInfo() { ProcInfo.NetRecvBytes = ; ProcInfo.NetSendBytes = ; ProcInfo.NetTotalBytes = ; Thread.Sleep(); ProcInfo.NetTotalBytes = ProcInfo.NetRecvBytes + ProcInfo.NetSendBytes; }
第五步:测试部分代码
while (true) { Console.WriteLine("proc NetTotalBytes : " + ProcInfo.NetTotalBytes); Console.WriteLine("proc NetSendBytes : " + ProcInfo.NetSendBytes); Console.WriteLine("proc NetRecvBytes : " + ProcInfo.NetRecvBytes); //每隔1s调用刷新函数对性能参数进行刷新 RefershInfo(); } //最后要记得调用Dispose方法停止抓包并关闭设备 Proc.Dispose();
以上仅仅是核心部分实现的代码简介,旨在为读者提供思路,具体实现还需要添加很多接口和类来实现。最后附上自己运行后的截图