一、小总结:
-
首先,在 《python socket编程5 - 最简单的命令行启动的tcp/udp server/client例子》 中实现了最简单的TCP/UDP server和client在命令行下可执行的代码。
-
然后,分别在《python socket编程6 - 使用PyQt6 开发UI界面实现TCP server和TCP client单机通讯的例子》和《python socket编程7 - 使用PyQt6 开发UI界面新增实现UDP server和client单机通讯的例子》中使用PyQt6做了一个界面,实现客户端和服务端TCP/UDP的单机通讯。
本篇在单机通讯的基础上进行重构,实现多线程TCP server与多个TCP client通讯的例子。
创建两个 PyQt6的项目,一个作为TCP server 项目,另一个作为TCP client项目。
二、TCP server部分
1、TCPServer 类定义中增加数组模拟线程池
2、消息发送修改为遍历线程池
3、停止服务修改为遍历线程池
4、TCP server完整代码
import socket
from PyQt6.QtCore import QThread, pyqtSignal
class TCPServer:
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 # socket
# self.socketThread = None # 新的 socket receive 线程
self.connectedThreadPool = [] # 模拟线程池,替代上面的 socket receive 线程
self.ui.statusbar.showMessage("服务已经启动,等待客户端的连接......")
self.start()
def start(self):
if not self.is_running:
self.is_running = True
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((self.ip, self.port)) # 绑定IP与端口
self.socket.listen(10) # 设定最大连接数
self.startSocketReceiveThread()
def stop(self):
try:
if self.is_running:
self.is_running = False
for connectThread in self.connectedThreadPool:
if connectThread.is_running:
connectThread.stop()
except Exception as e:
print(e)
def startSocketReceiveThread(self):
"""
启动一个新的监听线程,等待连接。
:return:
"""
socketThread = TCPServerSocketReceiveThread(self.socket)
self.connectedThreadPool.append(socketThread)
socketThread.clientConnection.connect(self.socket_client_connect_trigger)
socketThread.receivedClientData.connect(self.show_client_message)
socketThread.serverStatus.connect(self.server_status_trigger)
socketThread.start()
def server_status_trigger(self, status):
self.ui.statusbar.showMessage(status)
def socket_client_connect_trigger(self, state):
if state == 'connect':
self.startSocketReceiveThread()
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:
message = self.serverName + ':' + message
self.ui.textEdit.append(message)
for connectThread in self.connectedThreadPool:
if connectThread.is_connected:
connectThread.send_data_to_client(message)
class TCPServerSocketReceiveThread(QThread):
clientConnection: pyqtSignal = pyqtSignal(str) # 向主线程发送连接状态标志
receivedClientData: pyqtSignal = pyqtSignal(str) # 向主线程发送接受到客户端的数据
serverStatus: pyqtSignal = pyqtSignal(str) # 向主线程发送服务器状态
def __init__(self, serverSocket):
super(TCPServerSocketReceiveThread, self).__init__()
self.serverSocket = serverSocket
self.clientSocket = None
self.addr = None
self.clientIP = None
self.clientPort = None
self.is_running = True
self.is_connected = False
def run(self):
self.clientSocket, self.addr = self.serverSocket.accept() # 接受客户端的连接
self.is_connected = True
self.emitConnectEvent('connect') # 发送客户端连接成功通知到主界面
self.clientIP, self.clientPort = self.addr
self.serverStatus.emit("客户端【" + self.clientIP + "】已经连接。")
self.startReceiveData()
def startReceiveData(self):
while self.is_running:
try:
data = self.clientSocket.recv(1024).decode('utf-8') # 接受到字符串并按照utf-8编译
if not data:
self.emitConnectEvent('disconnect') # 发送客户端断开通知到主界面
break
print(data)
self.sendClientDataToUi(data)
except ConnectionResetError as reason:
self.sendClientDataToUi("已经离开对话。")
self.is_running = False
self.emitConnectEvent('disconnect') # 发送客户端断开通知到主界面
break
self.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 = False
def emitConnectEvent(self, state):
self.clientConnection.emit(state)
def sendClientDataToUi(self, message):
self.receivedClientData.emit(message)
三、TCP client 完整代码
import socket
from PyQt6.QtCore import QThread, pyqtSignal
class TCPClient:
def __init__(self, ui, ip, clientName, port):
self.ui = ui
self.ip = ip
self.hostName = clientName
self.port = port
self.socket = None
self.socketThread = None
self.connect_server()
def connect_server(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socketThread = TCPClientSocketReceiveThread(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.append(serverMessage)
def stop(self):
self.socketThread.stop()
def send_data(self, sentence):
sentence = self.hostName + ":" + sentence
self.ui.textEdit.append(sentence)
self.socket.send(sentence.encode())
def connect_success(self, ip, port):
try:
self.socket.connect((ip, port))
return True
except Exception as reason:
print(reason)
return False
class TCPClientSocketReceiveThread(QThread):
receivedServerData: pyqtSignal = pyqtSignal(str) # 向主线程发送接受到客户端的数据
def __init__(self, clientSocket):
super(TCPClientSocketReceiveThread, self).__init__()
self.clientSocket = clientSocket
self.is_running = True
def stop(self):
self.is_running = False
self.clientSocket.close()
def run(self):
while self.is_running:
try:
msg = self.clientSocket.recv(1024).decode("utf-8") # 接受服务端消息
if not msg:
break
self.receivedServerData.emit(msg)
except Exception as reason:
print(reason)
break
self.stop()
self.receivedServerData.emit("已经与服务端断开。")
四、截图
1、server端
2、第一个客户端
3、第二个客户端
可以单机多个客户端通讯,也可以在局域网测试。