六、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%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 = 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%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) 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(‘!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 软件进行调试,也可以打印传输数据信息进行调试!