[Python] socket实现TFTP上传和下载

2023-05-16

[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、源代码。

  __博客园原文地址:https://www.cnblogs.com/reader/p/10016738.html

二、TFTP协议介绍(参考网络,详情可搜索)

  TFTP(Trivial File Transfer Protocol,简单文件传输协议),是TCP/IP协议族中的一个用来在客户端与服务端(C/S架构)之间进行文件传输的协议。

 2.1、特点

简单、占用资源少
适合小文件传输
适合在局域网中进行传输
端口号为 69
基于UDP实现

 2.2、TFTP下载过程分析:

  当打开一个tftpd作为服务端,会默认监听69端口,所以客户端发送数据到服务端都是经过69端口。
Alt

  下载的数据流过程如上图所示,客户端首次发送需要下载的文件名到服务端,(文件存在)服务端收到后会返回该文件的第一个包,客户端收到后本地保存然后再发送ACK应答包给服务端,如此往来多次,一发一答,即实现了文件的下载。

 2.3、TFTP操作码与数据格式:

操作码功能
1读请求,即下载
2写请求,即上传
3表示数据包,即DATA
4确认码,即ACK
5错误

Alt

 2.4、差错码以及对应的提示:

Alt

 2.5、TFTP上传过程分析(此处做简单文件说明,可参考下面源码或自行搜索):

  上传的基本流程:客户端发送写请求(操作码为2)到服务端,如果可以进行上传,服务端会返回ACK应答包,客户端收到后即可进行第一个数据包发送,进而服务端收到后会返回ACK应打包,如此多次,当客户端文件读取完成,即可退出上传,此时上传完成。

三、程序运行图示和分析fmt

 3.1运行起来的tftpd服务端如下所示:

ALT

 3.2、下载过程:

  运行脚本传入三个参数:方法选择、服务端IP和文件名
在这里插入图片描述

  3.3、下载过程

在这里插入图片描述

 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的字符串等:
在这里插入图片描述

  fmt首个字符
fmt首个字符

  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()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[Python] socket实现TFTP上传和下载 的相关文章

随机推荐