簡介
本小項目使用python+PyQT編寫一個基於UDP的通訊軟件。
客戶端只有兩個界面,如下所示
軟件結構
項目分爲兩部分,一部分是客戶端,一部分是服務器。服務器不斷監聽指定的端口,並將接收到的消息再重新發到該端口。客戶端包含一個線程,負責監聽從服務器發來的消息,當在客戶端按下發送按鍵時,會將輸入框中的內容發送到服務器。
代碼
服務器
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
HOST = ""
PORT = 10888
# 獲取本機ip
try:
s.connect(('8.8.8.8', 80))
my_addr = s.getsockname()[0]
HOST = str(my_addr)
except Exception as ret:
# 若無法連接互聯網使用,會調用以下方法
print("無法獲取ip,請連接網絡!\n")
finally:
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定地址和端口
try:
s.bind((HOST, PORT))
except Exception as ret:
print("啓動時遇到錯誤!\n")
quit()
print("啓動成功\n正在監聽中...")
user = {}
while True:
try:
(data, addr) = s.recvfrom(1024)
if (user.get(addr, False) == False):
user[addr] = data.decode('utf-8')
print("IP:%s NickName:%s Join" % (addr, data.decode('utf-8')))
else:
data = user[addr] + " : " + data.decode('utf-8')
print(data)
for key, value in user.items():
if key != addr:
s.sendto(data.encode('utf-8'), key)
except Exception as ret:
print(ret)
continue
客戶端
import socket
import time
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QIcon, QFont
HOST = ""
PORT = 10888
NickName = ""
class logindialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowIcon(QIcon("..\\src\\sun.png"))
self.setWindowFlags(Qt.WindowCloseButtonHint)
self.setWindowTitle('登錄')
self.resize(280, 230)
self.setFixedSize(self.width(), self.height())
self.s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sty = style
# 添加界面控件
self.lineEdit_account = QLineEdit()
self.lineEdit_IP = QLineEdit()
self.pushButton_get_ip = QPushButton("獲取本機IP")
self.pushButton_enter = QPushButton("確定")
self.pushButton_quit = QPushButton("退出")
# 設置控件提示文本
self.lineEdit_account.setPlaceholderText("請輸入用戶名")
self.lineEdit_IP.setPlaceholderText("請輸入主機IP地址")
# 設置控件大小
self.lineEdit_account.setFixedSize(250, 30)
self.lineEdit_IP.setFixedSize(250, 30)
self.pushButton_get_ip.setFixedSize(250, 25)
self.pushButton_enter.setFixedSize(250, 25)
self.pushButton_quit.setFixedSize(250, 25)
# 設置按鍵樣式
self.pushButton_get_ip.setStyleSheet(self.sty.buttonStyle())
self.pushButton_enter.setStyleSheet(self.sty.buttonStyle())
self.pushButton_quit.setStyleSheet(self.sty.buttonStyle())
# 設置佈局
layout = QVBoxLayout()
layout.addWidget(self.lineEdit_account, alignment=Qt.AlignCenter)
layout.addWidget(self.lineEdit_IP, alignment=Qt.AlignCenter)
layout.addWidget(self.pushButton_get_ip, alignment=Qt.AlignCenter)
layout.addWidget(self.pushButton_enter, alignment=Qt.AlignCenter)
layout.addWidget(self.pushButton_quit, alignment=Qt.AlignCenter)
self.setLayout(layout)
# 綁定按鈕事件
self.pushButton_enter.clicked.connect(self.on_pushButton_enter_clicked)
self.pushButton_quit.clicked.connect(QCoreApplication.instance().quit)
self.pushButton_get_ip.clicked.connect(self.click_get_ip)
def on_pushButton_enter_clicked(self):
# 用戶名判斷
if self.lineEdit_account.text() == "":
QMessageBox.information(self, '提示', "請輸入用戶名!", QMessageBox.Yes)
return
else:
global NickName
NickName = self.lineEdit_account.text()
# IP地址判斷
if self.lineEdit_IP.text() == "":
QMessageBox.information(self, '提示', "請輸入IP地址!", QMessageBox.Yes)
return
else:
global HOST
HOST = self.lineEdit_IP.text()
# 通過驗證,關閉對話框並返回1
self.accept()
def click_get_ip(self):
"""
pushbutton_get_ip控件點擊觸發的槽
:return: None
"""
# 獲取本機ip
self.lineEdit_IP.clear()
try:
self.s1.connect(('8.8.8.8', 80))
my_addr = self.s1.getsockname()[0]
self.lineEdit_IP.setText(str(my_addr))
except Exception as ret:
# 若無法連接互聯網使用,會調用以下方法
try:
my_addr = socket.gethostbyname(socket.gethostname())
except Exception as ret_e:
QMessageBox.information(self, '提示', "無法獲取ip,請連接網絡!", QMessageBox.Yes)
finally:
self.s1.close()
class ClientWindow(QWidget):
def __init__(self):
super().__init__()
self.s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.s.sendto(NickName.encode('utf-8'), (HOST, PORT))
# 初始化線程
self.my_thread = MyThread(self.s) # 實例化線程對象
self.my_thread.my_signal.connect(self.append_message_func)
self.my_thread.start() # 啓動線程
self.sty = style
self.iniUI()
def iniUI(self):
self.resize(800, 500)
self.center()
self.setWindowTitle('UDPCommunication')
self.setWindowIcon(QIcon("..\\src\\plane.png"))
# 添加控件
self.messageWindow = QTextBrowser()
self.inputWindow = QLineEdit(self)
self.btn = QPushButton('發送', self)
self.qbtn = QPushButton('退出', self)
self.btn.move(230, 380)
self.qbtn.move(550, 380)
# 設置按鍵樣式
self.btn.setStyleSheet(self.sty.buttonStyle())
self.qbtn.setStyleSheet(self.sty.buttonStyle())
# 設置佈局
layout1 = QHBoxLayout()
layout2 = QVBoxLayout()
layout3 = QVBoxLayout()
layout1.addWidget(self.btn)
layout1.addWidget(self.qbtn)
layout2.addWidget(self.inputWindow)
layout2.addLayout(layout1)
layout3.addWidget(self.messageWindow)
layout3.addLayout(layout2)
self.setLayout(layout3)
self.btn.clicked.connect(self.send)
self.qbtn.clicked.connect(QCoreApplication.instance().quit)
self.show()
def center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
def closeEvent(self, event):
reply = QMessageBox.question(self, '提示', "確定要退出嗎?", QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
def send(self):
if self.inputWindow.text() == "":
# self.messageWindow.setStyleSheet(self.sty.buttonStyle())
self.messageWindow.insertHtml(self.toHtml("red", "請先輸入信息!"))
else:
try:
send_msg = (str(self.inputWindow.text())).encode('utf-8')
self.s.sendto(send_msg, (HOST, PORT))
self.messageWindow.insertHtml(self.toHtml("black", self.inputWindow.text()))
except Exception as ret:
print(ret)
self.messageWindow.insertHtml(self.toHtml("red", "發送失敗!"))
self.inputWindow.clear()
time.sleep(1)
def append_message_func(self, messagae):
self.messageWindow.insertHtml(self.toHtml("black", messagae))
def toHtml(self, c, s):
return "<font color='" + c + "' font-size='20'>" + s + "</font><br>"
class MyThread(QThread): # 線程類
my_signal = pyqtSignal(str) # 自定義信號對象。參數str就代表這個信號可以傳一個字符串
def __init__(self, socket):
super(MyThread, self).__init__()
self.s = socket
def run(self): # 線程執行函數
print('UDP服務端正在監聽端口:{}\n'.format(PORT))
while True:
(data, addr) = self.s.recvfrom(1024)
print(data.decode('utf-8'))
self.my_signal.emit(data.decode('utf-8')) # 釋放自定義的信號
time.sleep(1)
class style():
# 按鍵樣式
def buttonStyle():
fm = "QPushButton{font-family:'Microsoft YaHei'}" # 字體樣式
fs = "QPushButton{font-size:18px}" # 字體大小
fw = "QPushButton{font-weight:bold}" # 字體加粗
c = "QPushButton{color:#DCDCDC}" # 按鍵前景色
bc = "QPushButton{background-color:#00BFFF}" # 按鍵背景色
hc = "QPushButton:hover{color:#FFFFFF}" # 光標移動到上面後的前景色
hbc = "QPushButton:hover{background-color:#6495ED}" # 光標移動到上面後的背景色
br = "QPushButton{border-radius:5px}" # 圓角半徑
pbr = "QPushButton:pressed{background-color:rgb(180,180,180);border: 5px;}" # 按下時的樣式
res = fm + fs + fw + c + bc + hc + hbc + br + pbr
return res
if __name__ == '__main__':
app = QApplication(sys.argv)
dialog = logindialog()
if dialog.exec_() == QDialog.Accepted:
window = ClientWindow()
window.show()
sys.exit(app.exec_())
項目github地址:https://github.com/ilvli/UDPCommunication