import sys
import psutil
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QHeaderView, QComboBox, QHBoxLayout, QLabel, QLineEdit
from PySide6.QtCore import QTimer, Qt, QThread, Signal
from PySide6.QtGui import QIcon, QPixmap
from collections import defaultdict
import win32gui
import win32ui
from PIL import Image

def make_transparent(image):
    # 将图像转换为带有透明通道的格式
    image = image.convert("RGBA")
    
    # 获取图像的数据
    datas = image.getdata()
    
    # 创建新的图像数据，将黑色背景转换为透明
    new_data = []
    for item in datas:
        if item[0] == 0 and item[1] == 0 and item[2] == 0:  # 黑色
            new_data.append((255, 255, 255, 0))  # 透明
        else:
            new_data.append(item)
    
    # 更新图像数据
    image.putdata(new_data)
    return image

class NetworkMonitorThread(QThread):
    update_signal = Signal(list)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.last_process_net_io = {}
        self.total_process_net_io = defaultdict(lambda: {'bytes_recv': 0, 'bytes_sent': 0})
        self.running = True
        self.process_cache = {}
        self.update_interval = 2  # 更新间隔设为5秒

    def run(self):
        while self.running:
            current_process_net_io = self.get_process_net_io()
            sorted_data = self.process_data(current_process_net_io)
            self.update_signal.emit(sorted_data)
            self.msleep(self.update_interval * 1000)

    def get_process_net_io(self):
        process_net_io = {}
        active_connections = psutil.net_connections()
        active_pids = set(conn.pid for conn in active_connections if conn.pid is not None)
        
        for pid in active_pids:
            try:
                proc = psutil.Process(pid)
                if pid in self.process_cache:
                    name = self.process_cache[pid]
                else:
                    name = proc.name()
                    self.process_cache[pid] = name
                
                io_counters = proc.io_counters()
                process_net_io[pid] = {
                    'name': name,
                    'bytes_recv': io_counters.read_bytes,
                    'bytes_sent': io_counters.write_bytes
                }
            except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                pass
        return process_net_io

    def process_data(self, current_process_net_io):
        sorted_data = []
        for pid, current in current_process_net_io.items():
            last = self.last_process_net_io.get(pid, current)

            download_speed = (current['bytes_recv'] - last['bytes_recv']) / (self.update_interval * 1024)  # KB/s
            upload_speed = (current['bytes_sent'] - last['bytes_sent']) / (self.update_interval * 1024)  # KB/s

            self.total_process_net_io[pid]['bytes_recv'] += current['bytes_recv'] - last['bytes_recv']
            self.total_process_net_io[pid]['bytes_sent'] += current['bytes_sent'] - last['bytes_sent']

            total_downloaded = self.total_process_net_io[pid]['bytes_recv'] / (1024 * 1024)
            total_uploaded = self.total_process_net_io[pid]['bytes_sent'] / (1024 * 1024)

            if download_speed > 0 or upload_speed > 0 or total_downloaded > 0 or total_uploaded > 0:
                sorted_data.append((current['name'], download_speed, upload_speed, total_downloaded, total_uploaded))

        self.last_process_net_io = current_process_net_io

        sort_by = self.parent().sort_combo.currentIndex()
        sorted_data.sort(key=lambda x: (x[1], x[0]) if sort_by == 0 else (x[0], x[1]), reverse=sort_by == 0)
        return sorted_data[:20]  # 只返回前20个进程

    def stop(self):
        self.running = False

class NetworkMonitor(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Network Traffic Monitor")
        self.setGeometry(100, 100, 800, 600)

        self.table = QTableWidget(self)
        self.table.setColumnCount(6)
        self.table.setHorizontalHeaderLabels(["Icon", "Process", "Download Speed (KB/s)", "Upload Speed (KB/s)", "Downloaded (MB)", "Uploaded (MB)"])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        layout = QVBoxLayout()
        layout.addWidget(self.table)

        self.sort_combo = QComboBox()
        self.sort_combo.addItem("Sort by Download Speed")
        self.sort_combo.addItem("Sort by Process Name")
        self.sort_combo.currentIndexChanged.connect(self.update_network_info)

        self.filter_input = QLineEdit(self)
        self.filter_input.setPlaceholderText("Filter by process name")
        self.filter_input.textChanged.connect(self.update_network_info)

        sort_layout = QHBoxLayout()
        sort_layout.addWidget(QLabel("Sort by:"))
        sort_layout.addWidget(self.sort_combo)
        sort_layout.addWidget(QLabel("Filter:"))
        sort_layout.addWidget(self.filter_input)
        layout.addLayout(sort_layout)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        self.network_thread = NetworkMonitorThread(self)
        self.network_thread.update_signal.connect(self.update_table)
        self.network_thread.start()

        self.icon_cache = {}
        self.default_icon = QIcon("default_icon.png")  # 默认图标路径

    def update_table(self, sorted_data):
        filter_text = self.filter_input.text().lower()
        filtered_data = [data for data in sorted_data if filter_text in data[0].lower() or not filter_text]

        self.table.setRowCount(len(filtered_data))
        for row, data in enumerate(filtered_data):
            process_name = data[0]
            icon_item = QTableWidgetItem()
            icon = self.get_exe_icon(process_name)
            if icon:
                icon_item.setIcon(icon)
            else:
                icon_item.setIcon(self.default_icon)  # 使用默认图标
            self.table.setItem(row, 0, icon_item)

            for col, value in enumerate(data):
                self.table.setItem(row, col + 1, QTableWidgetItem(f"{value:.2f}" if isinstance(value, float) else str(value)))

    def get_exe_icon(self, process_name):
        if process_name in self.icon_cache:
            return self.icon_cache[process_name]

        try:
            # 获取进程的完整路径
            exe_path = None
            for proc in psutil.process_iter(['pid', 'name', 'exe']):
                if proc.info['name'] == process_name:
                    exe_path = proc.info['exe']
                    break

            if not exe_path:
                return None

            # 获取exe文件的图标
            large, small = win32gui.ExtractIconEx(exe_path, 0)
            if not large:
                return None

            # 创建一个设备上下文
            hdc = win32gui.GetDC(0)
            hdc = win32ui.CreateDCFromHandle(hdc)
            hbmp = win32ui.CreateBitmap()
            hbmp.CreateCompatibleBitmap(hdc, 32, 32)

            # 在设备上下文中绘制图标
            hdc = hdc.CreateCompatibleDC()
            hdc.SelectObject(hbmp)
            hdc.DrawIcon((0, 0), large[0])

            # 保存图标到本地文件
            bmpinfo = hbmp.GetInfo()
            bmpstr = hbmp.GetBitmapBits(True)
            img = Image.frombuffer(
                'RGB',
                (bmpinfo['bmWidth'], bmpinfo['bmHeight']),
                bmpstr, 'raw', 'BGRX', 0, 1)

            # 处理图像以去除黑色背景
            img = make_transparent(img)

            # 将图像转换为QPixmap
            img_path = f'temp_icon_{process_name}.png'
            img.save(img_path)
            pixmap = QPixmap(img_path)

            icon = QIcon(pixmap)
            self.icon_cache[process_name] = icon
            return icon
        except Exception as e:
            print(f"Error extracting icon: {e}")
            return None

    def update_network_info(self):
        pass

    def closeEvent(self, event):
        self.network_thread.stop()
        self.network_thread.wait()
        super().closeEvent(event)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = NetworkMonitor()
    window.show()
    sys.exit(app.exec())
。。。。。。。。。。。。
有的程序明明使用网络进行下载或上传，但是我的软件监控显示这个程序下载速度或上传速度是0，解决思路是：某些程序可能使用低级网络驱动程序或特殊的网络栈，绕过了常规的网络监控方法。考虑使用更底层的网络监控技术，如  Npcap。
。 。。。。。。。。
现在我已经安装了Npcap，请参考下面的例子修改代码利用scapy库解决问题，。
。。。。。。。。。。
#!/usr/bin/env python3
# coding:utf-8
# author:Lyu

from prometheus_client import Gauge
from scapy.all import *

import time

net_data = {}
d_net_info = {}

lock = threading.Lock()


def print_data():
    # 将ip 与进程关联，统计进程的流量
    while True:
        lock.acquire()
        for key in net_data:
            try:
                label = d_net_info[key]
                src, dst = key.split(' ')
                network_method.labels(src=src, dst=dst, pid=label['pid'], process_state=label['state'],
                                      process_name=label['name']).set(
                    net_data[key])
            except KeyError:
                # with open('a.json','w') as f:
                #     json.dump(d_net_info,f,indent=4,ensure_ascii=False)
                pass
        lock.release()
        time.sleep(5)


def get_net_info():
    # 获取进程与网络之间的关系
    while True:
        net_info = os.popen('netstat -nbo').readlines()
        process_info = ''
        process_state = ''
        process_pid = ''
        for l in net_info[4:]:
            s = l.split()
            if len(s) > 2:
                key, key_resv, process_state, process_pid = s[1:5]
                process_info = f'{key} {key_resv}'
            else:
                if s[0].startswith('['):
                    process_name = s[0].replace('[', '').replace(']', '')
                    d_net_info.setdefault(process_info, {})
                    a_ip = dict(name=process_name, state=process_state, pid=process_pid)
                    d_net_info[process_info] = a_ip
                    del process_info, process_pid, process_state
                else:
                    pass
        time.sleep(3)


# 回调打印函数
def PackCallBack(packet):
    # packet是在网卡抓到的帧，每取一次payload就相当于向上解封装一次。
    # 打印源IP，源端口，目的IP，目的端口
    # if packet.haslayer('HTTP'):
    # http = packet.payload.payload.payload
    # print(http.show())
    print("[%s]%s:%s-->%s:%s" % (packet['IP'].len, packet['IP'].src, packet.sport, packet['IP'].dst, packet.dport))
    net_data.setdefault(packet['IP'].dst, 0)
    net_data[packet['IP'].dst] += packet.len
    # 打印数据包
    # print(packet.show())
    print(net_data)


# 时间戳转换函数
def TimeStamp2Time(timeStamp):
    timeTmp = time.localtime(timeStamp)  # time.localtime()格式化时间戳为本地时间
    myTime = time.strftime("%Y-%m-%d %H:%M:%S", timeTmp)  # 将本地时间格式化为字符串
    return myTime


if __name__ == '__main__':
    # t = threading.Thread(target=get_net_info)
    # t.start()
    # start_http_server(9128)
    network_method = Gauge('process_network_total', '进程流量', ['pid', 'process_name', 'process_state', 'src', 'dst'])
    # t1 = threading.Thread(target=print_data)
    # t1.start()
    _uuid = uuid.uuid1()
    """
        count:抓取报的数量，设置为0时则一直捕获
        store:保存抓取的数据包或者丢弃，1保存，0丢弃
        offline:从pcap文件中读取数据包，而不进行嗅探，默认为None
        prn:为每个数据包定义一个回调函数，通常使用lambda表达式来写回调函数
        filter:过滤规则，可以在里面定义winreshark里面的过滤语法，使用 Berkeley Packet Filter (BPF)语法，具体参考：[http://blog.csdn.net/qwertyupoiuytr/article/details/54670477](http://blog.csdn.net/qwertyupoiuytr/article/details/54670477)
        L2socket:使用给定的L2socket
        timeout:在给定的事件后停止嗅探，默认为None
        opened_socket:对指定的对象使用.recv进行读取
        stop_filter:定义一个函数，决定在抓到指定的数据之后停止
        iface:指定抓包的网卡,不指定则代表所有网卡
    """
    # packets = sniff(offline='1.pcapng',prn=PackCallBack)
    packets = sniff(count=0,
                    offline=None,
                    store=1,
                    prn=PackCallBack,
                    filter='host baidu.com',
                    L2socket=None,
                    timeout=None,
                    opened_socket=None,
                    stop_filter=None,
                    iface=None)
    # filename = f"{_uuid}.pcap"
    # wrpcap(filename, packets)