参考链接: Python中的打包pack和拆包unpack参数
Num01–>TFTP协议介绍
TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
1,简单 2,占用资源小 3,适合传递小文件 4,适合在局域网进行传递 5,端口号为69 6,基于UDP实现
Num02–>TFTP下载过程
TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输
当服务器找到需要现在的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端
如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来
因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的
因为需要从服务器上下载文件时,文件可能不存在,那么此时服务器就会发送一个错误的信息过来,为了区分服务发送的是文件内容还是错误的提示信息,所以又用了2个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面
操作码 功能
1 读请求,即下载
2 写请求,即上传
3 表示数据包,即DATA
4 确认码,即ACK
5 错误
因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)
为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了
Num03–>TFTP数据包的格式
Num04–>TFTP客户端案例编写
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
from socket import *
import struct #为了实现打包struct.pack()和拆包struct.unpack()数据
import sys
# python3 05-xx.py 192.168.105.125 bb.jpg
def main():
if len(sys.argv) < 3:
sys.exit('usage : python3 %s ip filename' % sys.argv[0])
#server_ip = '192.168.105.125'
#file_name = 'bb.jpg'
server_ip = sys.argv[1]
file_name = sys.argv[2]
udp_socket = socket(AF_INET, SOCK_DGRAM)
server_addr = (server_ip,69)
# 打包数据
# !表示网络字节序,H表示2bytes无符号整数,
# 5s表示长度为5字符串
# B表示1byte的无符号整数
fmt = '!H%dsB5sB' % len(file_name)
send_data = struct.pack(fmt,1,file_name.encode() ,0,b'octet',0)
#send_data = struct.pack(fmt,1,file_name ,0,b'octet',0)
udp_socket.sendto(send_data,server_addr)
f = None # 文件对象
#上一次blockNum
lastBlockNum = 0
# 循环接收和应答
while True:
recv_data,peer_addr = udp_socket.recvfrom(1024)
# 拆包数据
opcode,blockNum = struct.unpack('!HH',recv_data[:4])
if opcode == 3: # 表示数据包
# 写入文件
# 1打开文件
# 第一次收到服务器发送数据包
if blockNum == 1:
f = open(file_name,'wb')
# 拆出数据
data_fmt = '!%ds' % (len(recv_data) - 4)
data_content = struct.unpack(data_fmt, recv_data[4:])
# 写入文件之前判断写过没有
# if 这一次blockNum == 上一次blockNum + 1
if lastBlockNum + 1 == blockNum:
#print(data_content[0])
f.write(data_content[0]) # 拆出来是元组,bytes对象,write时候需要str字符串
# 打包应答数据
ack_data = struct.pack('!HH',4,blockNum)
udp_socket.sendto(ack_data,peer_addr) # 不能再给server_addr,因为端口号变了
# 当应答完毕,更新lastBlockNum
lastBlockNum = blockNum
# 如果数据长度小于 2 + 2 + 512 传输结束
if len(recv_data) < 516:
print('over')
f.close()
break
elif opcode == 5:# 出错
err_num = blockNum
# 拆出错误信息
fmt = "!%ds" % (len(recv_data) - 5)
err_msg = struct.unpack(fmt,recv_data[4:-1])
print('出错信息:%s' % err_msg)
break
if __name__ == "__main__":
main()