pythonGUI之三: wxpython实现tcp客户端调试工具

安装 wxPython

使用 pip 命令安装是最简单的方法,可以在命令提示符(Windows)或终端(macOS/Linux)中输入以下命令:

pip install -U wxPython

该命令会自动从 Python Package Index (PyPI) 下载并安装与你的 Python 版本兼容的最新版本的 wxPython。

可以指定镜像源

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple wxPython

这个命令会从清华大学的镜像源下载并安装 wxPython,通常能显著提高下载速度。
如果你希望将清华大学的镜像源设置为默认源,可以使用以下命令:

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

这样,后续使用 pip 安装任何包时都会默认使用清华大学的镜像源

tcp客户端调试工具源码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TCP Client 网络调试助手(wxPython 极简版)
2025-07-15
"""

import wx
import wx.lib.scrolledpanel as scrolled  # 导入 ScrolledPanel
import socket
import threading
import binascii
import time

BUFFER = 4096


class TcpClientFrame(wx.Frame):
    def __init__(self):
        super().__init__(None, title='TCP Client 调试助手V1.0', size=(1200, 800), style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
        self.sock = None
        self.alive = False
        self.build_ui()
        self.Center()

    # ---------------- UI ----------------
    def build_ui(self):
        panel = wx.Panel(self)
        main_sizer = wx.BoxSizer(wx.HORIZONTAL)  # 主布局为水平布局

        # 左侧:接收区和发送区
        left_sizer = wx.BoxSizer(wx.VERTICAL)

        # 连接区
        conn_box = wx.StaticBoxSizer(wx.StaticBox(panel, label='连接'), wx.HORIZONTAL)
        conn_box.Add(wx.StaticText(panel, label='Host:'), 0, wx.ALL | wx.CENTER, 5)
        self.host_ctrl = wx.TextCtrl(panel, value='127.0.0.1', size=(100, -1))
        conn_box.Add(self.host_ctrl, 0, wx.ALL, 5)
        conn_box.Add(wx.StaticText(panel, label='Port:'), 0, wx.ALL | wx.CENTER, 5)
        self.port_ctrl = wx.TextCtrl(panel, value='5002', size=(60, -1))
        conn_box.Add(self.port_ctrl, 0, wx.ALL, 5)
        self.conn_btn = wx.Button(panel, label='连接')
        self.conn_btn.Bind(wx.EVT_BUTTON, self.on_connect)
        conn_box.Add(self.conn_btn, 0, wx.ALL, 5)
        left_sizer.Add(conn_box, 0, wx.EXPAND | wx.ALL, 5)

        # 接收区
        recv_box = wx.StaticBoxSizer(wx.StaticBox(panel, label='接收'), wx.VERTICAL)
        self.recv_ctrl = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2)
        recv_box.Add(self.recv_ctrl, 1, wx.ALL | wx.EXPAND, 5)

        # 创建一个水平布局用于放置 HEX 显示复选框、清空按钮、启用TCL复选框和保存参数按钮
        recv_options_box = wx.BoxSizer(wx.HORIZONTAL)

        # HEX 显示复选框
        self.hex_recv = wx.CheckBox(panel, label='HEX 显示')
        recv_options_box.Add(self.hex_recv, 0, wx.ALL | wx.CENTER, 5)

        # 清空接收区按钮
        self.clear_btn = wx.Button(panel, label='清空接收区')
        self.clear_btn.Bind(wx.EVT_BUTTON, lambda e: self.recv_ctrl.Clear())
        recv_options_box.Add(self.clear_btn, 0, wx.ALL | wx.CENTER, 5)

        # 创建一个水平布局用于放置启用TCL复选框和保存参数按钮
        right_options_box = wx.BoxSizer(wx.HORIZONTAL)

        # 启用TCL复选框
        self.enable_tcl_checkbox = wx.CheckBox(panel, label='启用TCL')
        right_options_box.Add(self.enable_tcl_checkbox, 0, wx.ALL | wx.CENTER, 5)

        # 保存参数按钮
        self.save_params_btn = wx.Button(panel, label='保存参数')
        self.save_params_btn.Bind(wx.EVT_BUTTON, self.on_save_params)
        right_options_box.Add(self.save_params_btn, 0, wx.ALL | wx.CENTER, 5)

        # 将右侧行布局添加到选项布局
        recv_options_box.AddStretchSpacer(1)  # 添加可伸展的空白区域,将右侧行布局推到右边
        recv_options_box.Add(right_options_box, 0, wx.ALL, 5)

        # 将选项布局添加到接收区
        recv_box.Add(recv_options_box, 0, wx.ALL | wx.EXPAND, 5)
        left_sizer.Add(recv_box, 1, wx.EXPAND | wx.ALL, 5)

        # 发送区
        send_box = wx.StaticBoxSizer(wx.StaticBox(panel, label='发送'), wx.HORIZONTAL)
        self.send_ctrl = wx.TextCtrl(panel, style=wx.TE_PROCESS_ENTER)
        self.send_ctrl.Bind(wx.EVT_TEXT_ENTER, self.on_send)
        send_box.Add(self.send_ctrl, 1, wx.ALL | wx.EXPAND, 5)
        self.hex_send = wx.CheckBox(panel, label='HEX')
        send_box.Add(self.hex_send, 0, wx.ALL | wx.CENTER, 5)
        self.send_btn = wx.Button(panel, label='发送')
        self.send_btn.Bind(wx.EVT_BUTTON, self.on_send)
        send_box.Add(self.send_btn, 0, wx.ALL, 5)
        left_sizer.Add(send_box, 0, wx.EXPAND | wx.ALL, 5)

        # 右侧:快捷发送区
        right_sizer = wx.BoxSizer(wx.VERTICAL)

        # 快捷发送区标题
        quick_send_notebook = wx.Notebook(panel)

        # 初始化快捷发送面板
        self.quick_send_panels = [[] for _ in range(8)]  # 创建8个空列表,每个列表对应一个标签页

        for tab_idx in range(8):
            tab_panel = scrolled.ScrolledPanel(quick_send_notebook, style=wx.VSCROLL | wx.BORDER_DOUBLE)
            tab_panel.SetupScrolling()  # 设置滚动功能
            quick_send_sizer = wx.BoxSizer(wx.VERTICAL)

            for row_idx in range(30):
                quick_send_panel = wx.Panel(tab_panel)
                quick_send_sizer.Add(quick_send_panel, 0, wx.EXPAND | wx.ALL, 5)
                quick_send_layout = wx.BoxSizer(wx.HORIZONTAL)

                # 创建一个静态文本作为标签
                tcl_label = wx.StaticText(quick_send_panel, label=f"{row_idx + 1}")
                quick_send_layout.Add(tcl_label, 0, wx.ALL | wx.CENTER, 5)

                # 创建 CheckBox
                delay_checkbox = wx.CheckBox(quick_send_panel, label="")
                quick_send_layout.Add(delay_checkbox, 0, wx.ALL | wx.CENTER, 5)

                # 发送数据输入框
                text_ctrl = wx.TextCtrl(quick_send_panel)
                quick_send_layout.Add(text_ctrl, 1, wx.ALL | wx.EXPAND, 5)

                # 发送按钮
                send_btn = wx.Button(quick_send_panel, label='发送')
                send_btn.Bind(wx.EVT_BUTTON, lambda event, tab=tab_idx, row=row_idx, ctrl=text_ctrl, delay=delay_checkbox: self.on_quick_send(event, tab, row, ctrl, delay))
                quick_send_layout.Add(send_btn, 0, wx.ALL, 5)

                # 延时输入框
                delay_ctrl = wx.TextCtrl(quick_send_panel, value='0', size=(40, -1))
                quick_send_layout.Add(delay_ctrl, 0, wx.ALL, 5)

                quick_send_panel.SetSizer(quick_send_layout)
                self.quick_send_panels[tab_idx].append((text_ctrl, send_btn, delay_ctrl, delay_checkbox))  # 将控件存储到对应的标签页列表中

            tab_panel.SetSizer(quick_send_sizer)
            quick_send_notebook.AddPage(tab_panel, f"指令区域{tab_idx + 1}")

        right_sizer.Add(quick_send_notebook, 1, wx.EXPAND | wx.ALL, 5)

        # 将左侧和右侧布局加入主布局
        main_sizer.Add(left_sizer, 60, wx.EXPAND | wx.ALL, 5)
        main_sizer.Add(right_sizer, 40, wx.EXPAND | wx.ALL, 5)

        panel.SetSizer(main_sizer)

        # 冻结窗口
        self.Freeze()
        panel.Layout()  # 强制布局更新
        self.Thaw()  # 解冻窗口

    # ---------------- 事件 ----------------
    def on_connect(self, event):
        if self.sock:     # 当前已连接 -> 断开
            self.close_conn()
            return
        host = self.host_ctrl.GetValue().strip()
        port = self.port_ctrl.GetValue().strip()
        if not host or not port:
            wx.MessageBox('请输入地址和端口', '提示', wx.OK | wx.ICON_WARNING)
            return
        try:
            port = int(port)
        except ValueError:
            wx.MessageBox('端口必须为数字', '提示', wx.OK | wx.ICON_WARNING)
            return

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(2)
        try:
            self.sock.connect((host, port))
            self.alive = True
            self.conn_btn.SetLabel('断开')
            self.recv_ctrl.AppendText(f'[+] 已连接 {host}:{port}\n')
            threading.Thread(target=self.recv_thread, daemon=True).start()
        except Exception as e:
            wx.MessageBox(f'连接失败: {e}', '错误', wx.OK | wx.ICON_ERROR)
            self.sock = None

    def on_send(self, event):
        if not self.sock:
            wx.MessageBox('请先连接服务器', '提示', wx.OK | wx.ICON_WARNING)
            return
        text = self.send_ctrl.GetValue()
        if not text:
            return
        try:
            if self.enable_tcl_checkbox.IsChecked():
                text = text   #需要加入TCL引擎, #TODO
            if self.hex_send.IsChecked():
                data = bytes.fromhex(text.replace(' ', ''))
            else:
                data = text.encode('utf-8', errors='ignore')
            self.sock.sendall(data)
            self.send_ctrl.Clear()
        except Exception as e:
            self.recv_ctrl.AppendText(f'[!] 发送失败: {e}\n')
            self.close_conn()

    def on_quick_send(self, event, tab_idx, row_idx, text_ctrl, delay_checkbox):
        if not self.sock:
            wx.MessageBox('请先连接服务器', '提示', wx.OK | wx.ICON_WARNING)
            return
        text = text_ctrl.GetValue()
        if not text:
            return
        delay_ctrl = self.quick_send_panels[tab_idx][row_idx][2]  # 通过 tab_idx 和 row_idx 访问延时输入框
        delay = int(delay_ctrl.GetValue()) if delay_ctrl.GetValue().isdigit() else 0

        try:
            if self.enable_tcl_checkbox.IsChecked():
                text = text  # 需要加入TCL引擎, #TODO
            if self.hex_send.IsChecked():
                data = bytes.fromhex(text.replace(' ', ''))
            else:
                data = text.encode('utf-8', errors='ignore')
            if delay_checkbox.IsChecked():
                # 延时后发送
                if delay > 0:
                    time.sleep(delay / 1000.0)  # 延时单位为毫秒
                self.sock.sendall(b'cmd:' + data)
                self.recv_ctrl.AppendText(f'-->>: cmd:{text}\n')
            else:
                self.sock.sendall(data)
                self.recv_ctrl.AppendText(f'-->: {text}\n')
        except Exception as e:
            self.recv_ctrl.AppendText(f'[!] 快捷发送失败: {e}\n')
            self.close_conn()

    def on_save_params(self, event):
        wx.MessageBox('功能待完成', '提示', wx.OK | wx.ICON_INFORMATION)

        # ---------------- 线程 ----------------

    def recv_thread(self):
        while self.alive :
            try:
                data = self.sock.recv(BUFFER)
                if not data:
                    break
                if self.hex_recv.IsChecked():
                    text = binascii.hexlify(data, ' ').decode('utf-8') + '\n'
                else:
                    text = data.decode('utf-8', errors='ignore')
                wx.CallAfter(self.recv_ctrl.AppendText, f"<--{text}")
            except socket.timeout:
                continue
            except Exception as e:
                if self.alive:
                    wx.CallAfter(self.recv_ctrl.AppendText, f'[!] 接收异常: {e}\n')
                    break
        wx.CallAfter(self.close_conn)

        # ---------------- 关闭 ----------------

    def close_conn(self):
        self.alive = False
        if self.sock:
            try:
                self.sock.close()
                self.conn_btn.SetLabel('连接')
                self.recv_ctrl.AppendText('[-] 连接已断开\n')
            except:
                pass
            self.sock = None


class App(wx.App):
    def OnInit(self):
        TcpClientFrame().Show()
        return True

if __name__ == '__main__':
    App().MainLoop()
运行效果

image

posted @ 2025-07-23 08:47  cupid8505  阅读(38)  评论(0)    收藏  举报