本人最近在做自己课题的时候用到了Python环境下的TCP网络通信,在互联网上进行了搜寻,然苦于缺乏较为完整的示例程序,于是自己依靠现存的一些姿态,以及询问AI不断摸索完成了课题所需的简单的单连接TCP网络通信软件程序,个人基于该程序进一步针对我自己课题做了定制化封装,而该软件就共享到这里,以便后来者参考。
需要事先声明,本人程序水平不高,该软件中可能仍存在着一些BUG(目前测试应用时尚未发现),故请辩证参考,我也欢迎与使用者进行一些交流。
关于该软件,我使用了Anaconda来配置Python的虚拟环境(Python的版本为3.8),环境中安装了PySide6(在Anaconda中通过pip指令安装)用于实现软件功能,使用PyCharm作为IDE进行开发,使用PySide6自带的designer.exe做界面设计(如果你也是和我一样使用Anaconda安装的PySide6,则该界面设计工具应位于 你的Anaconda配置环境目录/你的Python虚拟环境名/Lib/site-packages/PySide6/designer.exe ,可以留一个桌面快捷方式访问)。
先行展示、说明该软件的界面功能。

软件界面
右下角是一些最基本的设置,首先角色下拉列表框选择软件充当TCP服务端还是客户端。作为服务端时根据当前设置的IP、COM监听任何客户端连接,作为客户端时则连接到指定IP、COM的服务端中,实现双向通信的功能。
然后下方数据下拉列表框选择数据类型,包括三种类型。第一种为byte,通过ascii编码发送文本框数据;第二种为hex,通过文本框使用十六进制格式发送字节序列(如发送00即为发送十六进制0x00代表的字节);第三种为utf-8,通过utf-8编码发送数据,该模式下可发送汉字。如果是使用两个该软件建立连接,需要保证双方发送数据类型相同,以保证正常的数据编解码。
之后下方的IP、COM输入框分别用于输入网络通信的IP地址与端口号,如果是正常测试使用,最好使用10000以上的端口号防止冲突。
最后下方的连接按钮用于连接、断开连接,录制按钮用于录制接收数据,再次点击该按钮可停止录制。可通过右上角的按钮展示录制的数据、提供保存与打开数据的功能。
左侧为发送、接收数据的展示栏,设置了一个HEX复选框,在勾选时将以十六进制的形式展示数据,否则以指定的数据类型来展示数据,如下图所示。

HEX复选框功能展示
说了这么多功能,以下展示所有的代码,首先项目目录结构如图所示(.ico的图标可选择截图转换为.ico文件使用,需保证名字相同,也可以在源码中注释掉设置图标的代码)。

项目目录结构

软件图标
接下来是配置文件cfg.xml,可配置默认IP、COM、角色与发送数据类型、保存/读取文件基路径、是否启用HEX模式(False启用)、分隔符(用于保存/读取文件分割字节数据(所有的编码模式底层均需转换为字节数据用于网络传输))。
<?xml version="1.0" encoding="UTF-8"?>
<Config>
<TCP IP="127.0.0.1" COM="10000" Role="Client" MsgType="byte" />
<Settings DataFileBasePath="./DataFile" showByte="False" sep="FFFF" />
</Config>
之后是TCPPlumb.ui,通过PySide6提供的designer.exe进行可视化编辑(本质也是xml文件)。
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>645</width>
<height>425</height>
</rect>
</property>
<property name="windowTitle">
<string>网络调试工具</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>接收端:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="rcvTextBox">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>No Message was Received</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>发送端:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="sendTextBox">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string/>
</property>
<property name="placeholderText">
<string>No Message to send</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="ckBxHex">
<property name="text">
<string>HEX</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineSendText">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnSendData">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>发送</string>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClearTextBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>清空</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="minimumSize">
<size>
<width>270</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>270</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>270</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>270</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>字节数据列表:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="byteDataTextBox">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<property name="spacing">
<number>5</number>
</property>
<item row="0" column="1">
<widget class="QPushButton" name="btnClearByteData">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>清空</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="btnShowByteData">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>展示</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="btnSaveData">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="btnOpenData">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>打开</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="spacing">
<number>5</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>数据</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="lblIP">
<property name="text">
<string>IP</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>角色</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QPushButton" name="btnConnect">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>连接</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lblCOM">
<property name="text">
<string>COM</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QPushButton" name="btn2">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="comboRoleType">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<item>
<property name="text">
<string>TCP客户端</string>
</property>
</item>
<item>
<property name="text">
<string>TCP服务端</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="comboMsgType">
<item>
<property name="text">
<string>byte</string>
</property>
</item>
<item>
<property name="text">
<string>hex</string>
</property>
</item>
<item>
<property name="text">
<string>utf-8</string>
</property>
</item>
</widget>
</item>
<item row="3" column="1" colspan="3">
<widget class="QLineEdit" name="lineIP">
<property name="text">
<string>0.0.0.0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QLineEdit" name="lineCOM">
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="btnRecord">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string>录制</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QPushButton" name="btn1">
<property name="minimumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>24</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>645</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
最后就是主程序TcpPlumb.py了。
import os
import socket
import threading
from datetime import datetime
import xml.etree.ElementTree as ET
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QPushButton, QLineEdit, QPlainTextEdit, QComboBox, QMenuBar, QStatusBar, \
QFileDialog, QCheckBox
from PySide6.QtCore import Qt, Signal, QThread, QObject
from PySide6.QtUiTools import QUiLoader
uiLoader = QUiLoader()
class MainUI(QObject):
mainUICloseSignal = Signal()
def __init__(self):
super().__init__()
# 路径设置
self.ui = uiLoader.load('TcpPlumb.ui')
self.ui.setWindowIcon(QIcon('TcpPlumb.ico'))
self.cfgPath = "cfg.xml"
# 获取控件
self.btnConnect: QPushButton = getattr(self.ui, 'btnConnect')
self.btnOpenData: QPushButton = getattr(self.ui, 'btnOpenData')
self.btnSaveData: QPushButton = getattr(self.ui, 'btnSaveData')
self.btnSendData: QPushButton = getattr(self.ui, 'btnSendData')
self.btnClearTextBox: QPushButton = getattr(self.ui, 'btnClearTextBox')
self.btnShowByteData: QPushButton = getattr(self.ui, 'btnShowByteData')
self.btnClearByteData: QPushButton = getattr(self.ui, 'btnClearByteData')
self.btnRecord: QPushButton = getattr(self.ui, 'btnRecord')
self.lineSendText: QLineEdit = getattr(self.ui, 'lineSendText')
self.lineIP: QLineEdit = getattr(self.ui, 'lineIP')
self.lineCOM: QLineEdit = getattr(self.ui, 'lineCOM')
self.rcvTextBox: QPlainTextEdit = getattr(self.ui, 'rcvTextBox')
self.sendTextBox: QPlainTextEdit = getattr(self.ui, 'sendTextBox')
self.byteDataTextBox: QPlainTextEdit = getattr(self.ui, 'byteDataTextBox')
self.comboRoleType: QComboBox = getattr(self.ui, 'comboRoleType')
self.comboMsgType: QComboBox = getattr(self.ui, 'comboMsgType')
self.menubar: QMenuBar = getattr(self.ui, 'menubar')
self.statusbar: QStatusBar = getattr(self.ui, 'statusbar')
self.ckBxHex: QCheckBox = getattr(self.ui, 'ckBxHex')
# 事件绑定
self.btnConnect.clicked.connect(self.onBtnConnectClicked)
self.btnSendData.clicked.connect(self.onBtnSendDataClicked)
self.btnSaveData.clicked.connect(self.onBtnSaveDataClicked)
self.btnOpenData.clicked.connect(self.onBtnOpenDataClicked)
self.btnClearTextBox.clicked.connect(self.onBtnClearTextBoxClicked)
self.btnShowByteData.clicked.connect(self.onBtnShowByteDataClicked)
self.btnClearByteData.clicked.connect(self.onBtnClearByteDataClicked)
self.btnRecord.clicked.connect(self.onBtnRecord)
self.comboMsgType.currentIndexChanged.connect(self.onComboMsgTypeIndexChanged)
self.ckBxHex.stateChanged.connect(self.onCkBxHexStateChanged)
# 非控件属性
self.byteDataList = []
self.soc = None
self.linkingIP = None
self.linkingCOM = None
self.linkingType = None
self.recording = False
self.serverThread = None
self.receiveThread = None
self.serLinkCliSoc = None
self.dataListLock = threading.Lock()
self.msgType = 0
self.dataFileBasePath = ''
self.showByte = True
self.sep = None
# 初始化加载
self.xmlInit()
self.mainUICloseSignal.connect(self.onMainUICloseSignal)
def xmlInit(self):
if os.path.exists(self.cfgPath):
root = ET.parse(self.cfgPath).getroot()
else:
raise FileNotFoundError(f"配置文件{os.path.basename(self.cfgPath)}不存在。")
TCP = root.find('TCP')
self.lineIP.setText(TCP.get('IP'))
self.lineCOM.setText(TCP.get('COM'))
self.comboRoleType.setCurrentIndex(0 if TCP.get('Role') == 'Client' else 1)
msgType = TCP.get('MsgType')
if msgType == 'byte':
self.msgType = 0
elif msgType == 'hex':
self.msgType = 1
else:
self.msgType = 2
self.comboMsgType.setCurrentIndex(self.msgType)
settings = root.find('Settings')
self.dataFileBasePath = settings.get('DataFileBasePath')
if not os.path.exists(self.dataFileBasePath):
os.makedirs(self.dataFileBasePath, exist_ok=True)
self.ckBxHex.setCheckState(Qt.CheckState.Unchecked if settings.get('showByte') == 'True' else Qt.CheckState.Checked)
self.sep = bytearray(bytes.fromhex(settings.get('sep')))
def onBtnConnectClicked(self):
if self.btnConnect.text() == "连接":
self.enableBtnS(True)
self.linkingType = self.comboRoleType.currentIndex()
self.linkingIP = self.lineIP.text()
self.linkingCOM = int(self.lineCOM.text())
self.soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.linkingType == 0: # 建立客户端连接
try:
# self.soc.settimeout(3) # 可以设置客户端连接超时时间,不然连接不上会使界面卡住几十秒
self.soc.connect((self.linkingIP, self.linkingCOM))
except Exception as e:
self.enableBtnS(False)
self.statusbar.showMessage(f'{e}', 0)
return
self.receiveThread = ReceiveThread(self.soc)
self.receiveThread.updateMessageSignal.connect(self.updCliRcvData)
self.receiveThread.finished.connect(self.onCliRcvThFinished)
self.statusbar.showMessage(f"连接至服务端({self.linkingIP},{self.linkingCOM})。", 5000)
self.receiveThread.start()
else: # 建立服务端连接
self.serverThread = ServerThread(self.soc, self.linkingIP, self.linkingCOM)
self.serverThread.clientLinkingSignal.connect(self.onSerLinkCli)
self.serverThread.finished.connect(self.onSerThFinished)
self.statusbar.showMessage("服务端开始监听。", 5000)
self.serverThread.start()
else:
if self.linkingType == 0:
if self.receiveThread is not None:
self.receiveThread.stopReceiveThread()
self.receiveThread = None
else:
if self.receiveThread is not None:
self.receiveThread.stopReceiveThread()
self.receiveThread = None
if self.serverThread is not None:
self.serverThread.stopServerThread()
self.serverThread = None
def updCliRcvData(self, byteArray):
if self.recording:
with self.dataListLock:
self.byteDataList.append(byteArray)
self.addBAToTextBox(self.rcvTextBox, byteArray)
def onCliRcvThFinished(self):
self.statusbar.showMessage("客户端已断开连接。", 5000)
self.receiveThread = None
self.enableBtnS(False)
def onSerThFinished(self):
self.statusbar.showMessage("服务端已停止监听", 5000)
self.serverThread = None
self.enableBtnS(False)
def onSerLinkCli(self, soc, address):
self.serLinkCliSoc = soc
self.statusbar.showMessage(f"与客户端{address}已建立连接。", 5000)
self.receiveThread = ReceiveThread(self.serLinkCliSoc)
self.receiveThread.updateMessageSignal.connect(self.updCliRcvData)
self.receiveThread.finished.connect(self.onSerLinkCliThFinished)
self.receiveThread.start()
def onSerLinkCliThFinished(self):
self.statusbar.showMessage("服务端链接客户端已断开连接", 5000)
self.receiveThread = None
def onBtnSendDataClicked(self):
msg = self.lineSendText.text()
try:
if self.msgType == 0: # byte
byteArray = msg.encode('ascii')
elif self.msgType == 1: # hex
byteArray = bytes.fromhex(msg)
else: # utf-8
byteArray = msg.encode('utf-8')
except Exception as e:
self.statusbar.showMessage(f"编码出错:{e}", 0)
return
if self.linkingType == 0:
self.soc.sendall(byteArray)
else:
if self.serLinkCliSoc is not None:
self.serLinkCliSoc.sendall(byteArray)
else:
self.statusbar.showMessage("未连接客户端,无法发送消息。", 5000)
return
self.addBAToTextBox(self.sendTextBox, byteArray)
def onBtnSaveDataClicked(self):
with self.dataListLock:
if len(self.byteDataList) > 0:
filePath, _ = QFileDialog.getSaveFileName(parent=None,
caption='保存数据文件',
dir=os.path.join(self.dataFileBasePath, f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin"),
filter='数据文件(*.bin);;所有文件(*.*)',
selectedFilter='')
if filePath:
fileName = os.path.splitext(os.path.basename(filePath))[0]
fileDir = os.path.dirname(filePath)
filePath = os.path.join(fileDir, fileName + ".bin")
with open(filePath, "wb") as file:
for i, byteData in enumerate(self.byteDataList):
if i > 0:
file.write(self.sep) # 分隔符
file.write(byteData)
self.statusbar.showMessage(f"保存数据文件为 {fileName}。", 5000)
else:
self.statusbar.showMessage("数据列表为空,保存失败。", 5000)
def onBtnOpenDataClicked(self):
dialog = QFileDialog()
dialog.setWindowTitle("选择数据文件")
dialog.setNameFilter("数据文件(*.bin);;所有文件(*.*)")
dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
if dialog.exec():
selectedFilePath = dialog.selectedFiles()
filePath = selectedFilePath[0]
fileName = os.path.basename(filePath)
with self.dataListLock:
self.byteDataList.clear()
with open(filePath, 'rb') as file:
binaryContent = file.read()
segments = binaryContent.split(self.sep)
if self.showByte:
for i, segment in enumerate(segments):
self.byteDataList.append(segment)
self.byteDataTextBox.appendPlainText(f"[{i + 1}]: {segment}")
else:
for i, segment in enumerate(segments):
self.byteDataList.append(segment)
self.byteDataTextBox.appendPlainText(
f"[{i + 1}]: {' '.join([f'{byte:02x}' for byte in segment])}")
self.statusbar.showMessage(f"打开数据文件{fileName}。", 5000)
def onBtnClearTextBoxClicked(self):
self.lineSendText.clear()
self.sendTextBox.clear()
self.rcvTextBox.clear()
def onBtnShowByteDataClicked(self):
with self.dataListLock:
dataLength = len(self.byteDataList)
if dataLength > 0:
self.byteDataTextBox.clear()
if self.showByte:
for i in range(dataLength):
self.byteDataTextBox.appendPlainText(f"[{i + 1}]: {self.byteDataList[i]}")
else:
for i in range(dataLength):
self.byteDataTextBox.appendPlainText(
f"[{i + 1}]: {' '.join([f'{byte:02x}' for byte in self.byteDataList[i]])}")
else:
self.statusbar.showMessage(f"数据列表为空。", 5000)
def onBtnClearByteDataClicked(self):
with self.dataListLock:
self.byteDataList.clear()
self.byteDataTextBox.clear()
self.statusbar.showMessage("清空字节数据列表。", 5000)
def onBtnRecord(self):
self.recording = not self.recording
self.btnRecord.setText("停止" if self.recording else "录制")
def onComboMsgTypeIndexChanged(self, index):
self.msgType = index
def onCkBxHexStateChanged(self, state):
self.showByte = (state == 0)
def enableBtnS(self, b):
if b: # b = True: 连接
self.btnConnect.setText("断开")
self.comboRoleType.setEnabled(False)
self.lineIP.setEnabled(False)
self.lineCOM.setEnabled(False)
self.lineSendText.setEnabled(True)
self.btnSendData.setEnabled(True)
else: # b = False: 未连接
self.btnConnect.setText("连接")
self.comboRoleType.setEnabled(True)
self.lineIP.setEnabled(True)
self.lineCOM.setEnabled(True)
self.lineSendText.setEnabled(False)
self.btnSendData.setEnabled(False)
def addBAToTextBox(self, textbox, byteArray):
textbox.appendPlainText(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]:")
if self.msgType == 2:
if self.showByte:
textbox.appendPlainText(f"UFT8:{byteArray.decode('utf-8')}\n")
else:
textbox.appendPlainText(f"UFT8:{' '.join([f'{byte:02x}' for byte in byteArray])}\n")
else:
if self.showByte:
textbox.appendPlainText(f"ASCII:{byteArray}\n")
else:
textbox.appendPlainText(f"ASCII:{' '.join([f'{byte:02x}' for byte in byteArray])}\n")
# 始终滚动到最底部
scrollbar = textbox.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
@staticmethod
def addMsgToTextBox(textbox, msg):
textbox.appendPlainText(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]:")
textbox.appendPlainText(f"{msg}\n")
def onMainUICloseSignal(self):
if self.receiveThread is not None:
self.receiveThread.running = False
self.receiveThread.quit()
if self.serverThread is not None:
self.serverThread.running = False
self.serverThread.quit()
class ServerThread(QThread):
clientLinkingSignal = Signal(socket.socket, tuple)
def __init__(self, soc, ip, com):
super().__init__()
self.soc = soc
self.soc.settimeout(1.0)
self.ip = ip
self.com = com
self.running = True
def run(self):
try:
self.soc.bind((self.ip, self.com))
self.soc.listen(1)
except Exception as e:
self.running = False
print(e)
try:
while self.running:
try:
clientSoc, clientAdd = self.soc.accept()
self.clientLinkingSignal.emit(clientSoc, clientAdd)
except socket.timeout:
continue
finally:
self.soc.close()
def stopServerThread(self):
self.running = False
self.wait()
class ReceiveThread(QThread):
updateMessageSignal = Signal(bytes) # 用于将接收到的消息传递给主线程
def __init__(self, soc):
super().__init__()
self.soc = soc
self.soc.settimeout(0.25)
self.running = True
def run(self):
try:
while self.running:
try:
byteArray = self.soc.recv(1024)
if not byteArray:
break
self.updateMessageSignal.emit(byteArray)
except socket.timeout:
continue
except Exception as e:
print(f"Error: {e}")
finally:
self.soc.close()
def stopReceiveThread(self):
self.running = False
self.wait()
def main():
app = QApplication([])
mainUI = MainUI()
mainUI.ui.show()
app.exec()
mainUI.mainUICloseSignal.emit()
if __name__ == '__main__':
main()
以上就是全部的内容了,祝愿看到这里的各位做自己的项目、课题也能一帆风顺!
513

被折叠的 条评论
为什么被折叠?



