
前面我們講了 TCP 程式設計,我們知道 TCP 可以建立可靠連接配接,并且通信雙方都可以以流的形式發送資料。本文我們再來介紹另一個常用的協定--UDP。相對TCP,UDP則是面向無連接配接的協定。
UDP 協定
我們來看 UDP 的定義:
UDP 協定(User Datagram Protocol),中文名是使用者資料報協定,是 OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接配接的傳輸層協定,提供面向事務的簡單不可靠資訊傳送服務。
從這個定義中,我們可以總結出 UDP 的幾個特點以及其與 TCP 的差別:
- UDP 是使用者資料報協定,傳輸模式是資料報,而 TCP 是基于位元組流的傳輸協定。
- UDP 是無連接配接的協定,每個資料報都是一個獨立的資訊,包括完整的源位址或目的位址,它在網絡上以任何可能的路徑傳往目的地,是以能否到達目的地,到達目的地的時間以及内容的正确性都是不能被保證的。
- UDP 是簡單不可靠的協定,它不提供可靠性,隻是把資料包發送出去,并不保證能夠到達目的地。由于它不需要在用戶端和服務端之間建立連接配接,也沒有逾時重發機制,是以傳輸速度很快。
從以上特點,我們可以看到 UDP 适合應用在每次傳輸資料量小、對資料完整性要求不高、對傳輸速度要求高的領域。這裡面最典型的就是即時通信的場景,微信是一個很常見的例子。相信大家在使用微信的時候都遇到過先發的消息後收到,或者有些發送的消息對方沒有收到的情況吧,這就是 UDP 協定典型的特點,不保證傳輸資料的完整性和順序性。除此之外, UDP 還應用在線上視訊、網絡電話等場景。
UDP 傳輸過程
我們在講 TCP 的時候,我們說 TCP 用戶端和服務端必須先連接配接才可以傳輸資料:用戶端先請求連接配接伺服器,伺服器接受連接配接請求,然後雙方才可以通信。在 UDP 協定裡,用戶端隻需要知道伺服器的位址和端口号,就可以直接發送資料了。
我們來看下 UDP 傳輸的流程圖:
TCP伺服器的建立可以歸納這幾步:
- 建立 socket(套接字)
- 綁定 socket 的 IP 位址和端口号
- 接收用戶端資料
- 關閉連接配接
TCP用戶端的建立可總結為這幾步:
- 向伺服器發送資料
這裡需要注意的是 UDP 用戶端連接配接到伺服器的 IP 和端口号必須是 UDP 伺服器的 IP 和監聽的端口号,伺服器伺服器隻需要綁定 IP 和端口号,就可以時刻準備接收用戶端發送的資料,此時伺服器處于阻塞狀态,直到接收到資料為止。
UDP 用戶端
建立 socket,可以這樣做:
# 導入socket庫 import socket # 建立一個socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
建立 socket 時,第一個參數 socket.AF_INET 表示指定使用 IPv4 協定,如果要使用 IPv6 協定,就指定為 socket.AF_INET6。SOCK_DGRAM 指定基于 UDP 的資料報式 Socket 通信。
建立了 socket 之後,我們就可以向目标位址發送資料報了:
# 發送資料s.sendto(b'Hello Server', ('127.0.0.1', 6000))
第一個參數是需要發送的資料報内容,第二個參數是 IP 位址和端口号的二進制組。
如果是接收資料的話,我們可以這樣寫:
# 接收資料data, addr = s.recv(1024)# 解碼接收到的資料data = data.decode('utf-8')
接收資訊的時候,第一個 data 表示接收到的資料, addr 是對方的 IP 位址和端口号的二進制組。
想要關閉 socket,直接調用 close() 方法即可:
# 關閉 socketsocket.close()
UDP 伺服器
相比于用戶端,伺服器端隻是多了一個步驟,在建立 socket 之後,需要綁定一個 IP 位址和端口号,以便接收用戶端随時可能發送過來的資料。綁定的方法為:
# 綁定 IP 和端口s.bind(('127.0.0.1', 6000))
UDP 簡單執行個體
我們通過一個簡單的執行個體來體會下 UDP 的用戶端和伺服器的通信流程。
伺服器代碼為:
import socket # 建立 socket sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 綁定 IP 和端口号 sk.bind(('127.0.0.1', 6000)) while True: # 接收資料報 msg, addr = sk.recvfrom(1024) # 列印 print('來自[%s:%s]的消息: %s' % (addr[0], addr[1], msg.decode('utf-8'))) # 等待輸入 inp = input('>>>') # 發送資料報 sk.sendto(inp.encode('utf-8'), addr) # 關閉 socket sk.close()
這裡,我們先建立 socket,然後綁定本機的6000端口,然後等待接收用戶端發送的資料報,接收到資料後将資料内容列印在控制台。然後可以在控制台輸入回複内容,發送給用戶端。
用戶端代碼:
import socket # 建立 socket sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) addr = ('127.0.0.1', 6000) while True: # 等待輸入 msg = input('>>>') # 發送資料報 sk.sendto(msg.encode('utf-8'), addr) # 接收資料報 msg_recv, addr = sk.recvfrom(1024) # 列印 print(msg_recv.decode('utf-8')) # 關閉 socket sk.close()
在用戶端代碼中,我們就隻是建立 socket,然後在控制台輸入需要向伺服器發送的内容,通過 sentto() 方法發送給伺服器,然後接收伺服器傳回的内容,将接收的内容列印到控制台。
分别運作用戶端和伺服器代碼,然後我們在用戶端的控制台輸入 “hello server”,我們可以看到伺服器的控制台列印了用戶端發送的内容,然後我們在伺服器控制台輸入 “hello client”,同樣在用戶端控制台可以看你到内容。
下面是用戶端的控制台内容:
>>>hello serverhello client>>>
下面是伺服器的控制台内容:
來自[127.0.0.1:61207]的消息: hello server>>>hello client
這個執行個體其實就是一個簡單的聊天模型,用戶端和伺服器就像兩個人一樣可以發送和接收對方的資訊。
那麼多人群聊怎麼實作呢?簡單來說,我們需要設定一台中心伺服器,我們每個人發送的内容都先發送到中心伺服器,然後中心伺服器再轉發到每個群聊的人。
總結
本文為大家介紹了 UDP 程式設計的基本原理以及通過 Python 實作一個最簡單的聊天程式來模拟 UDP 通信的過程。通過本文的學習,我們需要對 UDP 協定有基本的認識,以及對 UDP 的通信過程比較熟悉。
文中示例代碼:https://github.com/JustDoPython/python-100-day/tree/master/day-100
參考
https://www.kancloud.cn/smilesb101/python3_x/29888
系列文章