參考鏈接: Python中的打包pack和拆包unpack參數
Num01–>TFTP協議介紹?
?
?TFTP(Trivial File Transfer Protocol,簡單文件傳輸協議)?
?是TCP/IP協議族中的一個用來在客戶端與服務器之間進行簡單文件傳輸的協議?
?特點:?
?1,簡單? 2,占用資源小? 3,適合傳遞小文件? 4,適合在局域網進行傳遞? 5,端口號為69? 6,基于UDP實現?
?
Num02–>TFTP下載過程?
?
?TFTP服務器默認監聽69號端口?
?當客戶端發送“下載”請求(即讀請求)時,需要向服務器的69端口發送?
?服務器若批準此請求,則使用一個新的、臨時的 端口進行數據傳輸?
?
?
?
?當服務器找到需要現在的文件后,會立刻打開文件,把文件中的數據通過TFTP協議發送給客戶端?
?如果文件的總大小較大(比如3M),那么服務器分多次發送,每次會從文件中讀取512個字節的數據發送過來?
?因為發送的次數有可能會很多,所以為了讓客戶端對接收到的數據進行排序,所以在服務器發送那512個字節數據的時候,會多發2個字節的數據,用來存放序號,并且放在512個字節數據的前面,序號是從1開始的?
?因為需要從服務器上下載文件時,文件可能不存在,那么此時服務器就會發送一個錯誤的信息過來,為了區分服務發送的是文件內容還是錯誤的提示信息,所以又用了2個字節 來表示這個數據包的功能(稱為操作碼),并且在序號的前面?
?
操作碼? ? ?功能
?1? ? ? 讀請求,即下載
?2? ? ? 寫請求,即上傳
?3? ? ? 表示數據包,即DATA
?4? ? ? 確認碼,即ACK
?5? ? ? 錯誤?
?
?因為udp的數據包不安全,即發送方發送是否成功不能確定,所以TFTP協議中規定,為了讓服務器知道客戶端已經接收到了剛剛發送的那個數據包,所以當客戶端接收到一個數據包的時候需要向服務器進行發送確認信息,即發送收到了,這樣的包成為ACK(應答包)?
?為了標記數據已經發送完畢,所以規定,當客戶端接收到的數據小于516(2字節操作碼+2個字節的序號+512字節數據)時,就意味著服務器發送完畢了?
?
Num03–>TFTP數據包的格式?
?
Num04–>TFTP客戶端案例編寫?
#! /usr/bin/env python3
# -*- coding:utf-8 -*-?
?
from socket import *
import struct #為了實現打包struct.pack()和拆包struct.unpack()數據
import sys
?
# python3 05-xx.py 192.168.105.125 bb.jpg
def main():
?
? ? if len(sys.argv) < 3:
? ? ? ? sys.exit('usage : python3? %s ip filename' % sys.argv[0])
?
? ? #server_ip = '192.168.105.125'
? ? #file_name = 'bb.jpg'
?
? ? server_ip = sys.argv[1]
? ? file_name = sys.argv[2]
?
? ? udp_socket = socket(AF_INET, SOCK_DGRAM)
? ? server_addr = (server_ip,69)
?
? ? #? 打包數據
? ? # !表示網絡字節序,H表示2bytes無符號整數,
? ? #? 5s表示長度為5字符串
? ? #? B表示1byte的無符號整數
? ? fmt = '!H%dsB5sB' % len(file_name)
? ? send_data = struct.pack(fmt,1,file_name.encode() ,0,b'octet',0)
? ? #send_data = struct.pack(fmt,1,file_name ,0,b'octet',0)
?
? ? udp_socket.sendto(send_data,server_addr)
?
? ? f =? None # 文件對象
? ? #上一次blockNum
? ? lastBlockNum = 0
?
? ? # 循環接收和應答
? ? while True:
? ? ? ? recv_data,peer_addr = udp_socket.recvfrom(1024)
? ? ? ? # 拆包數據
? ? ? ? opcode,blockNum = struct.unpack('!HH',recv_data[:4])
?
? ? ? ? if opcode == 3: # 表示數據包
? ? ? ? ? ? # 寫入文件
? ? ? ? ? ? # 1打開文件
? ? ? ? ? ? # 第一次收到服務器發送數據包
? ? ? ? ? ? if blockNum == 1:?
? ? ? ? ? ? ? ? f = open(file_name,'wb')
?
? ? ? ? ? ? # 拆出數據
? ? ? ? ? ? data_fmt = '!%ds' % (len(recv_data) - 4)
? ? ? ? ? ? data_content = struct.unpack(data_fmt, recv_data[4:])
?
? ? ? ? ? ? # 寫入文件之前判斷寫過沒有
? ? ? ? ? ? # if 這一次blockNum == 上一次blockNum + 1
? ? ? ? ? ? if lastBlockNum + 1 == blockNum:
? ? ? ? ? ? ? ? #print(data_content[0])
? ? ? ? ? ? ? ? f.write(data_content[0]) # 拆出來是元組,bytes對象,write時候需要str字符串
?
? ? ? ? ? ? # 打包應答數據
? ? ? ? ? ? ack_data = struct.pack('!HH',4,blockNum)
? ? ? ? ? ? udp_socket.sendto(ack_data,peer_addr) # 不能再給server_addr,因為端口號變了
?
? ? ? ? ? ? # 當應答完畢,更新lastBlockNum
? ? ? ? ? ? lastBlockNum = blockNum
?
? ? ? ? ? ? # 如果數據長度小于 2 + 2 + 512 傳輸結束
? ? ? ? ? ? if len(recv_data) < 516:
? ? ? ? ? ? ? ? print('over')
? ? ? ? ? ? ? ? f.close()
? ? ? ? ? ? ? ? break
? ? ? ? elif opcode == 5:# 出錯
? ? ? ? ? ? err_num = blockNum
? ? ? ? ? ? # 拆出錯誤信息
? ? ? ? ? ? fmt = "!%ds" % (len(recv_data) - 5)
? ? ? ? ? ? err_msg = struct.unpack(fmt,recv_data[4:-1])
? ? ? ? ? ? print('出錯信息:%s' % err_msg)
? ? ? ? ? ? break
?
if __name__ == "__main__":
? ? main()