[Python] socket实现TFTP上传和下载
- 一、说明
- 二、TFTP协议介绍(参考网络,详情可搜索)
- 2.1、特点
- 2.2、TFTP下载过程分析:
- 2.3、TFTP操作码与数据格式:
- 2.4、差错码以及对应的提示:
- 2.5、TFTP上传过程分析(此处做简单文件说明,可参考下面源码或自行搜索):
- 三、程序运行图示和分析fmt
- 3.1运行起来的tftpd服务端如下所示:
- 3.2、下载过程:
- 3.3、下载过程
- 3.4、关于struct.pack() 和 struct.unpack()的参数说明:
- 四、源码
一、说明
本文主要基于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架构)之间进行文件传输的协议。
简单、占用资源少
适合小文件传输
适合在局域网中进行传输
端口号为 69
基于UDP实现
当打开一个tftpd作为服务端,会默认监听69端口,所以客户端发送数据到服务端都是经过69端口。
下载的数据流过程如上图所示,客户端首次发送需要下载的文件名到服务端,(文件存在)服务端收到后会返回该文件的第一个包,客户端收到后本地保存然后再发送ACK应答包给服务端,如此往来多次,一发一答,即实现了文件的下载。
操作码 | 功能 |
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据包,即DATA |
4 | 确认码,即ACK |
5 | 错误 |
上传的基本流程:客户端发送写请求(操作码为2)到服务端,如果可以进行上传,服务端会返回ACK应答包,客户端收到后即可进行第一个数据包发送,进而服务端收到后会返回ACK应打包,如此多次,当客户端文件读取完成,即可退出上传,此时上传完成。
三、程序运行图示和分析fmt
运行脚本传入三个参数:方法选择、服务端IP和文件名
参考:
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的字符串等:
fmt首个字符
fmt其他字符
四、源码
# -*- coding:utf-8 -*-
"""
实现 TFTP 上传与下载功能
需要配合tftpd 软件测试
"""
from socket import *
import struct
import sys
class DownloadClient:
"""
下载基本流程:
--------------------------------------
客户端(Client) 服务端(Server)
--------------------------------------
读写请求 --->
<--- 数据包[0]
ACK[0] --->
<--- 数据包[1]
ACK[1] --->
....
--------------------------------------
操作码 功能
--------------------------------------
1 读请求,即下载
2 写请求,即上传
3 表示数据包,即Data
4 确认码,即ACK
5 错误
--------------------------------------
"""
def __init__(self):
# 读取参数
if len(sys.argv) != 4:
print("-" * 30)
print("Tips:")
print("python xxx.py 1 127.0.0.1 test.png")
print("-" * 30)
exit()
else:
self.mid = sys.argv[1] # 执行的方法,1下载或2上传
self.remoteIp = sys.argv[2] # 服务器IP
self.filename = sys.argv[3] # 下载文件名
# 创建socket实例
self.socketClient = socket(AF_INET, SOCK_DGRAM)
self.socketClient.bind(('', 7788))
def start(self):
"""启动执行"""
if self.mid == "1":
self.download()
elif self.mid == "2":
self.upload()
else:
print(self.mid)
print("参数输入错误 [python 脚本名 方法id(1下载,2上传) 服务器IP 文件名]:python xxx.py 1 127.0.0.1 test.png")
exit()
def download(self):
""" TFTP 下载"""
print("下载启动...")
# 构建下载请求数据
# 第一个参数 !H7sb5sb = "!H"+str(len(filename))+"sb5sb"
filenameLen = str(len(self.filename))
cmdBuf = struct.pack(("!H%ssb5sb" % filenameLen).encode("utf-8"), 1, self.filename.encode("utf-8"), 0, b"octet", 0)
# 发送下载文件请求数据到指定服务器
self.socketClient.sendto(cmdBuf, (self.remoteIp, 69))
# self.show()
locPackNum = 0 # 请求包号
saveFile = '' # 保存文件句柄
while True:
recvData, recvAddr = self.socketClient.recvfrom(1024)
recvDataLen = len(recvData)
# 解包
cmdTuple = struct.unpack(b"!HH", recvData[:4])
cmd = cmdTuple[0] # 指令
curPackNum = cmdTuple[1] # 当前包号
if cmd == 3: # 是否为数据包
if curPackNum == 1:
# 以追加的方式打开文件
saveFile = open(self.filename, "ab")
# 包编号是否和上次相等
if locPackNum + 1 == curPackNum:
saveFile.write(recvData[4:]) # 写入数据
locPackNum += 1
# 发送ACK应答
ackBuf = struct.pack(b"!HH", 4, locPackNum)
self.socketClient.sendto(ackBuf, recvAddr)
print("(%d)次接收到数据" % locPackNum)
# 确认为最后一个包
if recvDataLen < 516:
saveFile.close()
print("已经下载完成")
break
elif cmd == 5: # 是否为错误应答
print("error num:%d" % curPackNum)
break
def upload(self):
"""TFTP 上传"""
print("上传启动...")
# 1、发送读请求
filenameLen = str(len(self.filename))
cmdBuf = struct.pack(("!H%ssb5sb" % filenameLen).encode("utf-8"), 2, self.filename.encode("utf-8"), 0, b"octet", 0)
self.socketClient.sendto(cmdBuf, (self.remoteIp, 69))
localPackNum = 1 # 本地包号
sendFile = '' # 文件句柄
while True:
# 2、接收回复
recvData, recvAddr = self.socketClient.recvfrom(1024)
# 3、解包
cmdTuple = struct.unpack(b"!HH", recvData[:4])
cmd = cmdTuple[0] # 指令
curPackNum = cmdTuple[1] # 当前包号
# print(recvData)
if cmd == 4:
# 打开并读取文件
if curPackNum == 0:
sendFile = open(self.filename, "rb")
# ACK应答的包号是否与本地的一样
if localPackNum - 1 == curPackNum:
# 4、读取 512 byte数据
sendData = sendFile.read(512)
# 判断文件是否读取完成
if len(sendData) <= 0:
sendFile.close()
print("上传完成")
break
# 5、打包发送数据
sendDataBuf = struct.pack(b"!HH512s", 3, localPackNum, sendData)
self.socketClient.sendto(sendDataBuf, recvAddr)
# 打印过程
print("(%d)次已发送,数据长度:%d" % (localPackNum, len(sendData)))
localPackNum += 1
elif cmd == 5:
# sendFile.close()
print("error num:%d" % curPackNum)
break
def show(self):
"""测试打印数据"""
recvData = self.socketClient.recvfrom(1024)
print(recvData)
exit()
if __name__ == "__main__":
demo = DownloadClient()
demo.start()