• [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()
  • 相关阅读:
    IE9发布会,有想去的联系我,有赠票(3月21日,周一)
    HTML5时代的浏览器全面测试
    CTO门的windows 7 要不要买?是不是正版?
    约束与索引
    Linux关机命令详解
    CSS XSLT
    xml xslt中的空格输出处理
    Jmeter笔记(15)随机取 用户定义的变量
    $.getJSON不执行的原因
    更改eclipse中java和jsp文件字体的大小
  • 原文地址:https://www.cnblogs.com/reader/p/10016738.html
Copyright © 2020-2023  润新知