一、TFTP协议介绍
TFTP(Trivial File Transfer Protocol,简单文件传输协议)
是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议
特点:
- 简单
- 占用资源小
- 适合传递小文件
- 适合在局域网进行传递
- 端口号为69
- 基于UDP实现
二、TFTP下载过程
TFTP服务器默认监听69号端口
当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送
服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输
当服务器找到需要现在的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端。如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来
因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的
因为需要从服务器上下载文件时,文件可能不存在,那么此时服务器就会发送一个错误的信息过来,为了区分服务发送的是文件内容还是错误的提示信息,所以又用了2个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面操作码 | 功能 |
---|---|
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据包,即DATA |
4 | 确认码,即ACK |
5 | 错误 |
为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了
TFTP数据包的格式如下:
三、实例代码
# -*- coding:utf-8 -*- import struct from socket import * import time import os def main(): # 0. 获取要下载的文件名字: downloadFileName = raw_input("请输入要下载的文件名:") # 1.创建socket udpSocket = socket(AF_INET, SOCK_DGRAM) requestFileData = struct.pack("!H%dsb5sb" % len(downloadFileName), 1, downloadFileName, 0, "octet", 0) # 2. 发送下载文件的请求 udpSocket.sendto(requestFileData, ("192.168.119.215", 69)) flag = True # 表示能够下载数据,即不擅长,如果是false那么就删除 num = 0 f = open(downloadFileName, "w") while True: # 3. 接收服务发送回来的应答数据 responseData = udpSocket.recvfrom(1024) # print(responseData) recvData, serverInfo = responseData opNum = struct.unpack("!H", recvData[:2]) packetNum = struct.unpack("!H", recvData[2:4]) print(packetNum[0]) # print("opNum=%d"%opNum) # print(opNum) # if 如果服务器发送过来的是文件的内容的话: if opNum[0] == 3: # 因为opNum此时是一个元组(3,),所以需要使用下标来提取某个数据 # 计算出这次应该接收到的文件的序号值,应该是上一次接收到的值的基础上+1 num = num + 1 # 如果一个下载的文件特别大,即接收到的数据包编号超过了2个字节的大小 # 那么会从0继续开始,所以这里需要判断,如果超过了65535 那么就改为0 if num == 65536: num = 0 # 判断这次接收到的数据的包编号是否是 上一次的包编号的下一个 # 如果是才会写入到文件中,否则不能写入(因为会重复) if num == packetNum[0]: # 把收到的数据写入到文件中 f.write(recvData[4:]) num = packetNum[0] # 整理ACK的数据包 ackData = struct.pack("!HH", 4, packetNum[0]) udpSocket.sendto(ackData, serverInfo) elif opNum[0] == 5: print("sorry,没有这个文件....") flag = False # time.sleep(0.1) if len(recvData) < 516: break if flag == True: f.close() else: os.unlink(downloadFileName) # 如果没有要下载的文件,那么就需要把刚刚创建的文件进行删除 if __name__ == '__main__': main()
操作码 | 功能 |
---|---|
1 | 读请求,即下载 |
2 | 写请求,即上传 |
3 | 表示数据包,即DATA |
4 | 确认码,即ACK |
5 | 错误 |