當前位置:
首頁 > 知識 > PyQt5+ Python3 多線程通信

PyQt5+ Python3 多線程通信

關於PyQt5+Python3開發環境安裝可以參考上篇。這裡實現的是一種群聊工具,socket類使用的是Qt的TcpSocket, TcpServer 類;線程類使用的QTread;收發數據使用的QDataStream(當然也可以用其他的方式,不唯一)

先看伺服器端, 服務端界面設計,下面標註紅色的是後面代碼中要使用的控制項,其它的可要可不要 # Ui_server.py

# -*- coding: utf-8 -*-
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(541, 355)
self.splitter_2 = QtWidgets.QSplitter(Form)
self.splitter_2.setGeometry(QtCore.QRect(10, 10, 516, 331))
self.splitter_2.setOrientation(QtCore.Qt.Vertical)
self.splitter_2.setObjectName("splitter_2")
self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.layoutWidget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.txtPort = QtWidgets.QLineEdit(self.layoutWidget)
self.txtPort.setObjectName("txtPort")
self.horizontalLayout.addWidget(self.txtPort)
self.btnCreate = QtWidgets.QPushButton(self.layoutWidget)
self.btnCreate.setObjectName("btnCreate")
self.horizontalLayout.addWidget(self.btnCreate)
self.splitter = QtWidgets.QSplitter(self.splitter_2)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.layoutWidget1 = QtWidgets.QWidget(self.splitter)
self.layoutWidget1.setObjectName("layoutWidget1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.lbUserInfo = QtWidgets.QLabel(self.layoutWidget1)
self.lbUserInfo.setObjectName("lbUserInfo")
self.verticalLayout.addWidget(self.lbUserInfo)
self.listUser = QtWidgets.QListWidget(self.layoutWidget1)
self.listUser.setObjectName("listUser")
self.verticalLayout.addWidget(self.listUser)
self.layoutWidget2 = QtWidgets.QWidget(self.splitter)
self.layoutWidget2.setObjectName("layoutWidget2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget2)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.chatInfo = QtWidgets.QLabel(self.layoutWidget2)
self.chatInfo.setObjectName("chatInfo")
self.verticalLayout_2.addWidget(self.chatInfo)
self.browerChat = QtWidgets.QTextBrowser(self.layoutWidget2)
self.browerChat.setObjectName("browerChat")
self.verticalLayout_2.addWidget(self.browerChat)

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "ADDRESS:PORT:"))
self.btnCreate.setText(_translate("Form", "create"))
self.lbUserInfo.setText(_translate("Form", "用戶列表"))
self.chatInfo.setText(_translate("Form", "聊天信息"))

if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
self.btnCreate = QtWidgets.QPushButton(self.layoutWidget)
self.btnCreate.setObjectName("btnCreate")
self.horizontalLayout.addWidget(self.btnCreate)
self.splitter = QtWidgets.QSplitter(self.splitter_2)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.layoutWidget1 = QtWidgets.QWidget(self.splitter)
self.layoutWidget1.setObjectName("layoutWidget1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget1)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.lbUserInfo = QtWidgets.QLabel(self.layoutWidget1)
self.lbUserInfo.setObjectName("lbUserInfo")
self.verticalLayout.addWidget(self.lbUserInfo)
self.listUser = QtWidgets.QListWidget(self.layoutWidget1)
self.listUser.setObjectName("listUser")
self.verticalLayout.addWidget(self.listUser)
self.layoutWidget2 = QtWidgets.QWidget(self.splitter)
self.layoutWidget2.setObjectName("layoutWidget2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.layoutWidget2)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.chatInfo = QtWidgets.QLabel(self.layoutWidget2)
self.chatInfo.setObjectName("chatInfo")
self.verticalLayout_2.addWidget(self.chatInfo)
self.browerChat = QtWidgets.QTextBrowser(self.layoutWidget2)
self.browerChat.setObjectName("browerChat")
self.verticalLayout_2.addWidget(self.browerChat)

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "ADDRESS:PORT:"))
self.btnCreate.setText(_translate("Form", "create"))
self.lbUserInfo.setText(_translate("Form", "用戶列表"))
self.chatInfo.setText(_translate("Form", "聊天信息"))

if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())

伺服器端實現:

在伺服器端,使用QTcpServer類監聽接入客戶端,對於QTcpServer類,一旦有用戶接入,就調用

其incomingConnection函數 。要使用多線程的話,就要在incomingConnection中調用線程類QThread,生成新的線程,

然後再新的線程run函數中調用QTcpSocket實例,進行數據的收發,在這裡我也把QTcpSocket類重新實現了,也可以就在

重載的Thread實現QTcpSocket收發的代碼,代碼如下:

# -*- coding: utf-8 -*-

"""
Module implementing Server.
"""
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtNetwork import *
from Ui_server import Ui_Form
import sys

PORT =26711
SIZEOF_UINT16 = 2

class TcpSocket(QTcpSocket):
"""
實現了客戶端讀取信息與發送信息到客戶端的功能
"""
#信號用於Thread類中,信號在接受客戶端信息後發送,一直發送到Server更新其界面
#上級為Thead, 先將其交付給連接Thread中的slotRecv
signRecv= pyqtSignal(str, str)
def __init__(self, socketId, parent=None):
super(TcpSocket, self).__init__(parent)
self.socketId = socketId
#客戶端發送信息就接受
self.readyRead.connect(self.slotRecv)

#接受信息槽函數, client-> slotRecv -> Server
def slotRecv(self):
#使用QDataStream接受信息,也可以選擇其他接受方式
while self.state() == QAbstractSocket.ConnectedState:
nextBlockSize = 0
stream = QDataStream(self)
if self.bytesAvailable() >= SIZEOF_UINT16:
nextBlockSize = stream.readUInt16()
else:
#print("cannot read client")
break
if self.bytesAvailable() < nextBlockSize:
break
action = ""
msg = ""
action = stream.readQString()
msg = stream.readQString()
clientAddress = self.peerAddress().toString()
clientPort = str(self.peerPort())
msg= clientAddress+ ":"+clientPort + " "+ msg+"
"

#發射給上級Thread
self.signRecv.emit(action, msg)

#發送信息槽函數,其變數數據來源於上級 Server -> slotSend -> client
def slotSend(self, action, msg, id):
#print(id)
#print(int(self.socketId))
if id == int(self.socketId):
reply = QByteArray()
stream = QDataStream(reply, QIODevice.WriteOnly)
stream.writeUInt16(0)
stream.writeQString(action)
stream.writeQString(msg)
stream.device().seek(0)
stream.writeUInt16(reply.size() - SIZEOF_UINT16)
self.write(reply)

class Thread(QThread):
"""
線程類,在run中創建socket變數, 然後充當中介,向下交付slotSend中參數,
向上交付slotRecv中得到的參數
"""
#被TcpServer 使用,在類中發射
signRecv = pyqtSignal(str, str)
#在類中使用, 連接socket類中的slotSend
signSend = pyqtSignal(str, str, int)

def __init__(self, socketId, parent):
super(Thread, self).__init__(parent)
self.socketId = socketId

def run(self):
socket = TcpSocket(self.socketId)
if not socket.setSocketDescriptor(self.socketId):
return
#socket類中的signRecv信號在此連接,把socket接受到的信息作為參數傳遞給Thread的slotSend
socket.signRecv.connect(self.slotRecv)
#Thread類中的signSend連接socket中slotSend, 將從上級得到的信息傳遞給socket,最後發送到客戶端
self.signSend.connect(socket.slotSend)

#循環,不加這一句會出問題
self.exec_()

def slotSend(self, action, msg, id):
if action == "":
return
#發射到socket中
self.signSend.emit(action, msg, id)

def slotRecv(self, action, msg):
#發射到TcpServer
self.signRecv.emit(action, msg)

class TcpServer(QTcpServer):
signRecv= pyqtSignal(str, str)
signSend = pyqtSignal(str, str, int)
#存放客戶端socket的socketId
socketList = []
def __init__(self, parent=None):
super(TcpServer, self).__init__(parent)

def incomingConnection(self, socketId):
#一有客戶端進來就加入列表,當然在此要判斷列表是否存在該客戶端
if socketId not in self.socketList:
self.socketList.append(socketId)

#創建線程
thread = Thread(socketId, self)
#thread發射後,調用tcpserver的slotRecv ,然後其又發射自身的signRecv信號,該信號在Server中連接Server的slotRecv
thread.signRecv.connect(self.slotRecv)
#連接thread的slotSend
self.signSend.connect(thread.slotSend)
thread.finished.connect(thread.deleteLater)
thread.start()

def slotRecv(self, action, msg):
self.signRecv.emit(action, msg)

class Server(QWidget, Ui_Form):
"""
Class documentation goes here.
"""
signSend = pyqtSignal(str, str, int)
def __init__(self, parent=None):
"""
Constructor

@param parent reference to the parent widget
@type QWidget
"""
super(Server, self).__init__(parent)
self.setupUi(self)

self.server= TcpServer(self)
self.server.listen(QHostAddress("0.0.0.0"), PORT)
#tcoServer中的signRecv信號,連接自身的slotRecv
self.server.signRecv.connect(self.slotRecv)

def slotRecv(self, action, msg):
self.updateServer(action, msg)
for id in self.server.socketList:
self.server.signSend.emit(action, msg, id)

def showConnection(self):
print(self.server.socketList)

#def slotSend(self, action, msg):

def updateServer(self, action, msg):
if action == "USER":
self.listUser.addItem("*user*: "+ msg)
if action == "MSG":
self.browerChat.append(msg)

@pyqtSlot()
def on_btnCreate_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet

if __name__ == "__main__":
app = QApplication(sys.argv)
serverDlg = Server()
serverDlg.show()
app.exec_()

客戶端實現, 客戶端類似伺服器端,只不過多了一個發送消息的LineEdit, 取名為txtInput, 發送按鈕btnSend

代碼(Ui_client.py):

# -*- coding: utf-8 -*-

#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(455, 366)
self.listUser = QtWidgets.QListWidget(Form)
self.listUser.setGeometry(QtCore.QRect(0, 10, 161, 341))
self.listUser.setObjectName("listUser")
self.splitter_2 = QtWidgets.QSplitter(Form)
self.splitter_2.setGeometry(QtCore.QRect(180, 10, 256, 341))
self.splitter_2.setOrientation(QtCore.Qt.Vertical)
self.splitter_2.setObjectName("splitter_2")
self.setNameWidget = QtWidgets.QWidget(self.splitter_2)
self.setNameWidget.setObjectName("setNameWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.setNameWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.lbName = QtWidgets.QLabel(self.setNameWidget)
self.lbName.setObjectName("lbName")
self.horizontalLayout_3.addWidget(self.lbName)
self.txtName = QtWidgets.QLineEdit(self.setNameWidget)
self.txtName.setObjectName("txtName")
self.horizontalLayout_3.addWidget(self.txtName)
self.btnSet = QtWidgets.QPushButton(self.setNameWidget)
self.btnSet.setObjectName("btnSet")
self.horizontalLayout_3.addWidget(self.btnSet)
self.horizontalLayout.addLayout(self.horizontalLayout_3)
self.browerChat = QtWidgets.QTextBrowser(self.splitter_2)
self.browerChat.setObjectName("browerChat")
self.layoutWidget = QtWidgets.QWidget(self.splitter_2)
self.layoutWidget.setObjectName("layoutWidget")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget)
self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.txtInput = QtWidgets.QLineEdit(self.layoutWidget)
self.txtInput.setObjectName("txtInput")
self.horizontalLayout_2.addWidget(self.txtInput)
self.btnSend = QtWidgets.QPushButton(self.layoutWidget)
self.btnSend.setObjectName("btnSend")
self.horizontalLayout_2.addWidget(self.btnSend)

self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)

def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.lbName.setText(_translate("Form", "name"))
self.txtName.setText(_translate("Form", "deafault"))
self.btnSet.setText(_translate("Form", "SetConnection"))
self.btnSend.setText(_translate("Form", "Send"))

if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())

client.py 代碼:

# -*- coding: utf-8 -*-

"""
Module implementing Client.
"""

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtNetwork import *

from Ui_client import Ui_Form
import sys
import time

PORT = 26711
SIZEOF_UINT16 = 2

class Client(QWidget, Ui_Form):
"""
Class documentation goes here.
"""
def __init__(self, parent=None):
"""
Constructor

@param parent reference to the parent widget
@type QWidget
"""
super(Client, self).__init__(parent)
self.setupUi(self)

self.socket = QTcpSocket()

self.request = None
#self.nextBlockSize = 0
self.name = ""

#信號
#self.socket.connected.connected.connect(self.sendRequest)
self.socket.readyRead.connect(self.readResponse)
self.socket.disconnected.connect(self.serverHasStopped)
self.socket.error.connect(self.serverHasError)

self.socket.connectToHost("localhost", PORT)

@pyqtSlot()
def on_btnSend_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet

msg =self.name +" "+
time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time()))+"
"+
self.txtInput.text()
self.issueRequest("MSG", msg)
self.txtInput.setText("")

@pyqtSlot()
def on_txtInput_returnPressed(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
pass

@pyqtSlot()
def on_btnSet_clicked(self):
"""
Slot documentation goes here.
"""
# TODO: not implemented yet
self.name = self.txtName.text()
self.issueRequest("USER", self.name)

def issueRequest(self, action, msg):
print("sendRequest")
self.request= QByteArray()
stream = QDataStream(self.request, QIODevice.WriteOnly)
stream.writeUInt16(0)
stream.writeQString(action)
stream.writeQString(msg)
stream.device().seek(0)
stream.writeUInt16(self.request.size() - SIZEOF_UINT16)#overwrite seek(0)

self.socket.write(self.request)
self.nextBlockSize = 0
self.requst = None

def readResponse(self):
nextBlockSize = 0
stream = QDataStream(self.socket)
while 1:
if nextBlockSize == 0 :
if self.socket.bytesAvailable() <SIZEOF_UINT16:
break
nextBlockSize = stream.readUInt16()
if self.socket.bytesAvailable() < nextBlockSize:
break
action = ""
msg = ""
action = stream.readQString()
msg = stream.readQString()
print(msg)
if action == "MSG":
self.browerChat.append(msg)
if action == "USER":
self.listUser.addItem("*user*: "+ msg)

def serverHasStopped(self):
#self.issueRequest("CLOSE", self.name)
#self.socket.close()
print("socket is close")

def serverHasError(self, error):

self.socket.close()

if __name__ == "__main__":
app = QApplication(sys.argv)
dlg = Client()
dlg.show()
sys.exit(app.exec_())

運行結果如下:

PyQt5+ Python3 多線程通信

之前的設計中經常出現Cannot create children for a parent that is in a different thread

Socket notifiers cannot be enabled or disabled from another thread等問題

但是以上代碼是都解決了這些問題,當然還有很多不足之處,比如說用戶列表那一塊,懶得搞了,以後再說吧

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

NoSQL——MongoDB副本集和文檔管理
如何玩轉微服務

TAG:程序員小新人學習 |