一個簡單的UDP服務端與客戶端
服務端:
1 from socket import * 2 #創建套接字 3 udp_server = socket(AF_INET,SOCK_DGRAM) 4 5 msg_server = ("",8877) 6 #綁定ip地址和端口 7 udp_server.bind(msg_server) 8 9 while True: 10 #接受消息,注意此處用的是 recvfrom() 11 msg_client = udp_server.recvfrom(1024) 12 13 print("新客戶端已連接--->>>") 14 15 if len(msg_client) != 0: 16 #msg_client中有兩個值,第一個表示收到的消息的內容,第二個表示客戶端的IP地址和端口的信息 17 print("%s:%s" % (msg_client[1], msg_client[0].decode("utf-8"))) 18 else: 19 break 20 21 udp_server.close()
客戶端:
from socket import * sock = socket(AF_INET,SOCK_DGRAM) addr_msg = ("192.168.1.104",8877) #連接服務端 sock.connect(addr_msg) while True: msg_send = input("請輸入要發送的信息:") if msg_send =='q': break #注意此處用的是sendto() 其中第一個參數表示要發送的消息的內容,第二個參數表示服務端的IP地址和端口的元組 sock.sendto(msg_send.encode("utf-8"),addr_msg) sock.close()
基於UDP的tftp文件傳輸
客戶端:
1 from socket import * 2 import struct 3 import os 4 5 def main(): 6 file_name = input("請輸入要下載的文件名:") 7 #建立一個UDP的套接字 8 udpsocket = socket(AF_INET,SOCK_DGRAM) 9 #創建一個tftp的下載請求 H表示第一個參數占用兩個字節,d表示對應的參數為bite類型 10 # %ds 表示文件名占用的字符個數,如后面的octet占用了5個字符,所以寫成5s 11 # octet是tftp傳輸的一種模式,它決定了傳輸數據的格式,還有其他的幾種tftp傳輸的模式 12 #構造包使用的是struck中的pack ,其中的 H 表示使參數占用兩個字節 13 request_header = struct.pack("!H%dsb5sb"%len(file_name),1,file_name.encode('utf-8'),0,b"octet",0) 14 server_msg = ("192.168.0.100",69) 15 #發送一個下載文件的請求 16 udpsocket.sendto(request_header,server_msg) 17 18 # 這里必須以wb的格式打開文件,否則文件不能正常顯示 19 f = open(file_name,"wb") 20 21 # num表示的是數據塊的編號,這里使得其初始值為0 22 num = 0 23 #此處建立一個標記,以確認傳輸過程中沒發生錯誤 24 mask = True 25 26 while True: 27 # 接收服務端發來的數據包 28 response_data = udpsocket.recvfrom(1024) 29 recv_data, server_info = response_data 30 31 # 此處用struct中的unpack來解包 H 表示數據占用兩個字節長度 32 # 這里的前兩個字節中存儲的是操作碼 33 operation_num = struct.unpack("!H",recv_data[:2]) 34 # 這里的第三個和第四個字節中存儲的是數據塊的編號 35 package_num = struct.unpack("!H",recv_data[2:4]) 36 print(package_num[0]) 37 38 # 操作碼為3時表示接收到的信息是服務端收到響應而發給客戶端的數據包 39 if operation_num[0] == 3: 40 #這一次收到的值應該是在上一次收到的值的基礎上加上一后的結果 41 num =num + 1 42 #此處表示一旦num的值超過了它2個字節所表示的值的范圍,則讓它從0開始再來重復一遍(主要是因為操作碼只能占2個字節) 43 if num == 65535: 44 num = 0 45 # 進行塊編號的確認 46 if num == package_num[0]: #此處取下標是因為此時的package_num表示的是一個元組,而塊編號是元組中的第一個數據 47 # 將收到的具體數據寫入到文件中 48 f.write(recv_data[4:]) 49 #塊編號的確認每次都要在之前的值上加1 50 num = package_num[0] 51 #構造一個響應包 操作碼為4 發送的數據就是加上1后的塊編號 52 ack_data = struct.pack("!HH",4,package_num[0]) 53 #發送響應包 54 udpsocket.sendto(ack_data, server_info) 55 56 elif operation_num[0] == 5: 57 print("Error!") 58 #若發生錯誤信息,則將標記的值改成False , 以便后續操作刪除這個文件 59 mask = False 60 f.close() 61 62 # 一個數據包的最大長度為 4+512 = 516 位,如果數據包小於這個數值,則表明這已經是最后一次的數據傳輸了 63 if len(recv_data) < 516: 64 print("Finish!") 65 break 66 # mask標記的值為True則表示文件傳輸的過程中沒有發生錯誤,就將本地接收到的文件關閉保存 67 if mask == True: 68 f.close() 69 70 else: 71 # mask標記的值為False則表示文件傳輸的過程中出現了錯誤信息,則將已經接收到的文件刪除 72 os.remove(file_name) 73 74 if __name__ == "__main__": 75 main()
至於服務端可以用 tftpd32 這個軟件來模擬實現
關於TFTP協議
TFTP協議是一種基於UDP的小型文件傳輸協議,它不具備FTP的許多功能
TFTP的端口號為69
一個數據包在接受的過程中最大長度為512+2+2=516字節,所以當它的長度小於516字節時便可以判斷出這是最后一次的數據傳輸
上面構造數據包時使用的H的意思是,讓它所代表的參數占用兩個字節的長度,從而使數據包符合TFTP協議的要求