本篇实现 UDP server和client多客户端通讯的例子。
在UDP单机通讯的基础上进行重构,实现UDP server与多个 client通讯的例子。
创建两个 PyQt6的项目,一个作为UDP server 项目,另一个作为UDP client项目。
一、效果图
1、udp server界面
2、第一个客户端
3、第二个客户端
二、server 项目代码
1、启动代码
# -*- coding: utf-8 -*-
import sys
from module.main import MainWindow
from PyQt6.QtWidgets import QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
sys.exit(app.exec())
2、主控代码
"""
主窗口模块
"""
import socket
from PyQt6 import QtWidgets
from server import UDPServer
from ui.ui_Main import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
主窗口初始化
"""
def __init__(self):
super(MainWindow, self).__init__()
self.udpServer = None
self.setupUi(self)
self.show()
self.set_localhost_ip()
self.set_localhost_port()
self.pushButton_start.clicked.connect(self.udp_server_start)
self.pushButton_send_message.clicked.connect(self.send_udp_data_to_client)
def set_localhost_ip(self):
host_ip = socket.gethostbyname_ex(socket.gethostname())
self.lineEdit_IP.setText(host_ip[2][3])
def set_localhost_port(self):
self.lineEdit_port.setText('12000')
self.lineEdit_name.setText('管理员')
def udp_server_start(self):
server_ip = self.lineEdit_IP.text()
server_port = int(self.lineEdit_port.text())
server_name = self.lineEdit_name.text()
self.udpServer = UDPServer(self, server_ip, server_name, server_port)
def send_udp_data_to_client(self):
self.udpServer.send_data_to_client(self.lineEdit_message.text())
3、server完整代码
import socket
from PyQt6.QtCore import QThread, pyqtSignal
class UDPServer:
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.clientList = []
self.start()
def start(self):
if not self.is_running:
self.is_running = True
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind((self.ip, self.port)) # 绑定IP与端口
# self.socket.listen(5) # 设定最大连接数
self.ui.statusbar.showMessage("UDP服务已经启动...")
self.startSocketReceiveThread()
def startSocketReceiveThread(self):
self.socketThread = UDPServerSocketReceiveThread(self.socket)
self.socketThread.receivedClientData.connect(self.show_client_message)
self.socketThread.clientAddr.connect(self.server_status_trigger)
self.socketThread.start()
def send_data_to_client(self, message):
message = self.serverName + ':' + message
self.ui.textEdit.append(message)
for cl in self.clientList:
self.socket.sendto(message.encode(), cl)
def server_status_trigger(self, clientAddr):
if clientAddr not in self.clientList:
self.clientList.append(clientAddr)
self.ui.statusbar.showMessage("有新客户端接入:" + clientAddr[0] + ':' + str(clientAddr[1]))
def show_client_message(self, message):
self.ui.textEdit.append(message)
class UDPServerSocketReceiveThread(QThread):
receivedClientData: pyqtSignal = pyqtSignal(str) # 向主线程发送客户端的数据
clientAddr: pyqtSignal = pyqtSignal(tuple) # 向主线程发送服务器状态
def __init__(self, serverSocket):
super(UDPServerSocketReceiveThread, self).__init__()
self.serverSocket = serverSocket
self.is_running = True
def run(self):
self.startReceiveData()
def startReceiveData(self):
while self.is_running:
try:
message, clientAddress = self.serverSocket.recvfrom(2048)
self.receivedClientData.emit(message.decode('utf-8'))
self.clientAddr.emit(clientAddress)
except ConnectionResetError as reason:
self.is_running = False
break
4、ui代码(PyQt6 Designer生成)
# Form implementation generated from reading ui file 'D:\projects\python-projects\multithread-udp-server\ui\Main.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(11)
self.groupBox.setFont(font)
self.groupBox.setObjectName("groupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(parent=self.groupBox)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit_IP = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_IP.setObjectName("lineEdit_IP")
self.horizontalLayout.addWidget(self.lineEdit_IP)
self.verticalLayout.addLayout(self.horizontalLayout)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
self.label_2.setObjectName("label_2")
self.horizontalLayout_2.addWidget(self.label_2)
self.lineEdit_port = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_port.setObjectName("lineEdit_port")
self.horizontalLayout_2.addWidget(self.lineEdit_port)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
self.label_3.setObjectName("label_3")
self.horizontalLayout_3.addWidget(self.label_3)
self.lineEdit_name = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_name.setObjectName("lineEdit_name")
self.horizontalLayout_3.addWidget(self.lineEdit_name)
self.verticalLayout.addLayout(self.horizontalLayout_3)
self.verticalLayout_4.addWidget(self.groupBox)
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_4.addItem(spacerItem)
self.pushButton_start = QtWidgets.QPushButton(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(11)
self.pushButton_start.setFont(font)
self.pushButton_start.setObjectName("pushButton_start")
self.horizontalLayout_4.addWidget(self.pushButton_start)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_4.addItem(spacerItem1)
self.verticalLayout_3.addLayout(self.horizontalLayout_4)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.textEdit = QtWidgets.QTextEdit(parent=self.centralwidget)
self.textEdit.setObjectName("textEdit")
self.verticalLayout_2.addWidget(self.textEdit)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.lineEdit_message = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEdit_message.setObjectName("lineEdit_message")
self.horizontalLayout_5.addWidget(self.lineEdit_message)
self.pushButton_send_message = QtWidgets.QPushButton(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(11)
self.pushButton_send_message.setFont(font)
self.pushButton_send_message.setObjectName("pushButton_send_message")
self.horizontalLayout_5.addWidget(self.pushButton_send_message)
self.verticalLayout_2.addLayout(self.horizontalLayout_5)
self.verticalLayout_3.addLayout(self.verticalLayout_2)
self.verticalLayout_4.addLayout(self.verticalLayout_3)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MainWindow", "服务器配置:"))
self.label.setText(_translate("MainWindow", "服务IP地址:"))
self.label_2.setText(_translate("MainWindow", "服务端口:"))
self.label_3.setText(_translate("MainWindow", "用户昵称:"))
self.pushButton_start.setText(_translate("MainWindow", "启动服务"))
self.pushButton_send_message.setText(_translate("MainWindow", "发送"))
三、客户端项目代码
1、启动代码
# -*- coding: utf-8 -*-
import sys
from module.main import MainWindow
from PyQt6.QtWidgets import QApplication
if __name__ == '__main__':
app = QApplication(sys.argv)
main = MainWindow()
sys.exit(app.exec())
2、主控代码
"""
主窗口模块
"""
import socket
from PyQt6 import QtWidgets
from client import UDPClient
from ui.ui_Main import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
"""
主窗口初始化
"""
def __init__(self):
super(MainWindow, self).__init__()
self.udpClient = None
self.setupUi(self)
self.show()
self.set_localhost_ip()
self.set_localhost_port()
self.pushButton_send_message.clicked.connect(self.send_udp_data_to_server)
def set_localhost_ip(self):
host_ip = socket.gethostbyname_ex(socket.gethostname())
self.lineEdit_IP.setText(host_ip[2][3])
def set_localhost_port(self):
self.lineEdit_port.setText('12000')
self.lineEdit_name.setText('匿名用户')
def send_udp_data_to_server(self):
if self.udpClient is None:
server_ip = self.lineEdit_IP.text()
server_port = int(self.lineEdit_port.text())
client_name = self.lineEdit_name.text()
self.udpClient = UDPClient(self, server_ip, client_name, server_port)
self.udpClient.send_udp_data(self.lineEdit_message.text())
3、client代码
import socket
from PyQt6.QtCore import QThread, pyqtSignal
class UDPClient:
def __init__(self, ui, ip, clientName, port):
self.ui = ui
self.ip = ip
self.hostName = clientName
self.port = port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socketThread = None
self.startSocketReceiveThread()
def send_udp_data(self, sentence):
sentence = self.hostName + ":" + sentence
self.ui.textEdit.append(sentence)
self.socket.sendto(sentence.encode(), (self.ip, self.port))
def startSocketReceiveThread(self):
self.socketThread = UDPClientSocketReceiveThread(self.socket)
self.socketThread.receivedServerData.connect(self.show_server_message)
self.socketThread.start()
def show_server_message(self, message):
self.ui.textEdit.append(message)
class UDPClientSocketReceiveThread(QThread):
receivedServerData: pyqtSignal = pyqtSignal(str) # 向主线程发送接受到客户端的数据
def __init__(self, clientSocket):
super(UDPClientSocketReceiveThread, self).__init__()
self.clientSocket = clientSocket
self.addr = None
self.is_running = True
def run(self):
self.startReceiveData()
def startReceiveData(self):
while self.is_running:
try:
message, clientAddress = self.clientSocket.recvfrom(2048)
self.receivedServerData.emit(message.decode('utf-8'))
except ConnectionResetError as reason:
self.is_running = False
break
4、ui代码(PyQt6 Designer生成)
# Form implementation generated from reading ui file 'D:\projects\python-projects\multithread-udp-client\ui\Main.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(11)
self.groupBox.setFont(font)
self.groupBox.setObjectName("groupBox")
self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(parent=self.groupBox)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.lineEdit_IP = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_IP.setObjectName("lineEdit_IP")
self.horizontalLayout.addWidget(self.lineEdit_IP)
self.verticalLayout.addLayout(self.horizontalLayout)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
self.label_2.setObjectName("label_2")
self.horizontalLayout_2.addWidget(self.label_2)
self.lineEdit_port = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_port.setObjectName("lineEdit_port")
self.horizontalLayout_2.addWidget(self.lineEdit_port)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
self.label_3.setObjectName("label_3")
self.horizontalLayout_3.addWidget(self.label_3)
self.lineEdit_name = QtWidgets.QLineEdit(parent=self.groupBox)
self.lineEdit_name.setObjectName("lineEdit_name")
self.horizontalLayout_3.addWidget(self.lineEdit_name)
self.verticalLayout.addLayout(self.horizontalLayout_3)
self.verticalLayout_3.addWidget(self.groupBox)
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.textEdit = QtWidgets.QTextEdit(parent=self.centralwidget)
self.textEdit.setObjectName("textEdit")
self.verticalLayout_2.addWidget(self.textEdit)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.lineEdit_message = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEdit_message.setObjectName("lineEdit_message")
self.horizontalLayout_4.addWidget(self.lineEdit_message)
self.pushButton_send_message = QtWidgets.QPushButton(parent=self.centralwidget)
font = QtGui.QFont()
font.setPointSize(11)
self.pushButton_send_message.setFont(font)
self.pushButton_send_message.setObjectName("pushButton_send_message")
self.horizontalLayout_4.addWidget(self.pushButton_send_message)
self.verticalLayout_2.addLayout(self.horizontalLayout_4)
self.verticalLayout_3.addLayout(self.verticalLayout_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MainWindow", "服务器设置:"))
self.label.setText(_translate("MainWindow", "服务器IP地址:"))
self.label_2.setText(_translate("MainWindow", "服务端口:"))
self.label_3.setText(_translate("MainWindow", "用户昵称:"))
self.pushButton_send_message.setText(_translate("MainWindow", "发送"))
小结:
前面几篇文章,从命令行示例到UI界面示例,从单机通讯到多机通讯。
每个示例都是在上一个示例的基础上做了很小的一部分改动,易读易懂。
这些简单的示例搭建了一个测试环境,允许开发者在这个代码环境下对服务相关的参数进行修改,通过单个客户端或多个客户端连接,查看参数修改后的效果和影响。
也可以在这些示例的基础上,进行重构或二次封装成其他具体的应用,比如DHCP、web server、即时通讯IM server等。