实现终端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 + ' ' 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(' ') 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(' ') 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()