Python UDP 協議網路程式設計《三》
今日分享主題:Python 如何實現TFTP檔案伺服器。
一、定義
TFTP 是一個傳輸檔案的簡單協議,它基於UDP協議而實現。
TFTP (Trivial File Transfer Protocol):簡稱檔案傳輸協議。
TFTP 是TCP/IP協議族中的一個用來在客戶端與伺服器之間進行簡單檔案傳輸的協議,傳輸不復雜、開銷不大的檔案。埠號固定為69。
二、TFTP支援五種型別的包
opcode operation
-
Read request (RRQ)
-
Write request (WRQ)
-
Data (DATA)
-
Acknowledgment (ACK)
-
Error (ERROR)
三、TFTP 檔案下載過程,如下所示及相應的解釋(重點關注)
1、讀寫請求--當操作碼的取值為1時,表示RD 讀請求;當操作碼的取值為2時,表示WE 寫請求
操作碼 + 檔名 + 0 + 模式 + 0
2Bytes String 1Byte String 1Byte
2、資料包,所以資料包的大小為516Bytes--資料包操作碼值為3
操作碼 + 塊編碼 + 資料
2Bytes 2Bytes 512Bytes
3、ACK--ACK 操作碼值為4
操作碼 + 塊編碼
2Bytes 2Bytes
4、ERROR--ERROR 操作碼值為5
操作碼 + 差錯碼 + 差錯資訊 + 0
2Bytes 2Bytes String 1Byte
注意:
1、當客戶端接收到的資料小於516位元組時,表示伺服器傳送資料完成!
2、塊編碼從0開始,每次加1,它的範圍是[0, 65535]。
四、下載過程
第一步:客戶端給伺服器傳送下載請求,資料格式為(操作碼1+檔名+0+模式+0)。
第二步:伺服器接收到請求之後,回覆客戶端訊息,資料格式為元組型別。如下所示:(操作碼3+塊編碼0+資料, (IP號, 埠號))。
第三步:客戶端每接受一次資料,都要回復伺服器一次ACK訊號。
第四步:直到客戶端接收到的資料小於516個位元組,才說明伺服器傳送完畢。
五、上傳過程
第一步:客戶端給伺服器傳送上傳請求,資料格式為(操作碼2+檔名+0+模式+0)。
第二步:伺服器接收到請求之後,回覆客戶端ACK訊息,資料格式為元組型別。如下所示:(操作碼4+塊編碼0, (IP號, 埠號))。
第三步:客戶端每傳送一次資料,伺服器都要回復一次ACK訊號。
第四步:直到客戶端傳送完資料才結束。
六、struct 模組的使用說明
1、 struct.pack
struct.pack用於將Python的值根據格式符,轉換為字串(因為Python中沒有位元組(Byte)型別,可以把這裡的字串理解為位元組流,或位元組陣列)。
其函式原型為:struct.pack(fmt, v1, v2, …),引數fmt是格式字串,關於格式字串的相關資訊在下面有所介紹。v1, v2, …表示要轉換的python值。
2、 struct.unpack
struct.unpack做的工作剛好與struct.pack相反,用於將位元組流轉換成python資料型別。它的函式原型為:struct.unpack(fmt, string),該函式返回一個元組。
七、特殊說明
格式符的使用說明:
b-- signed char-- python裡面的型別integer --大小為1
H-- unsigned short--python裡面的型別integer --大小為2
s -- char[]--python裡面的型別string --大小為1
!H%dsb5sb 解析如下:
H=1表示讀請求,它是兩個位元組,所以用H來表示,如果是一個位元組則用b來表示
%ds表示%len(filename)
b表示為0
5s表示為octet
b表示為0
操作碼 + 檔名 + 0 + 模式 + 0
2Bytes String 1Byte String 1Byte
這是對上面內容的詮釋
八、程式碼實現
1from threading import Thread 2from socket import * 3import struct 4 5#定義一個登入認證的方法 6def login(username,password): 7 if(username=="admin" and password=="123456"): 8 return True 9 else: 10 return False 11 12#定義一個上傳檔案的方法 13def tftp_upload(filename, user_ip, user_port): 14 filenum = 0 #表示接收檔案的序號 15 fileHander = open(filename, 'ab')#建立一個檔案控制代碼 16 socket_up = socket(AF_INET, SOCK_DGRAM)#建立udp套接字 17 send_data_1 = struct.pack("!HH", 4, filenum)#打包 18 socket_up.sendto(send_data_1, (user_ip, user_port)) # 第一次傳送請求,伺服器用隨機埠傳送 19 20 while True: 21 #接收客戶端傳送的資料 22 recv_data, user_info = socket_up.recvfrom(1024) # 第二次客戶端返回響應,連線本伺服器的隨機埠 23 operator_code,ack_num = struct.unpack('!HH', recv_data[:4]) #解包,獲取操作碼 and ack確認碼 24 print(operator_code, ack_num, filenum)#列印 25 if int(operator_code) == 3 and ack_num == filenum: #判斷如果操作碼=3 並且確認號=0就開始上傳檔案 26 fileHander.write(recv_data[4:])#寫檔案內容到伺服器 27 send_data = struct.pack("!HH", 4, filenum)#打包 28 socket_up.sendto(send_data, (user_ip, user_port)) # 第二次傳送請求,伺服器用隨機埠發 29 filenum = filenum + 1#檔案序號+1 30 if len(recv_data) < 516:#當檔案的長度小於516,表示檔案傳輸完成 31 print(user_ip + '上傳檔案' + filename + ':完成')#列印輸出 32 fileHander.close()#關閉檔案 33 socket_up.close()#關閉socket 34 exit()#退出 35 36#定義一個下載檔案的方法 37def tftp_download(filename, user_ip, user_port): 38 socket_down = socket(AF_INET, SOCK_DGRAM)#建立一個udp 的socket套接字 39 filenum = 0 #定義一個檔案序號 40 try: 41 filehander = open(filename, 'rb')#開啟一個檔案控制代碼 42 except: 43 error_data = struct.pack('!HHHb', 5, 5, 5, filenum)#打包 44 socket_down.sendto(error_data, (user_ip, user_port)) # 檔案不存在時傳送 45 exit() # 只會退出此執行緒 46 while True:#進行無限迴圈中 47 read_data = filehander.read(1024)#讀檔案操作 48 send_data = struct.pack('!HH', 3, filenum) + read_data#打包 49 socket_down.sendto(send_data, (user_ip, user_port)) # 向客戶端進行資料第一次傳送 50 if len(read_data) < 512:#當檔案小於512時,表示檔案下載完成 51 print('傳輸完成, 對方下載成功') 52 exit()#退出 53 recv_data = socket_down.recvfrom(1024) # 第二次接收客戶端的資料 54 print(recv_data) #(b'\x00\x04\x00\x00', ('127.0.0.1', 61202)) 55 operator_code, ack_num = struct.unpack("!HH", recv_data[0])#解包獲取操作碼和ack確認號 56 filenum += 1 #檔案序號+1 57 if int(operator_code) != 4 or int(ack_num) != filenum - 1:#如果操作碼不是4 或者 ack確認號不等於檔案序號減1 58 exit()#程式退出 59 filehander.close()#檔案關閉 60 socket_down.close()#關閉socket 61 62def main(): 63 sockets = socket(AF_INET, SOCK_DGRAM) 64 sockets.bind(('', 69)) 65 while 1: 66 recv_data, (user_ip, user_port) = sockets.recvfrom(1024) # 第一次客戶連線69埠 67 print(recv_data, user_ip, user_port) 68 if struct.unpack('!b5sb', recv_data[-7:]) == (0, b'octet', 0):#解包判斷 69 operator_code = struct.unpack('!H', recv_data[:2])#解包獲取操作碼 70 filename = recv_data[2:-7].decode('gb2312') #獲取檔名 71 if operator_code[0] == 1:#操作碼為1 表示下載 72 print('對方想下載資料', filename) 73 t = Thread(target=tftp_download, args=(filename, user_ip, user_port))#多執行緒處理 74 t.start()#執行緒啟動 75 elif operator_code[0] == 2:#操作碼為2 表示上傳 76 print('對方想上傳資料', filename) 77 t = Thread(target=tftp_upload, args=(filename, user_ip, user_port))#多執行緒處理 78 t.start()#執行緒啟動 79 80def login_tftp(): 81 udp_socket = socket(AF_INET, SOCK_DGRAM)#建立udp socker連線 82 udp_socket.bind(('127.0.0.1', 69))#服務端繫結ip and port 83 recv_data = udp_socket.recvfrom(1024)#收資料等待 84 print('接收的內容:', recv_data[0].decode('utf-8'))#顯示收到的資訊 85 print('傳送人的地址:', recv_data[1])#顯示收到的資訊 86 87 msg=recv_data[0].decode('utf-8') 88 msg_length=len(msg.split(" ")) 89 if(msg_length==2): 90 username=msg.split(" ")[0] 91 password=msg.split(" ")[1] 92 if (login(username, password)): 93 print("登入成功!!!") 94 data = "登入成功,可以開始上傳下載檔案了!!!" 95 send_msg(data) 96 else: 97 print("登入失敗,請檢查登入賬號!!!") 98 data = "操作失敗,請檢查登入賬號!!!" 99 send_msg(data) 100 101def send_msg(data): 102 client_address = ('127.0.0.1', 8000) # 定義了本機的ip and port 103 server_address = ('127.0.0.1', 70) # 定義了接收訊息機器的ip and port 104 udp_sockets = socket(AF_INET, SOCK_DGRAM) # 建立udp socker連線 105 udp_sockets.bind(client_address) # 服務端繫結ip and port 106 udp_sockets.sendto(str(data).encode("utf-8"), server_address) # 向接收訊息機器傳送訊息 107 udp_sockets.close() 108 109if __name__ == '__main__': 110 print("tftp 服務正在提供服務...") 111 #伺服器端第一步校驗登入 112 login_tftp() 113 #提供上傳下載服務 114 main()
歡迎關注【無量測試之道】公眾號,回覆【領取資源】,
Python程式設計學習資源乾貨、
Python+Appium框架APP的UI自動化、
Python+Selenium框架Web的UI自動化、
Python+Unittest框架API自動化、
資源和程式碼 免費送啦~
文章下方有公眾號二維碼,可直接微信掃一掃關注即可。
備註:我的個人公眾號已正式開通,致力於測試技術的分享,包含:大資料測試、功能測試,測試開發,API介面自動化、測試運維、UI自動化測試等,微信搜尋公眾號:“無量測試之道”,或掃描下方二維碼:
新增關注,讓我們一起共同成長!