[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()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章