import sys
import pyshark
import psutil
import threading
import time
from collections import defaultdict

from PySide6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem,
    QComboBox, QLabel
)
from PySide6.QtCore import QTimer


def get_process_connections():
    connections = {}
    for proc in psutil.process_iter(['pid', 'name']):
        try:
            for conn in proc.net_connections(kind='tcp'):
                if conn.status == psutil.CONN_ESTABLISHED:
                    key = (conn.laddr.ip, conn.laddr.port, conn.raddr.ip, conn.raddr.port)
                    connections[key] = {'pid': proc.info['pid'], 'name': proc.info['name']}
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue
    print(f"Total connections captured: {len(connections)}")  # 调试信息
    return connections


class ProcessTraffic:
    def __init__(self):
        self.data = defaultdict(lambda: {
            'download_speed': 0,
            'upload_speed': 0,
            'download_total': 0,
            'upload_total': 0,
            'pid': None,
            'name': ''
        })
        self.lock = threading.Lock()
        self.last_time = time.time()
        self.last_bytes = defaultdict(lambda: {'download': 0, 'upload': 0})

    def update(self, packet, connections):
        try:
            ip_layer = packet.ip
            transport_layer = packet[packet.transport_layer]
            src_ip = ip_layer.src
            dst_ip = ip_layer.dst
            src_port = int(transport_layer.srcport)
            dst_port = int(transport_layer.dstport)
            length = int(packet.length)
        except AttributeError:
            return  # 非IP包或不支持的层

        key = (src_ip, src_port, dst_ip, dst_port)
        reverse_key = (dst_ip, dst_port, src_ip, src_port)

        process_info = connections.get(key) or connections.get(reverse_key)
        if not process_info:
            return

        pid = process_info['pid']
        name = process_info['name']

        current_time = time.time()
        elapsed = current_time - self.last_time if self.last_time else 1
        self.last_time = current_time

        direction = 'download' if key in connections else 'upload'

        with self.lock:
            self.data[pid]['pid'] = pid
            self.data[pid]['name'] = name
            if direction == 'download':
                self.data[pid]['download_total'] += length
                self.data[pid]['download_speed'] = length / elapsed / 1024  # KB/s
            else:
                self.data[pid]['upload_total'] += length
                self.data[pid]['upload_speed'] = length / elapsed / 1024  # KB/s

    def get_active_processes(self):
        with self.lock:
            return {pid: info for pid, info in self.data.items() if
                    info['download_total'] > 0 or info['upload_total'] > 0}


class NetworkMonitorGUI(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Network Monitor")
        self.resize(800, 600)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # 排序选项
        self.sort_label = QLabel("排序方式:")
        self.sort_combo = QComboBox()
        self.sort_combo.addItems(["下载速度", "程序名称"])
        self.sort_combo.currentIndexChanged.connect(self.update_table)

        self.layout.addWidget(self.sort_label)
        self.layout.addWidget(self.sort_combo)

        # 显示数据的表格
        self.table = QTableWidget()
        self.table.setColumnCount(6)
        self.table.setHorizontalHeaderLabels([
            "程序名称", "PID", "下载速度 (KB/s)",
            "上传速度 (KB/s)", "下载累计 (MB)", "上传累计 (MB)"
        ])
        self.layout.addWidget(self.table)

        # 初始化流量数据
        self.traffic = ProcessTraffic()

        # 初始化捕获
        self.capture = pyshark.LiveCapture(interface='WLAN')

        # 启动捕获线程
        self.capture_thread = threading.Thread(target=self.start_capture, daemon=True)
        self.capture_thread.start()

        # 每秒更新表格
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_table)
        self.timer.start(1000)

    def start_capture(self):
        for packet in self.capture.sniff_continuously():
            connections = get_process_connections()
            self.traffic.update(packet, connections)

    def update_table(self):
        processes = self.traffic.get_active_processes()
        sorted_processes = []

        sort_by = self.sort_combo.currentText()
        if sort_by == "下载速度":
            sorted_processes = sorted(
                processes.values(),
                key=lambda x: x.get('download_speed', 0),
                reverse=True
            )
        elif sort_by == "程序名称":
            sorted_processes = sorted(
                processes.values(),
                key=lambda x: x['name']
            )

        self.table.setRowCount(len(sorted_processes))
        for row, info in enumerate(sorted_processes):
            self.table.setItem(row, 0, QTableWidgetItem(info['name']))
            self.table.setItem(row, 1, QTableWidgetItem(str(info['pid'])))
            self.table.setItem(row, 2, QTableWidgetItem(f"{info.get('download_speed', 0):.2f}"))
            self.table.setItem(row, 3, QTableWidgetItem(f"{info.get('upload_speed', 0):.2f}"))
            self.table.setItem(row, 4, QTableWidgetItem(f"{info['download_total'] / 1e6:.2f}"))
            self.table.setItem(row, 5, QTableWidgetItem(f"{info['upload_total'] / 1e6:.2f}"))


def main():
    app = QApplication(sys.argv)
    gui = NetworkMonitorGUI()
    gui.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
有很多使用网络的程序都没检测到，只检测到了两个程序，而且界面上显示的这两个程序的下载速度和上传速度与实际使用小的太多，我用的管理员身份运行的，而且百分百确定接口名是正确的，控制台显示：Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43
Total connections captured: 43