1、读写请求
操作码 + 文件名 + 0 + 模式 + 0
2Bytes String 1Byte String 1Byte
当操作码的取值为1时,表示RD 读请求;当操作码的取值为2时,表示WE 写请求。
2、数据包
操作码 + 块编码 + 数据
2Bytes 2Bytes 512Bytes
数据包操作码值为3。
3、ACK
操作码 + 块编码
2Bytes 2Bytes
ACK 操作码值为4。
4、ERROR
操作码 + 差错码 + 差错信息 + 0
2Bytes 2Bytes String 1Byte
ERROR 操作码值为5。
注意:
1、当客户端接收到的数据小于516字节时,表示服务器发送数据完成!
2、块编码从0开始,每次加1,它的范围是[0, 65535]。
三、TFTP协议过程分析
1、下载过程
第一步:客户端给服务器发送下载请求,数据格式为(操作码1+文件名+0+模式+0)。
第二步:服务器接收到请求之后,回复客户端消息,数据格式为元组类型。如下所示:(操作码3+块编码0+数据, (IP号, 端口号))。
第三步:客户端每接受一次数据,都要回复服务器一次ACK信号。
第四步:直到客户端接收到的数据小于516个字节,才说明服务器发送完毕!
2、上传过程
第一步:客户端给服务器发送上传请求,数据格式为(操作码2+文件名+0+模式+0)。
第二步:服务器接收到请求之后,回复客户端ACK消息,数据格式为元组类型。如下所示:(操作码4+块编码0, (IP号, 端口号))。
第三步:客户端每发送一次数据,服务器都要回复一次ACK信号。
第四步:直到客户端发送完数据才结束。
四、TFTP具体传输数据
1、服务器回复客户端下载请求
(b‘x00x03x00x01xffxd8xffxe0x00x10JFIFx00x01x02x01x00Hx00Hx00x00xffxe1x08xbbExifx00x00MMx00*x00
x00x00x08x00x07x01x12x00x03x00x00x00x01x00x01x00x00x01x1ax00x05x00x00x00x01x00x00x00bx01x1b
x00x05x00x00x00x01x00x00x00jx01(x00x03x00x00x00x01x00x02x00x00x011x00x02x00x00x00x14x00x00x00r
x012x00x02x00x00x00x14x00x00x00x86x87ix00x04x00x00x00x01x00x00x00x9cx00x00x00xc8x00x00x00Hx00
x00x00x01x00x00x00Hx00x00x00x01Adobe Photoshop 7.0x002004:06:15 16:14:56x00x00x00x00x03xa0x01x00x03x00
x00x00x01xffxffx00x00xa0x02x00x04x00x00x00x01x00x00x04x00xa0x03x00x04x00x00x00x01x00x00x03x00
x00x00x00x00x00x00x00x06x01x03x00x03x00x00x00x01x00x06x00x00x01x1ax00x05x00x00x00x01x00x00x01
x16x01x1bx00x05x00x00x00x01x00x00x01x1ex01(x00x03x00x00x00x01x00x02x00x00x02x01x00x04x00x00x00
x01x00x00x01&x02x02x00x04x00x00x00x01x00x00x07x8dx00x00x00x00x00x00x00Hx00x00x00x01x00x00x00Hx00
x00x00x01xffxd8xffxe0x00x10JFIFx00x01x02x01x00Hx00Hx00x00xffxedx00x0cAdobe_CMx00x02xffxeex00x0eAdobe
x00dx80x00x00x00x01xffxdbx00x84x00x0cx08x08x08tx08x0cttx0cx11x0bnx0bx11x15x0fx0cx0cx0fx15x18x13
x13x15x13x13x18x11x0cx0cx0cx0cx0cx0cx11x0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0c
x0cx0cx0cx0cx0cx0cx0cx0cx0cx01rx0bx0brx0erx10x0ex0ex10x14x0ex0ex0ex14x14x0ex0ex0ex0ex14x11x0cx0c
x0cx0cx0cx11x11x0cx0cx0cx0cx0cx0cx11x0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0cx0c
x0cx0cx0cx0cx0cx0cx0cxffxc0x00x11’, (‘192.168.43.119’, 54835))
由于数据太长,一行不方便显示,这里多行展示了TFTP服务器回复客户端的下载请求数据。
2、服务器回复客户端上传请求
(b‘x00x04x00x00’, (‘192.168.43.119’, 62768))
五、TFTP传输过程
六、Python实现TFTP协议
1、客户端下载文件参考程序
#coding=utf-8
#导包
import sys
import struct
from socket import *
#全局变量
g_server_ip = ‘’
g_downloadFileName = ‘’
#运行程序格式不正确
def run_test():
“判断运行程序传入参数是否有错”
global g_server_ip
global g_downloadFileName
if len(sys.argv) != 3:
print(“运行程序格式不正确”)
print(‘-’*30)
print(“tips:”)
print(“python3 tftp_download.py 192.168.1.1 test.jpg”)
print(‘-’*30)
exit()
else:
g_server_ip = sys.argv[1]
g_downloadFileName = sys.argv[2]
#print(g_server_ip, g_downloadFileName)
#主程序
def main():
run_test()
# 打包
sendDataFirst = struct.pack(‘!H%d***5***’%len(g_downloadFileName), 1, g_downloadFileName.encode(‘gb2312’), 0, ‘octet’.encode(‘gb2312’), 0)
# 创建UDP套接字
s = socket(AF_INET, SOCK_DGRAM)
# 发送下载文件请求数据到指定服务器
s.sendto(sendDataFirst, (g_server_ip, 69)) #第一次发送, 连接tftp服务器
downloadFlag = True #表示能够下载数据,即不擅长,如果是false那么就删除
fileNum = 0 #表示接收文件的序号
# 以二进制格式创建新文件
f = open(g_downloadFileName, ‘wb’)
while True:
#3. 接收服务发送回来的应答数据
responseData = s.recvfrom(1024)
#print(responseData)
recvData, serverInfo = responseData
# 解包
packetOpt = struct.unpack(“!H”, recvData[:2]) #操作码
packetNum = struct.unpack(“!H”, recvData[2:4]) #块编号
#print(packetOpt, packetNum)
# 接收到数据包
if packetOpt[0] == 3: #optNum是一个元组(3,)
# 计算出这次文件的序号,是上一次接收到的+1。
fileNum += 1
# 文件超过了65535 那么就又从0开始计数。
if fileNum == 65536:
fileNum = 0
# 包编号是否和上次相等
if fileNum == packetNum[0]:
f.write(recvData[4:]) #写入文件
fileNum = packetNum[0]
# 整理ACK的数据包
ackData = struct.pack(“!HH”, 4, packetNum[0])
s.sendto(ackData, serverInfo)
# 错误应答
elif packetOpt[0] == 5:
print(“sorry,没有这个文件!”)
downloadFlag = False
break
else:
print(packetOpt[0])
break
# 接收完成,退出程序。
if len(recvData) downloadFlag = True
print(“%s文件下载完毕!”%g_downloadFileName)
break
if downloadFlag == True:
f.close()
else:
os.unlink(g_downloadFileName) #没有下载的文件,就删除刚创建的文件。
#调用main函数
if __name__ == ‘__main__’:
main()
2、客户端上传文件程序
#coding=utf-8
# 导包
import sys
import struct
from socket import *
# 全局变量
g_server_ip = ‘’
g_uploadFileName = ‘’
#运行程序格式不正确
def run_test():
“判断运行程序传入参数是否有错”
global g_server_ip
global g_uploadFileName
if len(sys.argv) != 3:
print(“运行程序格式不正确”)
print(‘-’*30)
print(“tips:”)
print(“python3 tftp_upload.py 192.168.1.1 test.jpg”)
print(‘-’*30)
exit()
else:
g_server_ip = sys.argv[1]
g_uploadFileName = sys.argv[2]
#print(g_server_ip, g_uploadFileName)
#主程序
def main():
run_test()
# 打包
sendDataFirst = struct.pack(‘!H%d***5***’%len(g_uploadFileName), 2, g_uploadFileName.encode(‘gb2312’), 0, ‘octet’.encode(‘gb2312’), 0)
# 创建UDP套接字
s = socket(AF_INET, SOCK_DGRAM)
# 发送上传文件请求到指定服务器
s.sendto(sendDataFirst, (g_server_ip, 69)) #第一次发送, 连接tftp服务器
fileNum = 0 #表示接收文件的序号
# 以二进制格式打开文件
f = open(g_uploadFileName, ‘rb’)
# 第一次接收数据
responseData = s.recvfrom(1024)
# print(responseData)
recvData, serverInfo = responseData
#print(recvData)
#print(serverInfo)
# 解包
packetOpt = struct.unpack(“!H”, recvData[:2]) #操作码
packetNum = struct.unpack(“!H”, recvData[2:4]) #块编号
#print(packetOpt, packetNum)
if packetOpt[0] == 5:
print(“tftp服务器发生错误!”)
exit()
while True:
# 从文件中读取512字节数据
readFileData = f.read(512)
# 打包
sendData = struct.pack(‘!HH’, 3, fileNum) + readFileData
# 发送数据到tftp服务器
s.sendto(sendData, serverInfo) #第二次发给服务器的随机端口
# 接受服务器回传数据
recvData, serverInfo = s.recvfrom(1024)
#print(recvData)
# 解包
packetOpt = struct.unpack(“!H”, recvData[:2]) #操作码
packetNum = struct.unpack(“!H”, recvData[2:4]) #块编号
if packetOpt[0] == 5:
print(“tftp服务器发生错误!”)
exit()
if len(sendData) print(“%s文件上传成功!”%g_uploadFileName)
break
fileNum += 1
# 关闭文件
f.close()
# 关闭套接字
s.close()
#调用main函数
if __name__ == ‘__main__’:
main()
3、服务器参考程序
#coding=utf-8
# 导包
import sys
import struct
from socket import *
from threading import Thread
‘’‘
利用多线程的机制,来实现tftp服务器同时进行上传和下载功能。
’‘’
# 客户端上传线程
def upload_thread(fileName, clientInfo):
“负责处理客户端上传文件”
fileNum = 0 #表示接收文件的序号
# 以二进制方式打开文件
f = open(fileName, ‘wb’)
# 创建UDP套接字
s = socket(AF_INET, SOCK_DGRAM)
# 打包
sendDataFirst = struct.pack(“!HH”, 4, fileNum)
# 回复客户端上传请求
s.sendto(sendDataFirst, clientInfo) #第一次用随机端口发送
while True:
# 接收客户端发送的数据
responseData = s.recvfrom(1024) #第二次客户连接我随机端口
# print(responseData)
recvData, clientInfo = responseData
#print(recvData, clientInfo)
# 解包
packetOpt = struct.unpack(“!H”, recvData[:2]) #操作码
packetNum = struct.unpack(“!H”, recvData[2:4]) #块编号
#print(packetOpt, packetNum)
# 客户端上传数据
if packetOpt[0] == 3 and packetNum[0] == fileNum:
# 保存数据到文件中
f.write(recvData[4:])
# 打包
sendData = struct.pack(“!HH”, 4, fileNum)
# 回复客户端ACK信号
s.sendto(sendData, clientInfo) #第二次用随机端口发
fileNum += 1
if len(recvData) print(“用户”+str(clientInfo), end=‘’)
print(‘:上传’+fileName+‘文件完成!’)
break
# 关闭文件
f.close()
# 关闭UDP套接字
s.close()
# 退出上传线程
exit()
# 客户端下载线程
def download_thread(fileName, clientInfo):
“负责处理客户端下载文件”
# 创建UDP套接字
s = socket(AF_INET, SOCK_DGRAM)
fileNum = 0 #表示接收文件的序号
try:
f = open(fileName,‘rb’)
except:
# 打包
errorData = struct.pack(‘!HHHb’, 5, 5, 5, fileNum)
# 发送错误信息
s.sendto(errorData, clientInfo) #文件不存在时发送
exit() #退出下载线程
while True:
# 从本地服务器中读取文件内容512字节
readFileData = f.read(512)
fileNum += 1
# 打包
sendData = struct.pack(‘!HH’, 3, fileNum) + readFileData
# 向客户端发送文件数据
s.sendto(sendData, clientInfo) #数据第一次发送
if len(sendData) print(“用户”+str(clientInfo), end=‘’)
print(‘:下载’+fileName+‘文件完成!’)
break
# 第二次接收数据
responseData = s.recvfrom(1024)
# print(responseData)
recvData, clientInfo = responseData
#print(recvData, clientInfo)
#解包
packetOpt = struct.unpack(“!H”, recvData[:2]) #操作码
packetNum = struct.unpack(“!H”, recvData[2:4]) #块编号
#print(packetOpt, packetNum)
if packetOpt[0] != 4 or packetNum[0] != fileNum:
print(“文件传输错误!”)
break
# 关闭文件
f.close()
# 关闭UDP套接字
s.close()
# 退出下载线程
exit()
# main函数
def main():
# 创建UDP套接字
s = socket(AF_INET, SOCK_DGRAM)
# 解决重复绑定端口
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 绑定任意IP,端口号69
s.bind((‘’, 69))
print(“tftp服务器成功启动!”)
print(“正在运行中。。.”)
while True:
# 接收客户端发送的消息
recvData, clientInfo = s.recvfrom(1024) # 第一次客户连接69端口
#print(clientInfo)
# 解包
if struct.unpack(‘!b5***’, recvData[-7:]) == (0, b‘octet’, 0):
opcode = struct.unpack(‘!H’,recvData[:2]) # 操作码
fileName = recvData[2:-7].decode(‘gb2312’) # 文件名
# 请求下载
if opcode[0] == 1:
t = Thread(target=download_thread, args=(fileName, clientInfo))
t.start() # 启动下载线程
# 请求上传
elif opcode[0] == 2:
t = Thread(target=upload_thread, args=(fileName, clientInfo))
t.start() # 启动上传线程
# 关闭UDP套接字
s.close()
# 调用main函数
if __name__ == ‘__main__’:
main()
调试可以通过Wireshark 软件进行调试,也可以打印传输数据信息进行调试!