Python实现TFTP

来源:互联网 发布:怎么避免淘宝找相似 编辑:程序博客网 时间:2024/06/05 21:42

一、TFTP协议简单介绍

1、定义

TFTP(Trivial File Transfer Protocol):简单文件传输协议)。

TFTP是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议,传输不复杂、开销不大的文件。端口号固定为69。

TFTP是一个传输文件的简单协议,它基于UDP协议而实现。

2、特点

简单、占用资源少、基于UDP实现、端口号为69、适合在局域网内传输小文件。

3、TFTP支持五种类型的包
opcode operation
1.Read request (RRQ)
2.Write request (WRQ)
3.Data (DATA)
4.Acknowledgment (ACK)
5.Error (ERROR)


二、TFTP数据包格式


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'\x00\x03\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xe1\x08\xbbExif\x00\x00MM\x00*\x00\x00\x00\x08\x00\x07\x01\x12\x00\x03\x00\x00\x00\x01\x00\x01\x00\x00\x01\x1a\x00\x05\x00\x00\x00\x01\x00\x00\x00b\x01\x1b\x00\x05\x00\x00\x00\x01\x00\x00\x00j\x01(\x00\x03\x00\x00\x00\x01\x00\x02\x00\x00\x011\x00\x02\x00\x00\x00\x14\x00\x00\x00r\x012\x00\x02\x00\x00\x00\x14\x00\x00\x00\x86\x87i\x00\x04\x00\x00\x00\x01\x00\x00\x00\x9c\x00\x00\x00\xc8\x00\x00\x00H\x00\x00\x00\x01\x00\x00\x00H\x00\x00\x00\x01Adobe Photoshop 7.0\x002004:06:15 16:14:56\x00\x00\x00\x00\x03\xa0\x01\x00\x03\x00\x00\x00\x01\xff\xff\x00\x00\xa0\x02\x00\x04\x00\x00\x00\x01\x00\x00\x04\x00\xa0\x03\x00\x04\x00\x00\x00\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x06\x01\x03\x00\x03\x00\x00\x00\x01\x00\x06\x00\x00\x01\x1a\x00\x05\x00\x00\x00\x01\x00\x00\x01\x16\x01\x1b\x00\x05\x00\x00\x00\x01\x00\x00\x01\x1e\x01(\x00\x03\x00\x00\x00\x01\x00\x02\x00\x00\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x01&\x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x07\x8d\x00\x00\x00\x00\x00\x00\x00H\x00\x00\x00\x01\x00\x00\x00H\x00\x00\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xed\x00\x0cAdobe_CM\x00\x02\xff\xee\x00\x0eAdobe\x00d\x80\x00\x00\x00\x01\xff\xdb\x00\x84\x00\x0c\x08\x08\x08\t\x08\x0c\t\t\x0c\x11\x0b\n\x0b\x11\x15\x0f\x0c\x0c\x0f\x15\x18\x13\x13\x15\x13\x13\x18\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\r\x0b\x0b\r\x0e\r\x10\x0e\x0e\x10\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11', ('192.168.43.119', 54835))
由于数据太长,一行不方便显示,这里多行展示了TFTP服务器回复客户端的下载请求数据。

2、服务器回复客户端上传请求

(b'\x00\x04\x00\x00', ('192.168.43.119', 62768))

五、TFTP传输过程



六、Python实现TFTP协议

1、客户端下载文件参考程序

#coding=utf-8#导包import sysimport structfrom socket import *#全局变量g_server_ip = ''g_downloadFileName = ''#运行程序格式不正确def run_test():"判断运行程序传入参数是否有错"global g_server_ipglobal g_downloadFileNameif 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%dsb5sb'%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 = Falsebreakelse:print(packetOpt[0])break# 接收完成,退出程序。if len(recvData) < 516:downloadFlag = Trueprint("%s文件下载完毕!"%g_downloadFileName)breakif downloadFlag == True:f.close()else:os.unlink(g_downloadFileName) #没有下载的文件,就删除刚创建的文件。#调用main函数if __name__ == '__main__':main()

2、客户端上传文件程序

#coding=utf-8# 导包import sysimport structfrom socket import *# 全局变量g_server_ip = ''g_uploadFileName = ''#运行程序格式不正确def run_test():"判断运行程序传入参数是否有错"global g_server_ipglobal g_uploadFileNameif 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%dsb5sb'%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) < 516 or packetNum[0] != fileNum:print("%s文件上传成功!"%g_uploadFileName)breakfileNum += 1# 关闭文件f.close()# 关闭套接字s.close()#调用main函数if __name__ == '__main__':main()

3、服务器参考程序

#coding=utf-8# 导包import sysimport structfrom 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 += 1if len(recvData) < 516: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) < 516: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,端口号69s.bind(('', 69))print("tftp服务器成功启动!")print("正在运行中...")while True:# 接收客户端发送的消息recvData, clientInfo = s.recvfrom(1024)  # 第一次客户连接69端口#print(clientInfo)# 解包if struct.unpack('!b5sb', 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 软件进行调试,也可以打印传输数据信息进行调试!