1. TFTP介绍(了解)
-
TFTP:(Trivial File Transfer Protocol)简单文件传输协议
-
使用这个协议,就可以实现简单地文件下载,TFTP端口号是69
-
TFTP介绍:
-
当客户端接收到的数据⼩于516(2字节操作码+2个字节的序号+512字节数据) 时, 就意味着服务器发送完毕了 (如果恰好最后一次数据长度为516,会再发一个长度为0的数据包)
-
构造下载请求数据:“1test.jpg0octet0”
-
import struct
-
cmb_buf = struct.pack(“!H8sb5sb”,1,b“test.jpg”,0,b“octet”,0)
如何保证操作码(1/2/3/4/5)占两个字节?如何保证0占一个字节? -
!H8sb5sb:“ ! ” 表示按照网络传输数据要求的形式来组织数据(占位的格式)
H:表示将后面的 1 替换成占两个字节
8s:相当于8个s(ssssssss)占8个字节
b:占一个字节
-
- struct模块使用:
-
struct模块可以按照指定格式将Python数据转换为字符串,该字符串为字节流
-
struct模块中最重要的三个函数是pack(), unpack(), calcsize()
-
按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)
pack(fmt, v1, v2, …)
-
按照给定的格式(fmt)解析字节流string,返回解析出来的元组
unpack(fmt, string)
-
计算给定的格式(fmt)占用多少字节的内存
calcsize(fmt)
-
-
struct.pack("!HH",4,p_num)
-
cmdTuple = struct.unpack("!HH", recvData[:4])
"""
编写tftp下载需求的 --- 客户端
"""
from socket import *
import struct
udpSocket = socket(AF_INET,SOCK_DGRAM)
# 构建下载请求(按照要求准备格式数据)
# 整体数据如:1bag.jpg0octet0
filename = 'bag.jpg'
server_ip = '192.168.2.119'
send_data = struct.pack('!H%dsb5sb'%len(filename),1,filename.encode(),0,'octet'.encode(),0)
# 创建一个空文件
f = open(filename,'ab')
#发送读写请求
udpSocket.sendto(send_data,(server_ip,69))
# 接收服务器端的数据包信息
message = udpSocket.recvfrom(1024)
# 查看接收的信息的格式:返回的是一个元组 (数据包的内容,(发送方ip,发送方端口号))
# print(message)
# 从数据包里边获取 【数据块 ack_num】的信息
caozuoma,ack_num = struct.unpack('!HH',message[0][:4])
print(caozuoma) #3
print(ack_num) #1
# 额外:做一个异常处理 拿操作码判断作处理
if int(caozuoma)==5:
print('您下载的文件不存在!!!')
f.close()
udpSocket.close()
"""
编写tftp下载需求的 --- 客户端
"""
from socket import *
import struct
udpSocket = socket(AF_INET,SOCK_DGRAM)
# 构建下载请求(按照要求准备格式数据)
# 整体数据如:1bag.jpg0octet0
filename = 'bag.jpg'
server_ip = '192.168.2.119'
send_data = struct.pack('!H%dsb5sb'%len(filename),1,filename.encode(),0,'octet'.encode(),0)
# 创建一个空文件
f = open(filename,'ab')
# 向服务器发送读写请求, 只是一次 服务器端服务的端口号为69
udpSocket.sendto(send_data,(server_ip,69))
# 要循环的接收服务器端返回来的数据包信息 和给服务器端发送确认包信息
while True:
# 接收服务器端返回来的数据包信息
message = udpSocket.recvfrom(1024)
# 从数据包里边获取 【操作码caozuoma,数据块ack_num】的信息
caozuoma, ack_num = struct.unpack('!HH', message[0][:4])
# 获取服务器端为当前这个客户端服务的端口号
rand_port = message[1][1]
# 打印程序跑的次数 ,以及每次所涉及的内容 操作码 数据块编号 每次数据内容的长度,为客户端服务的端口号
print('操作码:%d, 数据块编号:%d, 每次数据内容的长度:%d,为客户端服务的端口号:%d'%(caozuoma,ack_num,len(message[0]),rand_port))
# 额外:做一个异常处理 拿操作码判断作处理
if int(caozuoma) == 5:
print('您下载的文件不存在!!!')
break #跳出循环
# 向创建的空文件里面写数据
f.write(message[0][4:])
# 判断循环结束条件 用数据包的长度来判断
if len(message[0])<516:
print("文件写入完毕")
break
# 给服务器端发送确认包信息
ack_data = struct.pack('!HH',4,ack_num)
udpSocket.sendto(ack_data,(server_ip,rand_port))
f.close()
udpSocket.close()