實現終端FTP文件傳輸
代碼結構:
.
├── client.py
├── readme.txt
└── server.py
運行截圖:
readme.txt
tftp文件服務器 項目功能: * 客戶端有簡單的頁面命令提示 * 功能包含: 1、查看服務器文件庫中的文件列表(普通文件) -> os.listdir 2、可以下載其中的某個文件到本地 3、可以上傳客戶端文件到服務器文件庫 * 服務器需求: 1、允許多個客戶端同時操作 2、每個客戶端可能會連續發送命令 技術分析: 1、TCP套接字更適合文件傳輸 2、並發方案 -> fork多進程並發 3、對文件的讀寫操作 4、獲取文件列表 -> os.listdir() 或 tree 5、粘包的處理 整體結構設計: 1、服務器功能封裝在類中(上傳,下載,查看列表) 2、創建套接字,流程函數調用main() 3、客戶端負責發起請求,接收回復,展示 4、服務端負責接受請求,邏輯處理 編程實現: 1、搭建整體結構,創建網絡連接 2、創建多進程和類的結構 3、每個功能模塊的實現 模塊方法: os.listdir(path) os.path.isfile() os.path.isdir()
server.py
# server.py import struct from socket import * import os import signal import sys import time # 文件庫 FILE_PATH = '/home/noon/Python/Example/' # 實現功能模塊 class TftpServer(object): def __init__(self, sockfd, addr): super().__init__() self.sockfd = sockfd self.addr = addr self.opt = '' def display(self): re = '' for i in os.listdir(FILE_PATH): re += i + '\n' self.sockfd.send(re.encode()) def download(self): '下載模塊功能實現' # 嘗試打開文件 filename = FILE_PATH + self.opt.split(' ')[1] print(filename) try: fp = open(filename, 'rb') except: self.sockfd.send(b'Failed to open file') else: self.sockfd.send(b'Ready to transfer') # 循環發送數據 while True: data = fp.read(1024) if not data: # 如果傳輸完畢,data為空,傳輸0,跳出循環 res = struct.pack('i', 0) self.sockfd.send(res) break res = struct.pack('i', len(data)) self.sockfd.send(res) self.sockfd.send(data) print('Done!') def upload(self): filename = FILE_PATH + self.opt.split(' ')[1] try: fp = open(filename, 'wb') except: self.sockfd.send('Unable to open file'.encode()) else: self.sockfd.send(b'Ready to upload') while True: res = self.sockfd.recv(4) length = struct.unpack('i', res)[0] if length == 0: break data = self.sockfd.recv(length) fp.write(data) fp.close() print('Done!') def quit(self): print(self.addr, '斷開連接') self.sockfd.close() sys.exit() # 主流程 def main(): HOST = '0.0.0.0' PORT = 5555 ADDR = (HOST, PORT) sockfd = socket() sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sockfd.bind(ADDR) sockfd.listen(5) # 通知內核對子進程的結束不關心,由內核回收。 signal.signal(signal.SIGCHLD, signal.SIG_IGN) while True: try: connfd, addr = sockfd.accept() except KeyboardInterrupt: sockfd.close() sys.exit('服務器退出') except Exception as e: print(e) continue print('連接成功:', addr) # 創建子進程 pid = os.fork() if pid == 0: sockfd.close() tftp = TftpServer(connfd, addr) while True: tftp.opt = connfd.recv(1024).decode() if tftp.opt == 'display': tftp.display() elif tftp.opt.startswith('download'): tftp.download() elif tftp.opt.startswith('upload'): tftp.upload() elif tftp.opt == 'quit': tftp.quit() else: connfd.close() continue if __name__ == '__main__': main()
client.py
# client.py from socket import * import sys import time import struct # 實現各種功能請求 class TftpClient(object): def __init__(self, sockfd): super().__init__() self.sockfd = sockfd self.opt = '' def panel(self): print('+', '*'*30, '+', sep='') print('+', 'display'.center(30), '+', sep='') print('+', 'download'.center(30), '+', sep='') print('+', 'upload'.center(30), '+', sep='') print('+', 'quit'.center(30), '+', sep='') print('+', '*'*30, '+', sep='') def display(self): self.sockfd.send(b'display') print(self.sockfd.recv(1024).decode()) def download(self): '客戶端下載請求' # 先使用display命令向服務器請求文件列表,驗證用戶想要下載的文件是否存在 filename = input('filename>> ') if not filename: return self.sockfd.send(b'display') files = self.sockfd.recv(1024).decode().split('\n') if not filename in files: print('Cannot locate', filename) return # 文件存在,發送下載請求到服務端,並接收返回結果 data = 'download ' + filename self.sockfd.send(data.encode()) data = self.sockfd.recv(1024).decode() # 如果服務端無法打開文件 if data == 'Failed to open file': print('Failed to open file') # 可以執行下載操作 else: # 調用寫方法 print(data) self.write(filename) print('Done!') def write(self, filename): '從服務器下載文件' # 考慮到粘包問題,導入struct模塊,接收服務端要發送的數據的大小,再按照這個大小接收數據,循環執行 fp = open(filename, 'wb') while True: # 接收數據大小,調用struct.unpack方法獲得數據大小 res = self.sockfd.recv(4) length = struct.unpack('i', res)[0] # 如果數據大小為0,說明傳輸結束,退出循環 if length == 0: break # 按照數據的大小接收數據 data = self.sockfd.recv(length) fp.write(data) fp.close() def upload(self): # 文件路徑 filepath = input('filepath>> ') try: fp = open(filepath, 'rb') except: print('Unable to open', filepath) return else: # 文件上傳要保存為什么名字 # 先使用display命令向服務器請求文件列表,驗證用戶想要上傳的文件名是否存在 filename = input('filename>> ') if not filename: return self.sockfd.send(b'display') files = self.sockfd.recv(1024).decode().split('\n') if filename in files: print('File already exists!') return # 可以上傳 data = 'upload ' + filename self.sockfd.send(data.encode()) data = self.sockfd.recv(1024).decode() if data == 'Unable to open file': print('服務器打開文件出錯') return else: self.read(fp) def read(self, fp): '讀取文件上傳服務器' while True: data = fp.read(1024) if not data: res = struct.pack('i', 0) self.sockfd.send(res) break res = struct.pack('i', len(data)) self.sockfd.send(res) self.sockfd.send(data) print('Done!') def quit(self): self.sockfd.send(b'quit') self.sockfd.close() sys.exit('客戶端關閉') # 創建套接字,建立連接 def main(): argc = len(sys.argv) if argc != 3: sys.exit('Usage: python client.py host port') else: HOST = sys.argv[1] PORT = int(sys.argv[2]) ADDR = HOST, PORT sockfd = socket() try: sockfd.connect(ADDR) except ConnectionRefusedError: sys.exit('無法連接到服務端') tftp = TftpClient(sockfd) tftp.panel() while True: try: tftp.opt = input('>> ').lower() except KeyboardInterrupt: tftp.quit() if tftp.opt == 'display': tftp.display() elif tftp.opt == 'download': tftp.download() elif tftp.opt == 'upload': tftp.upload() elif tftp.opt == 'quit': tftp.quit() else: continue if __name__ == '__main__': main()