背景介紹 在程式中,主線程啟動一個子線程進行非同步計算,主線程是不阻塞繼續執行的,這點看起來是非常自然的,都已經選擇啟動子線程去非同步執行了,主線程如果是阻塞的話,那還不如主線程自己去執行不就好了。那會不會有一種場景,非同步線程執行的結果主線程是需要使用的,或者說主線程先做一些工作,然後需要確認子線程執行 ...
在這個周末剛剛寫出來的python桌面應用--網路聊天室,主要通過pyqt5作為桌面應用框架,socket作為網路編程的框架,從而實現包括客戶端和服務端的網路聊天室的GUI應用,希望可以一起學習、一起進步!
應用包括服務端server_ui.py、客戶端client_ui.py兩個python模塊實現,並且在pyqt5的使用過程中都使用QThread多線程應用以及基本的UI頁面佈局。開始之前通過一個動態圖來觀察一下socket服務端、socket客戶端通信的實現效果。
- socket_ui.py 服務端
1-1. 依賴引用
在socket服務端的實現過程中,除了pyqt5相關的UI界面的引用外,還包括time、threading、sys、socket等輔助模塊來一起實現socket服務端的桌面應用程式。
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from QCandyUi import CandyWindow
# 導入 socket 通訊模塊
import socket
# 導入時間管理模塊
import time
# 導入多線程模塊
import threading
1-2. 實現過程
在服務端的業務實現上面,我們依然是按照之前的GUI實現方式,採用主線程用來實現頁面佈局,子線程QThread來實現業務邏輯的方式來進行實現的,socket的服務端通信業務都是在子線程ServerThread中編寫的。下麵是socket服務端桌面應用實現的全部代碼塊,copy到自己的ide中即可直接啟動使用。
class ServerUI(QWidget):
def __init__(self):
super(ServerUI, self).__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle('socket 服務端 公眾號:[Python 集中營]')
self.setWindowIcon(QIcon('hi.ico'))
self.setFixedSize(500, 300)
hbox = QHBoxLayout()
hbox_v1 = QVBoxLayout()
self.brower = QTextBrowser()
self.brower.setFont(QFont('宋體', 8))
self.brower.setReadOnly(True)
self.brower.setPlaceholderText('消息展示區域...')
self.brower.ensureCursorVisible()
hbox_v1.addWidget(self.brower)
hbox_v2 = QVBoxLayout()
hbox_v2_f1 = QFormLayout()
self.ip_label = QLabel()
self.ip_label.setText('ip地址 ')
self.ip_txt = QLineEdit()
self.ip_txt.setPlaceholderText('0.0.0.0')
self.port_label = QLabel()
self.port_label.setText('埠 ')
self.port_txt = QLineEdit()
self.port_txt.setPlaceholderText('4444')
self.lis_num_label = QLabel()
self.lis_num_label.setText('最大監聽個數 ')
self.lis_num_txt = QLineEdit()
self.lis_num_txt.setPlaceholderText('10')
self.close_cli_label = QLabel()
self.close_cli_label.setText('客戶端關閉指令 ')
self.close_cli_txt = QLineEdit()
self.close_cli_txt.setPlaceholderText('exit,客戶端發送相應指令則關閉')
hbox_v2_f1.addRow(self.ip_label, self.ip_txt)
hbox_v2_f1.addRow(self.port_label, self.port_txt)
hbox_v2_f1.addRow(self.lis_num_label, self.lis_num_txt)
hbox_v2_f1.addRow(self.close_cli_label, self.close_cli_txt)
self.start_btn = QPushButton()
self.start_btn.setText('開啟服務端')
self.start_btn.clicked.connect(self.start_btn_clk)
hbox_v2.addLayout(hbox_v2_f1)
hbox_v2.addWidget(self.start_btn)
hbox.addLayout(hbox_v1)
hbox.addLayout(hbox_v2)
self.thread_ = ServerThread(self)
self.thread_.message.connect(self.show_message)
self.setLayout(hbox)
def show_message(self, text):
'''
槽函數:向文本瀏覽器中寫入內容
:param text:
:return:
'''
cursor = self.brower.textCursor()
cursor.movePosition(QTextCursor.End)
self.brower.append(text)
self.brower.setTextCursor(cursor)
self.brower.ensureCursorVisible()
def start_btn_clk(self):
self.thread_.start()
self.start_btn.setEnabled(False)
class ServerThread(QThread):
message = pyqtSignal(str)
def __init__(self, parent=None):
super(ServerThread, self).__init__(parent)
self.parent = parent
self.working = True
def __del__(self):
self.working = False
self.wait()
def run(self):
self.message.emit('準備啟動socket服務端...')
# 創建服務端 socket
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定服務地址、埠
address = (self.parent.ip_txt.text().strip(), int(self.parent.port_txt.text().strip()))
socket_server.bind(address)
# 設置監聽最大等待數
socket_server.listen(int(self.parent.lis_num_txt.text().strip()))
self.message.emit("服務已經啟動,正在等待客戶端連接...")
while True:
# 設置睡眠時間
time.sleep(0.1)
# 允許客戶端連接
client, info = socket_server.accept()
self.client, self.info = client, info
# 啟用新線程調用消息處理
thread = threading.Thread(target=self.catch_message)
# 設置為守護線程
thread.setDaemon(True)
# 開啟線程執行
thread.start()
def catch_message(self):
self.client.send("歡迎來到網路聊天室".encode('utf-8'))
self.message.emit("客戶端信息:" + str(self.info))
close_cli = self.parent.close_cli_txt.text().strip()
while True:
try:
# 接收客戶端消息、接收最大長度為 1024,併進行 utf-8 解碼
message = self.client.recv(1024).decode('utf-8')
# 校驗是否關閉客戶端
if not message and close_cli == message:
self.client.close()
self.message.emit("當前客戶端已關閉!")
break
self.message.emit("接收到消息:" + message)
# 將消息進行 utf-8 編碼後發給客戶端
rcv = "服務端成功接收消息:" + message
self.client.send(rcv.encode('utf-8'))
except Exception as e:
self.client.send("服務端處理消息異常!".encode('utf-8'))
break
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CandyWindow.createWindow(ServerUI(), theme='blueGreen', title='socket 服務端 公眾號:[Python 集中營]',
ico_path='hi.ico')
w.show()
sys.exit(app.exec_())
1-3. 實現效果
- client_ui.py 客戶端
2-1. 依賴引用
在socket客戶端的實現過程中,除了pyqt5相關的UI界面的引用外,還包括sys、socket等輔助模塊來一起實現socket服務端的桌面應用程式,相比服務端來說,客戶端並沒有使用多線程threading模塊。
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from QCandyUi import CandyWindow
# 導入socket 通信模塊
import socket
2-2. 實現過程
客戶端的實現過程和服務端server_ui.py實現是基本相似的,同樣也使用到了pyqt5的QThread的子線程應用,唯一不同的是socket客戶端通信方式跟服務端不大相同,同樣將下麵的代碼塊copy到自己的ide中直接使用即可。
class ClientUI(QWidget):
def __init__(self):
super(ClientUI, self).__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle('socket 客戶端 公眾號:[Python 集中營]')
self.setWindowIcon(QIcon('hi.ico'))
self.setFixedSize(500, 300)
hbox = QHBoxLayout()
hbox_v1 = QVBoxLayout()
self.brower = QTextBrowser()
self.brower.setFont(QFont('宋體', 8))
self.brower.setReadOnly(True)
self.brower.setPlaceholderText('消息展示區域...')
self.brower.ensureCursorVisible()
hbox_v1.addWidget(self.brower)
hbox_v2 = QVBoxLayout()
hbox_v2_g1 = QGridLayout()
self.ip_label = QLabel()
self.ip_label.setText('ip地址 ')
self.ip_txt = QLineEdit()
self.ip_txt.setPlaceholderText('0.0.0.0')
self.port_label = QLabel()
self.port_label.setText('埠 ')
self.port_txt = QLineEdit()
self.port_txt.setPlaceholderText('4444')
self.message = QTextEdit()
self.message.setPlaceholderText('發送消息內容...')
hbox_v2_g1.addWidget(self.ip_label, 0, 0, 1, 1)
hbox_v2_g1.addWidget(self.ip_txt, 0, 1, 1, 1)
hbox_v2_g1.addWidget(self.port_label, 1, 0, 1, 1)
hbox_v2_g1.addWidget(self.port_txt, 1, 1, 1, 1)
hbox_v2_g1.addWidget(self.message, 2, 0, 1, 2)
self.start_btn = QPushButton()
self.start_btn.setText('發送消息')
self.start_btn.clicked.connect(self.start_btn_clk)
hbox_v2.addLayout(hbox_v2_g1)
hbox_v2.addWidget(self.start_btn)
hbox.addLayout(hbox_v1)
hbox.addLayout(hbox_v2)
self.thread_ = ClientThread(self)
self.thread_.message.connect(self.show_message)
self.setLayout(hbox)
def show_message(self, text):
'''
槽函數:向文本瀏覽器中寫入內容
:param text:
:return:
'''
cursor = self.brower.textCursor()
cursor.movePosition(QTextCursor.End)
self.brower.append(text)
self.brower.setTextCursor(cursor)
self.brower.ensureCursorVisible()
def start_btn_clk(self):
self.thread_.start()
class ClientThread(QThread):
message = pyqtSignal(str)
def __init__(self, parent=None):
super(ClientThread, self).__init__(parent)
self.parent = parent
self.working = True
self.is_connect = False
def __del__(self):
self.working = False
self.wait()
def run(self):
try:
if self.is_connect is False:
self.connect_serv()
# 將控制台輸入消息進行 utf-8 編碼後發送
self.socket_client.send(self.parent.message.toPlainText().strip().encode('utf-8'))
self.message.emit(self.socket_client.recv(1024).decode('utf-8'))
except Exception as e:
self.message.emit("發送消息異常:" + str(e))
def connect_serv(self):
try:
self.message.emit("正在創建客戶端socket...")
# 創建客戶端 socket
self.socket_client = socket.socket()
# 連接服務端
address = (self.parent.ip_txt.text().strip(), int(self.parent.port_txt.text().strip()))
self.socket_client.connect(address)
self.message.emit("服務端連接成功...")
# 接收服務端消息併進行 utf-8 解碼
self.message.emit(self.socket_client.recv(1024).decode())
self.is_connect = True
except:
self.is_connect = False
if __name__ == '__main__':
app = QApplication(sys.argv)
w = CandyWindow.createWindow(ClientUI(), theme='blueGreen', title='socket 客戶端 公眾號:[Python 集中營]',
ico_path='hi.ico')
w.show()
sys.exit(app.exec_())
2-3. 實現效果
【往期精彩】
零配置python日誌,安裝即用!
英語沒學好到底能不能做coder,別再糾結了先學起來...
數據清洗工具flashtext,效率直接提升了幾十倍數!
一個help函數解決了python的所有文檔信息查看...
python 自定義異常/raise關鍵字拋出異常
歡迎關註作者公眾號【Python 集中營】,專註於後端編程,每天更新技術乾貨,不定時分享各類資料!