Python中TFTP的理解----下载文件

1.首先要大概理解整个过程

TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
1,简单
2,占用资源小
3,适合传递小文件
4,适合在局域网进行传递
5,端口号为69
6,基于UDP实现

TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输

①需求就是我想从TFTP服务器下载一个文件,我就是客户端
sendto(请求数据,地址1)
②我去发送请求,服务器回复数据包(从我的角度看就是 接收数据包)
recvfrom()
过程很简单,不过有一些细节问题需要仔细研究:

  • 第一就是如果文件太大的话,服务器是不是一次传一部分更优;
  • 第二我(客户端)在收到一部分数据之后是不是应该回应一下,说明我收到了,要不然中间有一部数据有错误,服务器还一直传。这时候应该注意到(参看下方示意图)端口是改变的(动态端口),所以此时的地址2 和地址1并不相同。地址2获取的方式显而易见,肯定是当客户端接收数据的时候获取动态端口。
    udp_socket.sendto(应答数据,地址2)
  • 第三就是传输时应该按照TFTP数据包的格式,解包时也是一样啊。


    TFTP数据包的格式.png

示意图如下:


传输过程

2.代码实现

num01:发送请求

#发送请求 ,至于socket的用法直接百度可以了解
#server_ip就是服务器的IP地址(这个地方我用了Tftpd32,本地打开作为服务器,虚拟机作为客户端编写代码。
#[我之前用虚拟机作为服务器老是出小问题,所以就这样用了])
udpSocket=socket(AF_xx,SOCK_DGRANM)
server_addr = (server_ip,69)
#send_data需要按照TFTP数据包的格式
udp_socket.sendto(send_data,server_addr) #发送请求

关于send_data需要按照TFTP数据包的格式:

  • 上图中读写请求的格式是:操作码+文件名+0+模式+0。操作码需要占2个字节(原因我也不知道,操作码下面的2Bytes就是占两个字节的意思)
  • 那我们就需要处理成要求的格式;这就要借助于struct模块了(用法可参看这篇https://blog.csdn.net/lis_12/article/details/52777983 只看「格式符」就可以了)
    附:操作码功能:
操作码     功能
 1      读请求,即下载
 2      写请求,即上传
 3      表示数据包,即DATA
 4      确认码,即ACK
 5      错误
  • 读写请求:1,'test.jpg',0,'octet',0 (test.jpg是要下载的文件名字,可以是任意需要下载文件的名字;模式:octet这个是固定的,记下来就可以了)
  • 处理成要求的格式:
    send_data=struct.pack(fmt,1,'test.jpg'.encode('utf-8'),0,'octet'.encode('utf-8'),0)
    send_data你可以自己打印试试结果是:b'\x00\x01test.jpg\x00octet\x00'
1需要占两个字节,参看struct格式符
H——unsigned short——integer——2 
 fmt='!H'
test.jpg是字符串,长度为8
 fmt='!H8s'
以此类推:fmt='!H8sb5sb'

num02:接收数据并处理

数据包格式参看上图:操作码+块编号+数据
那么我们就应该进行拆包确认是不是数据(怎么判断?看前面的操作码 3 — 表示数据包,即DATA)

#接收并拆包数据
#一次接收一部分,返回的是元组(data,包含IP和端口的元组)
recv_data,peer_addr = udp_socket.recvfrom(1024)
# 拆包数据
opcode,blockNum = struct.unpack('!HH',recv_data[:4])#拆出来操作码和块编号
#如果操作码是3就是数据包,如果是5的话就是报错了
if opcode == 3: # 表示数据包
    ....
elif opcode == 5:# 出错
    ....

接下来返回的是3的话:

 #1.拆出数据
data_fmt = '!%ds' % (len(recv_data) - 4) #减去4是去掉操作码+块编号
data_content = struct.unpack(data_fmt, recv_data[4:])#解包出来

#2.写入数据(这才是我们的目的)
f = open(file_name,'wb')
f.write(data_content[0])  # 拆出来是元组,bytes对象,write时候需要str字符串
#3.给个回应证明我接收到了
ack_data = struct.pack('!HH',4,blockNum)
udp_socket.sendto(ack_data,peer_addr) # 不能再给server_addr,因为端口号变了
#4.如果数据长度小于 2 + 2 + 512 传输结束 (很好理解 看TFTP数据包的格式.png)
if len(recv_data) < 516:
    print('over')
    f.close()
    break

num03:整理代码,发现问题

from socket import *
import struct 

udp_socket = socket(AF_INET, SOCK_DGRAM)
server_addr = (server_ip,69)
fmt='!H8sb5sb'
send_data=struct.pack(fmt,1,'test.jpg'.encode('utf-8'),0,'octet'.encode('utf-8'),0)
udp_socket.sendto(send_data,server_addr)


# 循环接收和应答
while True:
    recv_data,peer_addr = udp_socket.recvfrom(1024)
    # 拆包数据
    opcode,blockNum = struct.unpack('!HH',recv_data[:4])

    if opcode == 3: # 表示数据包

        # 拆出数据
        data_fmt = '!%ds' % (len(recv_data) - 4)
        data_content = struct.unpack(data_fmt, recv_data[4:])
        f = open(file_name,'wb')
        f.write(data_content[0]) # 拆出来是元组,bytes对象,write时候需要str字符串

        # 打包应答数据
        ack_data = struct.pack('!HH',4,blockNum)
        udp_socket.sendto(ack_data,peer_addr) # 不能再给server_addr,因为端口号变了


        # 如果数据长度小于 2 + 2 + 512 传输结束
        if len(recv_data) < 516:
            print('over')
            f.close()
            break
    elif opcode == 5:# 出错
            ...
  • 第一个问题:f = open(file_name,'wb')

wb:以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。

因为我们是循环接收数据的,所以如果每次接收都运行这行代码会清空以前的数据。所以我们应该在第一次接收数据的时候运行。之后只运行写入的操作就可以了。

        if blockNum == 1: 
            f = open(file_name,'wb')
            # 拆出数据
            data_fmt = '!%ds' % (len(recv_data) - 4)
            data_content = struct.unpack(data_fmt, recv_data[4:])
  • 第二问题:在写入数据的时候应该判断之前是否写过;同理也是通过blockNum(块编号)
if  lastblockNum+1==blockNum:
     f.write(data_content[0])

     ......

       # 打包应答数据
     ack_data = struct.pack('!HH',4,blockNum)
     udp_socket.sendto(ack_data,peer_addr) # 不能再给server_addr,因为端口号变了

     # 当应答完毕,更新lastBlockNum
     lastBlockNum = blockNum
  • 第三个问题:如果操作码是5如何处理
if 操作码==5:
    err_num = blockNum
    # 拆出错误信息
    fmt = "!%ds" % (len(recv_data) - 5)
    err_msg = struct.unpack(fmt,recv_data[4:-1])
    print('出错信息:%s' % err_msg)
    break
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,846评论 6 13
  • 15.1 引言 TFTP(Trivial File Transfer Protocol)即简单文件传送协议,最初打...
    张芳涛阅读 1,042评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • 1.1 udp网络程序-发送数据 Socket函数 mySocket = socket(family, type...
    TENG书阅读 342评论 0 0
  • 百日练,一百天看一百本书,第82天,《黄金时代》30分钟,理解70%,2800字/分 讲了王二因厕所画画给污蔑的事...
    骑了蜗牛闯世界阅读 150评论 0 0