使用PyQt6 开发UI界面实现TCP server和TCP client单机通讯的示例。
一、PyQt6 实现的界面
二、TCP server代码的修改示意
界面提供网络参数的配置,以及提供人机交互过程中的数据获取和显示。
1、把上面的server代码封装成两个部分
A、class Server 负责接受UI界面的参数并通过信号与后台线程通讯
B、class ServerSocketReceiveThread 封装了 server socket 接收数据的行为并提供信号与Server交换数据
如果在UI界面调用while语句接收socket数据,会导致界面卡死。
所以使用新线程运行socket接收数据的操作,通过信号传递给Server中定义的方法,实现数据传递。
下图是原来的代码与修改后的代码部分映射的关系:
2、Server的完整代码
class Server:def __init__(self, ui, server_ip, server_hostname, server_port):self.ui = ui # 主界面self.ip = server_ip # 服务器ip地址self.port = server_port # 服务器端口号self.serverName = server_hostname # 显示名称self.is_running = False # 是否已经启动self.socket = None # socketself.socketThread = None # 新的 socket receive 线程self.start()def start(self):if not self.is_running:self.is_running = Trueself.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.socket.bind((self.ip, self.port)) # 绑定IP与端口self.socket.listen(1) # 设定最大连接数self.startSocketReceiveThread()def stop(self):try:if self.is_running:self.is_running = Falseif self.socketThread.is_running:self.socketThread.stop()except Exception as e:print(e)def startSocketReceiveThread(self):self.socketThread = ServerSocketReceiveThread(self.socket)self.socketThread.clientConnection.connect(self.socket_client_connect_trigger)self.socketThread.receivedClientData.connect(self.show_client_message)self.socketThread.serverStatus.connect(self.server_status_trigger)self.socketThread.start()def server_status_trigger(self, status):self.ui.statusbar.showMessage(status)def socket_client_connect_trigger(self, state):if state == 'connect':self.ui.statusbar.showMessage("客户端已经连接。")else:self.ui.statusbar.showMessage("客户端已经断开。")def show_client_message(self, message):self.ui.textEdit.append('客户端:' + message)def send_message_to_client(self, message):if self.is_running:self.ui.textEdit.append(self.serverName + ':' + message)self.socketThread.send_data_to_client(message)
3、ServerSocketReceiveThread 完整代码
class ServerSocketReceiveThread(QThread):clientConnection: pyqtSignal = pyqtSignal(str) # 向主线程发送连接状态标志receivedClientData: pyqtSignal = pyqtSignal(str) # 向主线程发送接受到客户端的数据serverStatus: pyqtSignal = pyqtSignal(str) # 向主线程发送服务器状态def __init__(self, serverSocket):super(ServerSocketReceiveThread, self).__init__()self.serverSocket = serverSocketself.clientSocket = Noneself.addr = Noneself.is_running = Truedef run(self):self.serverStatus.emit("服务已经启动,等待客户端的连接......")self.clientSocket, self.addr = self.serverSocket.accept() # 接受客户端的连接self.emitConnectEvent('connect') # 发送通知到主界面self.startReceiveData()def startReceiveData(self):while self.is_running:try:data = self.clientSocket.recv(1024).decode('utf-8') if not data:self.emitConnectEvent('disconnect') # 发送通知到主界面breakself.sendClientDataToUi(data)except ConnectionResetError as reason:self.sendClientDataToUi("已经离开对话。")self.is_running = Falseself.emitConnectEvent('disconnect') # 发送通知到主界面breakself.clientSocket.close()self.serverSocket.close()self.serverStatus.emit("服务已经关闭。")def send_data_to_client(self, message):try:self.clientSocket.send(message.encode("utf-8"))except Exception as reason:print("发送失败,原因 = ", reason)def stop(self):if self.is_running:self.is_running = Falsedef emitConnectEvent(self, state):self.clientConnection.emit(state)def sendClientDataToUi(self, message):self.receivedClientData.emit(message)
三、TCP Client 代码也是同样封装为两部分
1、class Client完整代码
class Client:def __init__(self, ui, ip, clientName, port):self.ui = uiself.ip = ipself.hostName = clientNameself.port = portself.socket = Noneself.socketThread = Noneself.connect_server()def connect_server(self):self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.socketThread = ClientSocketReceiveThread(self.socket)self.socketThread.receivedServerData.connect(self.update_ui_chat_content)if self.connect_success(self.ip, self.port):self.socketThread.start()def update_ui_chat_content(self, serverMessage):self.ui.textEdit_2.append("服务端:" + serverMessage)def stop(self):self.socketThread.stop()def send_data(self, sentence):self.ui.textEdit_2.append(self.hostName + ":" + sentence)self.socket.send(sentence.encode())def connect_success(self, ip, port):try:self.socket.connect((ip, port))return Trueexcept Exception as reason:print(reason)return False
2、class ClientSocketReceiveThread完整代码
class ClientSocketReceiveThread(QThread):receivedServerData: pyqtSignal = pyqtSignal(str) # 向主线程发送接受到客户端的数据def __init__(self, clientSocket):super(ClientSocketReceiveThread, self).__init__()self.clientSocket = clientSocketself.is_running = Truedef stop(self):self.is_running = Falseself.clientSocket.close()def run(self):while self.is_running:try:msg = self.clientSocket.recv(1024).decode("utf-8") # 接受服务端消息if not msg:breakself.receivedServerData.emit(msg)except Exception as reason:print(reason)breakself.stop()self.receivedServerData.emit("已经与服务端断开。")
四、ui_Main.py代码
class Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(957, 600)self.centralwidget = QtWidgets.QWidget(parent=MainWindow)self.centralwidget.setObjectName("centralwidget")self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)self.verticalLayout.setObjectName("verticalLayout")self.tabWidget = QtWidgets.QTabWidget(parent=self.centralwidget)self.tabWidget.setObjectName("tabWidget")self.tab = QtWidgets.QWidget()self.tab.setObjectName("tab")self.horizontalLayout_10 = QtWidgets.QHBoxLayout(self.tab)self.horizontalLayout_10.setObjectName("horizontalLayout_10")self.verticalLayout_5 = QtWidgets.QVBoxLayout()self.verticalLayout_5.setObjectName("verticalLayout_5")self.groupBox = QtWidgets.QGroupBox(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.groupBox.setFont(font)self.groupBox.setObjectName("groupBox")self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox)self.verticalLayout_2.setObjectName("verticalLayout_2")self.horizontalLayout_2 = QtWidgets.QHBoxLayout()self.horizontalLayout_2.setObjectName("horizontalLayout_2")self.label = QtWidgets.QLabel(parent=self.groupBox)self.label.setObjectName("label")self.horizontalLayout_2.addWidget(self.label)self.lineEdit = QtWidgets.QLineEdit(parent=self.groupBox)self.lineEdit.setObjectName("lineEdit")self.horizontalLayout_2.addWidget(self.lineEdit)self.pushButton_3 = QtWidgets.QPushButton(parent=self.groupBox)self.pushButton_3.setObjectName("pushButton_3")self.horizontalLayout_2.addWidget(self.pushButton_3)self.verticalLayout_2.addLayout(self.horizontalLayout_2)self.horizontalLayout_3 = QtWidgets.QHBoxLayout()self.horizontalLayout_3.setObjectName("horizontalLayout_3")self.label_2 = QtWidgets.QLabel(parent=self.groupBox)self.label_2.setObjectName("label_2")self.horizontalLayout_3.addWidget(self.label_2)self.lineEdit_2 = QtWidgets.QLineEdit(parent=self.groupBox)self.lineEdit_2.setObjectName("lineEdit_2")self.horizontalLayout_3.addWidget(self.lineEdit_2)spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout_3.addItem(spacerItem)self.horizontalLayout_3.setStretch(2, 1)self.verticalLayout_2.addLayout(self.horizontalLayout_3)self.horizontalLayout_4 = QtWidgets.QHBoxLayout()self.horizontalLayout_4.setObjectName("horizontalLayout_4")self.verticalLayout_2.addLayout(self.horizontalLayout_4)self.verticalLayout_5.addWidget(self.groupBox)self.verticalLayout_4 = QtWidgets.QVBoxLayout()self.verticalLayout_4.setObjectName("verticalLayout_4")self.horizontalLayout = QtWidgets.QHBoxLayout()self.horizontalLayout.setObjectName("horizontalLayout")spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout.addItem(spacerItem1)self.pushButton = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton.setFont(font)self.pushButton.setObjectName("pushButton")self.horizontalLayout.addWidget(self.pushButton)spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout.addItem(spacerItem2)self.pushButton_2 = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton_2.setFont(font)self.pushButton_2.setObjectName("pushButton_2")self.horizontalLayout.addWidget(self.pushButton_2)spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout.addItem(spacerItem3)self.verticalLayout_4.addLayout(self.horizontalLayout)self.verticalLayout_3 = QtWidgets.QVBoxLayout()self.verticalLayout_3.setObjectName("verticalLayout_3")self.textEdit = QtWidgets.QTextEdit(parent=self.tab)self.textEdit.setObjectName("textEdit")self.verticalLayout_3.addWidget(self.textEdit)self.horizontalLayout_5 = QtWidgets.QHBoxLayout()self.horizontalLayout_5.setObjectName("horizontalLayout_5")self.lineEdit_4 = QtWidgets.QLineEdit(parent=self.tab)self.lineEdit_4.setObjectName("lineEdit_4")self.horizontalLayout_5.addWidget(self.lineEdit_4)self.pushButton_4 = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton_4.setFont(font)self.pushButton_4.setObjectName("pushButton_4")self.horizontalLayout_5.addWidget(self.pushButton_4)self.verticalLayout_3.addLayout(self.horizontalLayout_5)self.verticalLayout_4.addLayout(self.verticalLayout_3)self.verticalLayout_5.addLayout(self.verticalLayout_4)self.horizontalLayout_10.addLayout(self.verticalLayout_5)self.verticalLayout_8 = QtWidgets.QVBoxLayout()self.verticalLayout_8.setObjectName("verticalLayout_8")self.groupBox_2 = QtWidgets.QGroupBox(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.groupBox_2.setFont(font)self.groupBox_2.setObjectName("groupBox_2")self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.groupBox_2)self.verticalLayout_9.setObjectName("verticalLayout_9")self.horizontalLayout_6 = QtWidgets.QHBoxLayout()self.horizontalLayout_6.setObjectName("horizontalLayout_6")self.label_4 = QtWidgets.QLabel(parent=self.groupBox_2)self.label_4.setObjectName("label_4")self.horizontalLayout_6.addWidget(self.label_4)self.lineEdit_5 = QtWidgets.QLineEdit(parent=self.groupBox_2)self.lineEdit_5.setObjectName("lineEdit_5")self.horizontalLayout_6.addWidget(self.lineEdit_5)self.verticalLayout_9.addLayout(self.horizontalLayout_6)self.horizontalLayout_7 = QtWidgets.QHBoxLayout()self.horizontalLayout_7.setObjectName("horizontalLayout_7")self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2)self.label_5.setObjectName("label_5")self.horizontalLayout_7.addWidget(self.label_5)self.lineEdit_6 = QtWidgets.QLineEdit(parent=self.groupBox_2)self.lineEdit_6.setObjectName("lineEdit_6")self.horizontalLayout_7.addWidget(self.lineEdit_6)self.verticalLayout_9.addLayout(self.horizontalLayout_7)self.verticalLayout_8.addWidget(self.groupBox_2)self.verticalLayout_7 = QtWidgets.QVBoxLayout()self.verticalLayout_7.setObjectName("verticalLayout_7")self.horizontalLayout_8 = QtWidgets.QHBoxLayout()self.horizontalLayout_8.setObjectName("horizontalLayout_8")spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout_8.addItem(spacerItem4)self.pushButton_5 = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton_5.setFont(font)self.pushButton_5.setObjectName("pushButton_5")self.horizontalLayout_8.addWidget(self.pushButton_5)spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout_8.addItem(spacerItem5)self.pushButton_6 = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton_6.setFont(font)self.pushButton_6.setObjectName("pushButton_6")self.horizontalLayout_8.addWidget(self.pushButton_6)spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)self.horizontalLayout_8.addItem(spacerItem6)self.verticalLayout_7.addLayout(self.horizontalLayout_8)self.verticalLayout_6 = QtWidgets.QVBoxLayout()self.verticalLayout_6.setObjectName("verticalLayout_6")self.textEdit_2 = QtWidgets.QTextEdit(parent=self.tab)self.textEdit_2.setObjectName("textEdit_2")self.verticalLayout_6.addWidget(self.textEdit_2)self.horizontalLayout_9 = QtWidgets.QHBoxLayout()self.horizontalLayout_9.setObjectName("horizontalLayout_9")self.lineEdit_7 = QtWidgets.QLineEdit(parent=self.tab)self.lineEdit_7.setObjectName("lineEdit_7")self.horizontalLayout_9.addWidget(self.lineEdit_7)self.pushButton_7 = QtWidgets.QPushButton(parent=self.tab)font = QtGui.QFont()font.setPointSize(11)self.pushButton_7.setFont(font)self.pushButton_7.setObjectName("pushButton_7")self.horizontalLayout_9.addWidget(self.pushButton_7)self.verticalLayout_6.addLayout(self.horizontalLayout_9)self.verticalLayout_7.addLayout(self.verticalLayout_6)self.verticalLayout_8.addLayout(self.verticalLayout_7)self.horizontalLayout_10.addLayout(self.verticalLayout_8)self.tabWidget.addTab(self.tab, "")self.tab_2 = QtWidgets.QWidget()self.tab_2.setObjectName("tab_2")self.tabWidget.addTab(self.tab_2, "")self.verticalLayout.addWidget(self.tabWidget)MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(parent=MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 957, 22))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)font = QtGui.QFont()font.setPointSize(11)self.statusbar.setFont(font)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)self.tabWidget.setCurrentIndex(0)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))self.groupBox.setTitle(_translate("MainWindow", "服务器配置:"))self.label.setText(_translate("MainWindow", "服务端IP地址:"))self.lineEdit.setText(_translate("MainWindow", "127.0.0.1"))self.pushButton_3.setText(_translate("MainWindow", "使用本机IP地址"))self.label_2.setText(_translate("MainWindow", "服务器端口:"))self.lineEdit_2.setText(_translate("MainWindow", "12000"))self.pushButton.setText(_translate("MainWindow", "启动服务"))self.pushButton_2.setText(_translate("MainWindow", "停止服务"))self.pushButton_4.setText(_translate("MainWindow", "发送"))self.groupBox_2.setTitle(_translate("MainWindow", "客户端配置"))self.label_4.setText(_translate("MainWindow", "服务器IP:"))self.lineEdit_5.setText(_translate("MainWindow", "127.0.0.1"))self.label_5.setText(_translate("MainWindow", "服务器端口:"))self.lineEdit_6.setText(_translate("MainWindow", "12000"))self.pushButton_5.setText(_translate("MainWindow", "连接服务器"))self.pushButton_6.setText(_translate("MainWindow", "断开连接"))self.pushButton_7.setText(_translate("MainWindow", "发送"))self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "TCP协议"))self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "UDP协议"))
五、application.py 启动代码
import os
import sys
from module.main import MainWindow
from PyQt6.QtWidgets import QApplicationif __name__ == '__main__':app = QApplication(sys.argv)login = MainWindow()sys.exit(app.exec())
六、main.py 代码
from PyQt6 import QtWidgets
from .server import Server
from .client import Client
from ui.ui_Main import Ui_MainWindowclass MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):"""主窗口初始化"""def __init__(self):super(MainWindow, self).__init__()self.setupUi(self)self.show()self.pushButton.clicked.connect(self.server_start)self.pushButton_2.clicked.connect(self.server_stop)self.pushButton_4.clicked.connect(self.server_send_data)self.pushButton_5.clicked.connect(self.client_connect_server)self.pushButton_6.clicked.connect(self.client_disconnect_server)self.pushButton_7.clicked.connect(self.client_send_data)self.server = Noneself.client = Nonedef client_connect_server(self):server_ip = self.lineEdit_5.text()server_port = int(self.lineEdit_6.text())client_name = '客户端'self.client = Client(self, server_ip, client_name, server_port)def client_disconnect_server(self):self.client.stop()def client_send_data(self):message = self.lineEdit_7.text()self.client.send_data(message)def server_start(self):server_ip = self.lineEdit.text()server_port = int(self.lineEdit_2.text())server_name = '服务器'self.server = Server(self, server_ip, server_name, server_port)def server_stop(self):self.server.stop()def server_send_data(self):message = self.lineEdit_4.text()self.server.send_message_to_client(message)