tftp协议给服务器上传数据,TFTP连接过程详解

苍和裕
2023-12-01

概述

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 包,查看其具体内容。

 类似资料: