2 socket模塊-UDP
gitbook鏈接:用python帶你進入AI中的深度學習技術(shù)領(lǐng)域https://www.gitbook.com/book/scrappyzhang/python_to_deeplearn/details
github鏈接:https://github.com/ScrappyZhang/python_web_Crawler_DA_ML_DL
TCP/IP協(xié)議中的TCP和UDP協(xié)議都是通過一種名為套接字(socket)來實現(xiàn)網(wǎng)絡功能。套接字是一種類文件對象,它使得程序能夠接受客戶端的連接或者建立對客戶端的連接,用以發(fā)送和接收數(shù)據(jù)。不論是客戶端程序還是服務器端程序,為了進行網(wǎng)絡通信,都要創(chuàng)建套接字對象。本章講解UDP協(xié)議用python套接字模塊的實現(xiàn)。
2.1 UDP
2.1.1 udp定義
UDP 是User Datagram Protocol的簡稱, 中文名是用戶數(shù)據(jù)報協(xié)議,是OSI(Open System Interconnection,開放式系統(tǒng)互聯(lián)) 參考模型中一種無連接的傳輸層協(xié)議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規(guī)范。UDP在IP報文的協(xié)議號是17。UDP有不提供數(shù)據(jù)包分組、組裝和不能對數(shù)據(jù)包進行排序的缺點,也就是說,當報文發(fā)送之后,是無法得知其是否安全完整到達的。UDP用來支持那些需要在計算機之間傳輸數(shù)據(jù)的網(wǎng)絡應用。包括網(wǎng)絡視頻會議系統(tǒng)在內(nèi)的眾多的客戶/服務器模式的網(wǎng)絡應用都需要使用UDP協(xié)議。
2.1.2 udp特性
(1) UDP是一個無連接協(xié)議,傳輸數(shù)據(jù)之前源端和終端不建立連接,當它想傳送時就簡單地去抓取來自應用程序的數(shù)據(jù),并盡可能快地把它扔到網(wǎng)絡上。在發(fā)送端,UDP傳送數(shù)據(jù)的速度僅僅是受應用程序生成數(shù)據(jù)的速度、計算機的能力和傳輸帶寬的限制;在接收端,UDP把每個消息段放在隊列中,應用程序每次從隊列中讀一個消息段。 (2) 由于傳輸數(shù)據(jù)不建立連接,因此也就不需要維護連接狀態(tài),包括收發(fā)狀態(tài)等,因此一臺服務機可同時向多個客戶機傳輸相同的消息。 (3) UDP信息包的標題很短,只有8個字節(jié),相對于TCP的20個字節(jié)信息包的額外開銷很小。 (4) 吞吐量不受擁擠控制算法的調(diào)節(jié),只受應用軟件生成數(shù)據(jù)的速率、傳輸帶寬、源端和終端主機性能的限制。 (5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持復雜的鏈接狀態(tài)表(這里面有許多參數(shù))。 (6)UDP是面向報文的。發(fā)送方的UDP對應用程序交下來的報文,在添加首部后就向下交付給IP層。既不拆分,也不合并,而是保留這些報文的邊界,因此,應用程序需要選擇合適的報文大小。
雖然UDP是一個不可靠的協(xié)議,但它是分發(fā)信息的一個理想?yún)f(xié)議。例如,在屏幕上報告股票市場、在屏幕上顯示航空信息等等。UDP也用在路由信息協(xié)議RIP(Routing Information Protocol)中修改路由表。在這些應用場合下,如果有一個消息丟失,在幾秒之后另一個新的消息就會替換它。UDP廣泛用在多媒體應用中,例如,Progressive Networks公司開發(fā)的RealAudio軟件,它是在因特網(wǎng)上把預先錄制的或者現(xiàn)場音樂實時傳送給客戶機的一種軟件,該軟件使用的RealAudio audio-on-demand protocol協(xié)議就是運行在UDP之上的協(xié)議,大多數(shù)因特網(wǎng)電話軟件產(chǎn)品也都運行在UDP之上。
2.2 socket模塊函數(shù)
python中實現(xiàn)套接字的基本模塊為socket。一般公共socket()函數(shù)來創(chuàng)建套接字,并進行網(wǎng)絡通信。要使用socket需要導入socket模塊:import socket。一般使用socket.socket()函數(shù)來創(chuàng)建套接字。其語法如下:
socket.socket(family=AF_INET, type=SOCK_STREAM, proto)
其中: family為套接字家族名:AN_INET、AF_INET6、AF_UNIX、AF_CAN、AF_RDS;AN_INET默認值代表ipv4。 type為套接字類型:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;SOCK_STREAM為TCP協(xié)議使用的類型,SOCK_DGRAM為UDP使用的類型。 proto為協(xié)議類型,默認為0 。
常見的socket對象常用的方法有:
bind(address)
其參數(shù)address是由ip和端口組成的元組,如('127.0.0.1', 8888) 。如果ip地址為空,則表示本機,它的作用為綁定端口,使該程序在運行時使用操作系統(tǒng)的固定指定端口。
listen(backlog)
其參數(shù)backlog是指在拒絕連接之前,操作系統(tǒng)允許此程序的最大掛起連接數(shù)量。最小值為0.
accept()
等待進入連接,并返回一個由新建的與客戶端的socket連接和客戶端地址組成的元組,其中客戶的地址是由客戶端ip地址和端口組成的元組。
close()
關(guān)閉套接字,停止連接。
recv(buffersize[, flag])
TCP用于接收遠程連接發(fā)來的信息,并獲取該信息,python3中為bytes類型。buffersize為接收緩沖區(qū)的大小。阻塞函數(shù)
send(data[, flags])
TCP用于發(fā)送數(shù)據(jù),data為bytes類型,返回值為已經(jīng)發(fā)送的字節(jié)數(shù)。
recvfrom(buffersize[, flag])
UDP用于接收遠程連接發(fā)來的信息,并獲取該信息,python3中為bytes類型。buffersize為接收緩沖區(qū)的大小。阻塞函數(shù)
sendto(data[, flags])
UDP用于發(fā)送數(shù)據(jù),data為bytes類型,返回值為已經(jīng)發(fā)送的字節(jié)數(shù)。
2.3 UDP套接字流程
這幾章為了更好的無負擔學習套接字的客戶端和服務端程序?qū)W習,我們借助非常有名的網(wǎng)絡調(diào)試助手來充當服務器或者客戶端進行配合演示。網(wǎng)絡調(diào)試助手的具體使用可以查看UDP操作https://jingyan.baidu.com/article/20b68a88a9c056796dec625e.html和TCP操作:https://jingyan.baidu.com/article/148a1921dc93e74d71c3b1d7.html。 本書采用mac的網(wǎng)絡調(diào)試助手演示,其他系統(tǒng)的可以查看百度鏈接。


根據(jù)UDP協(xié)議的無連接特點,一般客戶端僅僅需要創(chuàng)建套接字、數(shù)據(jù)收發(fā)、關(guān)閉套接字三個部分就可以完成了。服務器由于需要給眾多客戶端一個明確的連接地址和端口,所以需要額外的綁定端口操作。下圖為UDP客戶端和UDP服務器之間的通信流程。

udp通信模型中,在通信開始之前,不需要建立相關(guān)的鏈接,只需要發(fā)送數(shù)據(jù)即可,類似于生活中,"寫信"/“發(fā)短信“。

2.4 UDP客戶端實現(xiàn)
需求實現(xiàn):
向服務器發(fā)送一條數(shù)據(jù),并接收來自服務器的數(shù)據(jù),并打印
根據(jù)流程圖書寫模塊代碼
導入套接字模塊
import socket
創(chuàng)建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
發(fā)數(shù)據(jù)
sock.sendto(data, address)
收數(shù)據(jù)
sock.recvfrom(buffersize)
關(guān)閉套接字
sock.close()
完整代碼
'''net01_udp_client.py'''
import socket # 導入模塊
?
address = ('192.168.234.129', 8080) # 服務器地址為192.168.234.129,端口號為8080
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 創(chuàng)建套接字
?
send_data = 'net01_udp_client.py'
print('要發(fā)送的數(shù)據(jù)為', send_data)
?
sock.sendto(send_data.encode('utf-8'), address) # 發(fā)送數(shù)據(jù)為bytes類型
?
recv_data = sock.recvfrom(1024) # 接收到的數(shù)據(jù)為兩部分,recv_data[1]為數(shù)據(jù)發(fā)送端的地址,recv_data[2]為接收到的數(shù)據(jù)
print(recv_data[1], '傳送回來的數(shù)據(jù)為:', recv_data[0].decode('utf-8'))
?
sock.close()
實現(xiàn)結(jié)果

本節(jié)通過mac上運行我們實例中的udp客戶端程序,在虛擬機上用linux上打開網(wǎng)絡助手作為UDP服務器。其中,本地mac的ip為192.168.234.1,在運行時系統(tǒng)自動分配的端口號為58505;虛擬機上的linux系統(tǒng)的ip為192.168.234.129,設定服務器端口號為8080。 先讓服務端運行(點擊連接網(wǎng)絡),然后運行程序,可以看到linux上的網(wǎng)絡調(diào)試助手接收到的數(shù)據(jù)為net01_udp_client.py;然后通過設置網(wǎng)絡調(diào)試助手上的目標ip和端口為我們的udp客戶端程序運行的ip和端口(192.168.234.1, 58505),寫入數(shù)據(jù)(我已經(jīng)接收到數(shù)據(jù)了),點擊發(fā)送;在udp客戶端可以看到成功接收到了來自網(wǎng)絡調(diào)試助手發(fā)送的數(shù)據(jù)和相關(guān)地址信息。這樣我們就簡單的實現(xiàn)了udp客戶端的收發(fā)功能和語法學習。
2.5 UDP服務器端實現(xiàn)
需求實現(xiàn):
從客戶端收到一條數(shù)據(jù)后,在數(shù)據(jù)頭增加’來自服務器‘字符串,然后一起轉(zhuǎn)發(fā)回客戶端,然后關(guān)閉服務器套接字。
根據(jù)流程圖書寫模塊代碼
導入套接字模塊
import socket
創(chuàng)建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
綁定端口
sock.bind(address)
收數(shù)據(jù)
sock.recvfrom(buffersize)
發(fā)數(shù)據(jù)
sock.sendto(data, address)
關(guān)閉套接字
sock.close()
完整代碼
'''net02_udp_server.py'''
import socket
?
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
?
address = ('192.168.234.1', 8888) # 地址:設定服務器要使用端口8888
sock.bind(address) # 綁定端口
?
recv_data = sock.recvfrom(1024) # 接收數(shù)據(jù)
send_data = '來自服務器' + recv_data[0].decode() # 數(shù)據(jù)處理,增加'來自服務器'
sock.sendto(send_data.encode('utf-8'), recv_data[1]) # 發(fā)送數(shù)據(jù)
?
sock.close() # 關(guān)閉套接字
實現(xiàn)結(jié)果
本節(jié)通過mac上運行我們實例中的udp服務端程序,在虛擬機上用linux上打開網(wǎng)絡助手作為UDP客戶端。其中,本地mac的ip為192.168.234.1,設定綁定的端口號為8888;虛擬機上的linux系統(tǒng)的ip為192.168.234.129。 先運行程序,然后通過網(wǎng)絡調(diào)試助手指定發(fā)送ip和端口,發(fā)送數(shù)據(jù)’服務器你好‘,可以看到linux上的網(wǎng)絡調(diào)試助手接收到的數(shù)據(jù)為’來自服務器服務器你好‘。這樣我們就簡單的實現(xiàn)了udp服務端的收發(fā)功能和語法學習。

如果把上一節(jié)的客戶端所要發(fā)送的目標地址修改為本節(jié)所創(chuàng)建的服務器地址('192.168.234.1', 8888),我們會發(fā)現(xiàn)它們實現(xiàn)了通信:同一操作系統(tǒng)不同進程間的通信。
# address = ('192.168.234.129', 8080) # 服務器地址為192.168.234.129,端口號為8080
address = ('192.168.234.1', 8888) # 和net02_udp_server服務器進行通信

2.6 實例:UDP簡易版聊天工具實現(xiàn)
需求實現(xiàn):
編寫1個程序,有2個功能:1.獲取鍵盤數(shù)據(jù),并將其發(fā)送給指定方,2.接收數(shù)據(jù)并顯示。進行簡單選擇以上的2個功能調(diào)用實現(xiàn)相應的功能。
根據(jù)流程圖書寫模塊代碼
主程序 套接字創(chuàng)建與端口綁定
功能菜單:1、發(fā)送數(shù)據(jù) 2、接收數(shù)據(jù)
功能調(diào)用:如果1,調(diào)用發(fā)送數(shù)據(jù)函數(shù);如果2 調(diào)用接收數(shù)據(jù)函數(shù)
發(fā)送數(shù)據(jù)函數(shù) 輸入數(shù)據(jù)、指定方的ip和端口 發(fā)送數(shù)據(jù)
接收數(shù)據(jù)函數(shù) 接收數(shù)據(jù)并打印

完整代碼
'''net03_udp_chat.py'''
import socket
?
?
def send_message():
send_data = input('請輸入要發(fā)送的消息:\n')
send_ip = input('請輸入要發(fā)送的ip:\n')
send_port = input('請輸入要發(fā)送的端口:\n')
send_address = (send_ip, int(send_port))
sock.sendto(send_data.encode('utf-8'), send_address)
?
?
def recv_message():
recv_data = sock.recvfrom(1024) # 接收數(shù)據(jù)
print('從', recv_data[1], '接收的數(shù)據(jù)為:', recv_data[0].decode('utf-8'))
?
?
if __name__ == '__main__':
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
?
address = ('192.168.234.1', 8888) # 地址:設定服務器要使用端口8888
sock.bind(address) # 綁定端口
?
# 功能菜單顯示
print('*' * 30)
print('1、發(fā)送數(shù)據(jù)')
print('2、接收數(shù)據(jù)')
print('*' * 30)
fun_num = input('請選擇并輸入指定數(shù)字:\n') # 獲取鍵盤選項數(shù)據(jù)
?
# 輸入判斷
if fun_num == '1':
send_message()
elif fun_num == '2':
recv_message()
else:
print('您輸入的數(shù)據(jù)有誤!程序結(jié)束')
實現(xiàn)結(jié)果

本節(jié)運行我們實例中的udp聊天器程序,在虛擬機上用linux上打開網(wǎng)絡助手作為UDP客戶端。其中,我們的udp聊天器的ip為192.168.234.1,設定綁定的端口號為8888;虛擬機上的linux系統(tǒng)的ip為192.168.234.129,設定服務器端口號為8080。 從下圖的結(jié)果可以看到,選擇不同的選項會進入不同的模塊,來完成指定功能。此時我們便完成了udp的基本學習。
2.7 小結(jié)
