TFTP 详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/young2415/article/details/91125718

概述

TFTP,全称是 Trivial File Transfer Protocol(简单文件传输协议),基于 UDP 实现,该协议简单到只能从远程服务器读取数据或向远程服务器上传数据。TFTP 有三种模式:netascii,这是8位的ASCII码形式;另一种是octet,这是8位源数据类型;最后一种 mail 已经不再支持,它将返回的数据直接返回给用户而不是保存为文件。

虽然 TFTP 不具备通常的 FTP 的许多功能,但是学习 TFTP 可以帮助我们了解网络通信协议的基本工作过程和原理,对后续学习更加复杂的协议有很大的帮助作用。

首先看一下 TFTP 的包的类型,TFTP 有 5 种类型的包:
在这里插入图片描述

建立连接

默认情况下,作为 TFTP 服务器的主机 A 会监听 69 端口,当作为客户端的主机 B 想要下载或上传文件时,会向主机 A 的 69 端口发送包含读文件(下载)请求或写文件(上传)请求的数据包。主机 A 收到读写请求后,会打开另外一个随机的端口,通过这个端口向主机 B 发送确认包、数据包或者错误包。

下载

客户端向服务器的 69 端口(通常情况下)发送一个读请求,服务器收到这个读请求以后,会打开另外一个随机的端口(假设端口号是 59509),然后在它默认的路径下寻找这个文件,找到这个文件以后,每次读入文件的 512 个字节,通过端口 59509 将这 512 个字节放入数据包中发送给客户端,数据包中还包含了操作码和数据块的编号,块编号从 1 开始计数;客户端收到数据包以后,会向服务器的 59509 端口发送一个确认包,里面包含了它收到的数据包的块编号;服务器收到确认包以后,继续发送文件的下一个 512 个字节。

如此循环往复,直到文件的末尾,最后一个数据包的数据块的大小会小于 512 个字节,这时服务器就认为传输已经结束,等他接收到这最后一个数据包的确认包之后就会主动关闭连接。而客户端收到这个小于 512 个字节的数据包后也认为传输已经结束,发送完确认包之后也会关闭连接。

也许会有一种极端情况,就是文件的大小正好是 512 字节的倍数,这样的话,最后一个数据包的大小也是 512 个字节,这时服务器发送完包含文件数据的数据包以后,还会额外发送一个包含 0 字节的数据包,作为最后一个数据包,这样就可以保证客户端收到的最后一个数据包的大小总是小于 512 个字节的。也就是说,对于客户端而言,只要它收到的数据包的大小小于 512 个字节,它就认为传输已经结束,它就会关闭连接。

下面是 TFTP 下载图示:
在这里插入图片描述

上传

客户端向服务器的 69 端口(通常情况下)发送一个写请求,服务器收到这个写请求以后,会打开另外一个随机的端口(假设端口号是 59509),向客户端发送一个确认包,其中块编号是 0,以此来告诉客户端自己已经准备好接收文件,并且告诉客户端自己接收文件的端口号。

然后客户端就开始向服务器的 59509 端口发送数据包,服务器收到数据包后向客户端发送确认包,直到整个文件发送完毕。这个过程和下载是一样的,只不过双方的角色互换了,客户端成了发数据的一方,而服务器是接收数据的一方。

下面是 TFTP 上传图示:
在这里插入图片描述

错误机制

TFTP 提供了一些错误机制,若出现错误,服务器会向客户端发送 ERROR 包,包格式如下:
在这里插入图片描述
前两个字节是操作码,值是 5,代表这是一个 ERROR 包。接下来两个字节是差错码,代表了错误的类型,下面是不同的差错码对应的错误类型:

差错码 含义
1 File not found. (文件未找到,服务器未找到下载请求中指定的文件)
2 Access violation. (访问违规,程序对于服务器的默认路径没有写权限导致的)
3 Disk full or allocation exceeded. (磁盘已满或超出分配,上传文件时可能会出现这个错误)
4 Illegal TFTP operation. (非法的 TFTP 操作,服务器无法识别 TFTP 包中的操作码)
5 Unknown transfer ID. (未知的传输标识)
6 File already exists. (文件已存在,要上传的文件已存在于服务器中)
7 No such user. (没有该用户)

接下来的 n 个字节用于存放错误信息,这部分可以由程序员自己决定存放什么信息。最后一个字节是 0,用来标识结尾。

代码实现

下面以下载文件为例,用 Python 实现一个 TFTP 的客户端。

实验环境

  • Windows 10
  • VMware 15,里面安装了 Windows 10 操作系统的虚拟机
  • Python 3.7
  • tftpd64(一个支持 TFTP 协议的软件,安装到 Windows 10 虚拟机里面作为服务器)

下面是 Python 代码:

#filename: tftp_client_download.py
import struct
from socket import *

'''
第一个参数是要下载的文件名,类型是字符串
第二个参数是服务器的IP地址和端口号,类型是元组,
元组中有两个元素,第一个元素是IP地址,类型是字符串,第二个元素是端口号,类型是整数。
比如:('192.168.1.2', 69)
'''
def download(file_name, servAddr):
    file_name_byte_array = file_name.encode('gb2312')
    #组包,octet 代表TFTP协议的一种模式
    sendData = struct.pack('!H'+str(len(file_name_byte_array))+'sb5sb', 
                           1, file_name_byte_array, 0, b'octet', 0) 

    udpSocket = socket(AF_INET, SOCK_DGRAM)
    udpSocket.sendto(sendData, servAddr)

    newFile = open(file_name, 'wb')
    while True:
        #等待接收数据
        recvInfo = udpSocket.recvfrom(1024) #1024表示本次接受的最大字节数
        transPort = recvInfo[1][1] #传输端口
        data = recvInfo[0] #TFTP数据包的字节流
        len_data = len(data)
        result = struct.unpack("!H", data[:2]) #解包
        opcode = result[0] #获取操作码
        if opcode == 3: #如果操作码是3,说明是DATA包
            result = struct.unpack('!H'+str(len_data-4)+'s', data[2:len_data])
            block = result[0] #获取块编号
            fileStream = result[1] #文件字节流
            newFile.write(fileStream)

            #向服务器发送一个确认包
            ackInfo = struct.pack('!HH', 4, block)
            udpSocket.sendto(ackInfo, (servAddr[0], transPort))
            if len(fileStream) < 512:
                break
        elif opcode == 5: #如果操作码是5,说明是ERROR包
            result = struct.unpack('!H'+str(len_data-5)+'s', data[2:len_data-1])
            print('传输出现异常!')
            print(result[1].decode('gb2312')) #输出错误信息
            break
    newFile.close()
    udpSocket.close()

def main():
    #服务器的IP地址
    serverIP = '192.168.133.135'
    #服务器监听端口,默认是69,这只是用来监听客户端请求的端口,另外还有操作系统随机分配的用来传输文件的端口
    serverPort = 69 
    servAddr = (serverIP, serverPort)
    filename = 'zoro.png'
    download(filename, servAddr)
   
if __name__ == '__main__':
    main()

实验过程

在虚拟机中打开 tftpd64,选择 Tftp Server 选项卡,点击右上角的 Browse 按钮,选择一个主目录,将来收到下载请求的时候,程序会在你选择的这个目录里查找请求下载的文件。
在这里插入图片描述

查看一下虚拟机的IP地址,将上面 Python 代码中的服务器 IP 地址改成虚拟机的IP地址,在真实的电脑中运行 Python 代码。
在这里插入图片描述
如图所示,若没有出现任何错误提示,就文件说明下载成功,文件已经下载到了当前目录下。

延伸

在代码运行过程中,还可以打开 Wireshark,抓取 TFTP 包,查看其具体内容。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/young2415/article/details/91125718