[Python] socket實現TFTP上傳和下載
一、說明
本文主要基於socket實現TFTP檔案上傳與下載。
測試環境:Win10/Python3.5/tftpd64。
tftpd下載:根據自己的環境選擇下載,地址 :http://tftpd32.jounin.net/tftpd32_download.html
主要內容:TFTP協議介紹、程式執行圖示和分析fmt、原始碼。
二、TFTP協議介紹(參考網路,詳情可搜尋)
TFTP(Trivial File Transfer Protocol,簡單檔案傳輸協議),是TCP/IP協議族中的一個用來在客戶端與服務端(C/S架構)之間進行檔案傳輸的協議。
1、特點:
> 簡單、佔用資源少
> 適合小檔案傳輸
> 適合在區域網中進行傳輸
> 埠號為69
> 基於UDP實現
2、TFTP下載過程分析:
當開啟一個tftpd作為服務端,會預設監聽69埠,所以客戶端傳送資料到服務端都是經過69埠。
下載的資料流過程如上圖所示,客戶端首次傳送需要下載的檔名到服務端,(檔案存在)服務端收到後會返回該檔案的第一個包,客戶端收到後本地儲存然後再發送ACK應答包給服務端,如此往來多次,一發一答,即實現了檔案的下載。
3、TFTP操作碼與資料格式:
4、差錯碼以及對應的提示:
5、TFTP上傳過程分析(此處做簡單檔案說明,可參考下面原始碼或自行搜尋):
上傳的基本流程:客戶端傳送寫請求(操作碼為2)到服務端,如果可以進行上傳,服務端會返回ACK應答包,客戶端收到後即可進行第一個資料包傳送,進而服務端收到後會返回ACK應打包,如此多次,當客戶端檔案讀取完成,即可退出上傳,此時上傳完成。
三、程式執行圖示和分析fmt
1、執行起來的tftpd服務端如下所示:
選擇作為下載的路徑和配置IP
2、下載過程:
執行指令碼傳入兩個引數:服務端IP和檔名
3、上傳過程:
4、關於struct.pack() 和 struct.unpack()的引數說明:
參考:https://blog.csdn.net/DaxiaLeeSuper/article/details/82018070
struct.pack(b"!H7sb5sb", b"test.png", 0, b"octet", 0 )
主要分析第一個引數 fmt:如 “!H7sb5sb" => [ 1, b"test.png", 0, b"octet", 0 ]
fmt對後面幾個引數說明,其中H代表1,7s表示長度為7的字串等
1、fmt首個字元:
2、fmt其他字元:
四、原始碼
1 # -*- coding:utf-8 -*- 2 3 """ 4 實現 TFTP 上傳與下載功能 5 需要配合tftpd 軟體測試 6 """ 7 8 from socket import * 9 import struct 10 import sys 11 12 13 class DownloadClient: 14 """ 15 下載基本流程: 16 -------------------------------------- 17 客戶端(Client) 服務端(Server) 18 -------------------------------------- 19 讀寫請求 ---> 20 <--- 資料包[0] 21 ACK[0] ---> 22 <--- 資料包[1] 23 ACK[1] ---> 24 .... 25 -------------------------------------- 26 27 操作碼 功能 28 -------------------------------------- 29 1 讀請求,即下載 30 2 寫請求,即上傳 31 3 表示資料包,即Data 32 4 確認碼,即ACK 33 5 錯誤 34 -------------------------------------- 35 """ 36 def __init__(self): 37 # 讀取引數 38 if len(sys.argv) != 4: 39 print("-" * 30) 40 print("Tips:") 41 print("python xxx.py 1 127.0.0.1 test.png") 42 print("-" * 30) 43 exit() 44 else: 45 self.mid = sys.argv[1] # 執行的方法,1下載或2上傳 46 self.remoteIp = sys.argv[2] # 伺服器IP 47 self.filename = sys.argv[3] # 下載檔名 48 49 # 建立socket例項 50 self.socketClient = socket(AF_INET, SOCK_DGRAM) 51 self.socketClient.bind(('', 7788)) 52 53 def start(self): 54 """啟動執行""" 55 if self.mid == "1": 56 self.download() 57 elif self.mid == "2": 58 self.upload() 59 else: 60 print(self.mid) 61 print("引數輸入錯誤 [python 指令碼名 方法id(1下載,2上傳) 伺服器IP 檔名]:python xxx.py 1 127.0.0.1 test.png") 62 exit() 63 64 def download(self): 65 """ TFTP 下載""" 66 print("下載啟動...") 67 68 # 構建下載請求資料 69 # 第一個引數 !H7sb5sb = "!H"+str(len(filename))+"sb5sb" 70 filenameLen = str(len(self.filename)) 71 cmdBuf = struct.pack(("!H%ssb5sb" % filenameLen).encode("utf-8"), 1, self.filename.encode("utf-8"), 0, b"octet", 0) 72 73 # 傳送下載檔案請求資料到指定伺服器 74 self.socketClient.sendto(cmdBuf, (self.remoteIp, 69)) 75 76 # self.show() 77 78 locPackNum = 0 # 請求包號 79 saveFile = '' # 儲存檔案控制代碼 80 while True: 81 recvData, recvAddr = self.socketClient.recvfrom(1024) 82 recvDataLen = len(recvData) 83 84 # 解包 85 cmdTuple = struct.unpack(b"!HH", recvData[:4]) 86 cmd = cmdTuple[0] # 指令 87 curPackNum = cmdTuple[1] # 當前包號 88 89 if cmd == 3: # 是否為資料包 90 if curPackNum == 1: 91 # 以追加的方式開啟檔案 92 saveFile = open(self.filename, "ab") 93 94 # 包編號是否和上次相等 95 if locPackNum + 1 == curPackNum: 96 saveFile.write(recvData[4:]) # 寫入資料 97 locPackNum += 1 98 99 # 傳送ACK應答 100 ackBuf = struct.pack(b"!HH", 4, locPackNum) 101 self.socketClient.sendto(ackBuf, recvAddr) 102 103 print("(%d)次接收到資料" % locPackNum) 104 105 # 確認為最後一個包 106 if recvDataLen < 516: 107 saveFile.close() 108 print("已經下載完成") 109 break 110 111 elif cmd == 5: # 是否為錯誤應答 112 print("error num:%d" % curPackNum) 113 break 114 115 def upload(self): 116 """TFTP 上傳""" 117 print("上傳啟動...") 118 119 # 1、傳送讀請求 120 filenameLen = str(len(self.filename)) 121 cmdBuf = struct.pack(("!H%ssb5sb" % filenameLen).encode("utf-8"), 2, self.filename.encode("utf-8"), 0, b"octet", 0) 122 123 self.socketClient.sendto(cmdBuf, (self.remoteIp, 69)) 124 125 localPackNum = 1 # 本地包號 126 sendFile = '' # 檔案控制代碼 127 while True: 128 # 2、接收回復 129 recvData, recvAddr = self.socketClient.recvfrom(1024) 130 131 # 3、解包 132 cmdTuple = struct.unpack(b"!HH", recvData[:4]) 133 cmd = cmdTuple[0] # 指令 134 curPackNum = cmdTuple[1] # 當前包號 135 136 # print(recvData) 137 138 if cmd == 4: 139 # 開啟並讀取檔案 140 if curPackNum == 0: 141 sendFile = open(self.filename, "rb") 142 143 # ACK應答的包號是否與本地的一樣 144 if localPackNum - 1 == curPackNum: 145 # 4、讀取 512 byte資料 146 sendData = sendFile.read(512) 147 148 # 判斷檔案是否讀取完成 149 if len(sendData) <= 0: 150 sendFile.close() 151 print("上傳完成") 152 break 153 154 # 5、打包傳送資料 155 sendDataBuf = struct.pack(b"!HH512s", 3, localPackNum, sendData) 156 self.socketClient.sendto(sendDataBuf, recvAddr) 157 158 # 列印過程 159 print("(%d)次已傳送,資料長度:%d" % (localPackNum, len(sendData))) 160 localPackNum += 1 161 162 elif cmd == 5: 163 # sendFile.close() 164 print("error num:%d" % curPackNum) 165 break 166 167 def show(self): 168 """測試列印資料""" 169 recvData = self.socketClient.recvfrom(1024) 170 print(recvData) 171 exit() 172 173 174 if __name__ == "__main__": 175 demo = DownloadClient() 176 demo.start()