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