使用Python語言通過PyQt5和socket實現UDP服務器


前言

最近做了一個小軟件,記錄一下相關內容。

已有條件

現在已有一個硬件設備作為客戶端(暫稱其為“電路”)。

基於SIM卡,電路可以通過UDP協議傳輸數據(程序已經內置在電路中),只需要修改配置文件(位於SD卡中,主要修改服務器端的IP和端口)即可。

需求

我面向的需求是這樣的:我需要開發一個服務器端的程序,接收多個客戶端發來的數據並開發可視化界面。

總結

從開發角度和技術角度來看,軟件的基礎和核心技術是使用UDP協議進行數據傳輸,並使用PyQt5和pyqtgraph做可視化界面(還用到了QThread和自定義的下拉復選框),開發過程中還涉及到了內網穿透和NATAPP。

理論基礎:運輸層

為使用UDP協議進行數據傳輸,我大致復習了一下計算機網絡中的運輸層。

功能

運輸層實現兩台主機中進程之間的通信,一個主機中的多個進程可以和另一台主機中的多個進程通信。

運輸層實現上述功能的方案是端口(port)

兩個主要協議

運輸層有兩個主要協議:

  • 傳輸控制協議TCP(Transmission Control Protocol)
  • 用戶數據報協議UDP(User Datagram Protocol)

TCP

  • TCP是面向連接
    • 應用進程在傳輸數據前必須先建立連接,數據傳送結束后要釋放連接
  • TCP連接是點對點
    • 每一條TCP連接只能有兩個端點
    • TCP不提供廣播或多播服務
  • TCP提供可靠交付的服務
    • 通過TCP連接傳送的數據,無差錯、不丟失、不重復,並且按序到達
  • TCP面向字節流
    • 雖然應用程序和TCP的交互是一次一個數據塊(大小不等),但TCP把應用程序交下來的數據僅僅看成一連串的無結構的字節流。
    • TCP不保證接收方應用程序所收到的數據塊和發送方應用程序所發出的數據塊具有對應大小
    • TCP保證接收方應用程序收到的字節流必須和發送方應用程序發出的字節流完全一樣,同時接收方應用程序必須有能力識別收到的字節流,把它還原成有意義的應用層數據

UDP

  • UDP是無連接
    • 在傳輸數據前不需要先建立連接,主機在收到UDP報文后不需要給出任何確認
  • UDP是面向報文
    • 發送方:UDP對應用層交下來的報文,不合並也不拆分,添加首部后就交付給IP層
    • 接收方:UDP對IP層交上來的UDP用戶數據包,在去除首部后就直接交付給應用層的進程
  • UDP盡最大努力交付
    • 不保證可靠交付
  • UDP支持一對一、一對多、多對一和多對多的交互通信

Python中的UDP編程

Python中的UDP編程可以通過socket來實現,下面是一個簡單樣例

服務器端

import socket

server_ip = '127.0.0.1'
server_port = 9999

# 建立套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # socket.SOCK_DGRAM代表是UDP通信
# 綁定IP和端口
s.bind((server_ip, server_port))
print('Bind UDP Server on %s:%s' % (server_ip, server_port))

while True:
    # 接收數據
    data, addr = s.recvfrom(1024)
    print(addr, "\t", data)
    # 發送數據
    s.sendto(b'Received:%s'%data, addr)

客戶端

import socket

server_ip = '127.0.0.1'
server_port = 59955

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # socket.SOCK_DGRAM代表是UDP通信
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 發送數據
    s.sendto(data, (server_ip, server_port))
    # 接收數據
    # print(s.recv(1024).decode('utf-8'))
s.close()

值得注意的問題:緩沖區機制

UDP通信時,兩個主機都要建立一個socket。

我這里的情況是客戶端會一直給服務器端發數據。

在服務器端我發現socket一旦建立(准確來講是創建socket對象並綁定至本地端口),就會一直接收數據,而不是調用recvfrom等函數(這類函數用來接收數據)時才會接收。

估計這是緩沖區機制,UDP應該就是這么設計的。大概就是socket對象創建后,收到的內容就會放入緩沖區,如果調用了recvfrom等數據接收函數就從緩沖區中取出數據。

內網穿透

為什么要用內網穿透

先不講內網穿透是什么,有興趣的可以自己去查查,下面我大概講講我淺顯的理解。

在開發服務器端程序的過程中,我用的是自己的電腦,連接的網絡是手機熱點(因為在宿舍),因此我的電腦是沒有公網IP的。

客戶端程序用的是SIM卡,用的是公網(外網)IP,我開發的服務器端程序用的是私網(內網)IP。

公網IP是無法訪問私網IP的(因為NAT),所以我需要讓我的服務器端程序能夠被外網訪問

問了一下@roadwide,他說要用內網穿透,並推薦了NATAPP等軟件。

NATAPP的使用

怎么用呢?看看官方教程就知道了,鏈接放在文章末尾了。

講一個比較關鍵的點,以理解下NATAPP是干嘛的

NATAPP截圖

NATAPP運行起來后,就會將上圖紅框里的URL映射到本機(127.0.0.1)的80端口。

NATAPP會給我一個URL(作為我的外網IP),這樣客戶端程序通過訪問NATAPP給我的URL就可以間接訪問我在本機運行的服務器端程序。

PyQt5

QThread

服務器端程序的界面上有兩個作用分別是開始接收數據和停止接收數據的按鈕。

接收數據是通過一個while循環(循環體中接收一個數據)實現的,如果點擊開始接收數據的按鈕,那就運行while循環直到停止接收數據的按鈕被點擊。

剛開始實現數據接收功能時發現程序界面會崩潰、點擊不動,因為直接把while循環寫在軟件主界面的代碼中。

后來使用了PyQt5中的QThread(也有人說QThread並不是一個線程),在一個線程中實現while循環,然后就成功了。

在實現時我參考了其他網友的代碼,參考鏈接放在文章末尾,注意一點是實現方式不止一種,比如說有些網友說用threading也可以,而且我也發現我的思路和參考的那份代碼稍有不一樣(我們實現的功能是相似的,但我只用了一個pyqtSignal,而那位網友用了兩個)。

下拉復選框

這個軟件需要有一個下拉復選框,而PyQt5中並沒有這個東西,因此需要手動實現,這里我參考了其他網友的實現方式,參考鏈接見文章末尾。

參考鏈接

Python中的UDP編程

https://blog.csdn.net/vict_wang/article/details/81587093

https://www.jb51.net/article/165933.htm

理解NAT和內網穿透

https://baike.baidu.com/item/nat/320024

https://baike.baidu.com/item/內網穿透

NATAPP

https://natapp.cn/#

https://natapp.cn/article/natapp_newbie

PyQt5

pyqtgraph


作者:@臭咸魚

轉載請注明出處:https://www.cnblogs.com/chouxianyu/

歡迎討論和交流!



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM