基於UDP協議編寫一個簡單的通訊軟件

簡介

本小項目使用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

 

發佈了66 篇原創文章 · 獲贊 50 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章