使用PySide6建立单连接的TCP网络通信软件

部署运行你感兴趣的模型镜像

        本人最近在做自己课题的时候用到了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()

        以上就是全部的内容了,祝愿看到这里的各位做自己的项目、课题也能一帆风顺!

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值