【IO多路复用版FTP】
需求:
- 实现文件上传及下载功能
- 支持多连接并发传文件
- 使用select or selectors
流程图
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import socket import pickle import sys import time import os A = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class Ftp_client(object): def __init__(self): self.client = socket.socket() def connet(self, ip, port): ''' 链接服务器 :param ip: :param port: :return: ''' self.client.connect((ip, port)) name = self.main() self.ftp_main(name) def login(self): ''' 登录 :return: ''' name = input('请输入姓名').lower().strip() password = input('请输入密码') dict = { 'attr': 'login', 'name': name, 'password': password, } self.client.sendall(pickle.dumps(dict)) data = self.client.recv(1024) print(data.decode()) if data.decode()=='输入有误': return False else: return name def register(self): ''' 注册 :return: ''' name = input('请输入姓名').lower().strip() pd = input('请输入密码') dict = {'attr': 'register', 'name': name, 'password': pd} self.client.sendall(pickle.dumps(dict)) data = self.client.recv(1024) print(data.decode()) if data.decode() == '用户名已存在,请重新输入': return False else: return name def main(self): while True: a = input('请输入 1. 用户登录 2. 用户注册 3.退出') if a == '1': res = self.login() elif a == '2': res = self.register() elif a == '3': exit() else: print('输入有误') continue if res is False: continue else: return res def download(self, name): ''' 下载 :return: ''' filename = input('请输入下载文件名') dic = {'attr': 'download', 'filename': filename, 'name': name} self.client.sendall(pickle.dumps(dic)) size = self.client.recv(1024).decode() if size == '该文件不存在': print ('该文件不存在') return False else: size = int(size) try: f = open(os.path.join(A, 'client','db', 'file', filename), 'xb') #文件不存在新建 except Exception: f = open(os.path.join(A, 'client','db', 'file', filename), 'wb')#文件存在打开重新下载 if size == 0: f.close() print('接收完成') else: r_size = 0 while r_size < size: file = self.client.recv(1024) f.write(file) r_size += len(file) view_bar(r_size, size) time.sleep(0.1) else: print('接收完成') f.close() def upload(self, name): filename = input('请输入上传的文件名') if os.path.exists(os.path.join(A, 'client', 'db', 'file',filename)) and filename !='.': size = os.path.getsize(os.path.join(A, 'client', 'db', 'file', filename)) #文件size f = open(os.path.join(A, 'client', 'db', 'file', filename), 'rb') else: print ('此文件不存在') return False if size == 0: dic = {'attr': 'upload', 'filename': filename, 'size': size, 'name': name} self.client.sendall(pickle.dumps(dic)) else: for line in f: dic = {'attr': 'upload', 'filename': filename, 'size': size, 'name': name, 'text': line} self.client.sendall(pickle.dumps(dic)) num = f.tell() #查看文件上传位置 view_bar(num, size) time.sleep(0.1) f.close() print ('接收完成') return False def ftp_main(self, name): while True: a = input('请输入相应的指令, download: 下载; upload: 上传; exit:退出').strip() if hasattr(self, a): func = getattr(self, a) func(name) elif a == 'exit': exit() else: print('输入有误') def view_bar(num, total): '''进度条''' rate = float(num) / float(total) rate_num = int(rate * 100) r = '\r%d%%' % (rate_num, ) #\r 回到到开头 sys.stdout.write(r) sys.stdout.flush() #删除记录 ftp = Ftp_client() ftp.connet('localhost', 9999)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
import selectors import socket import pickle import os A = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sel = selectors.DefaultSelector() def accept(sock,mask): conn, addr = sock.accept() conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, read) #调用read函数 def read(conn, mask): try: data = conn.recv(1024) except Exception: sel.unregister(conn) #出现异常,取消注册的conn conn.close() else: if data: r_dic = pickle.loads(data) if r_dic['attr'] == 'login': login(conn, r_dic) elif r_dic['attr'] == 'register': register(conn, r_dic) elif r_dic['attr'] == 'download': download(conn, r_dic) elif r_dic['attr'] == 'upload': upload(r_dic) else: print('链接断开') sel.unregister(conn) conn.close() def login(conn, dict): ''' 登录 :param conn: :param dict: :return: ''' db_dict = pickle.load(open(os.path.join(A, 'server', 'db', 'register'),'rb')) if dict['name'] in db_dict: if dict['password']==db_dict[dict['name']][0]: conn.sendall('登录成功'.encode()) else: conn.sendall('输入有误'.encode()) else: conn.sendall('输入有误'.encode()) def register(conn, r_dic): ''' 注册 :param conn: :param r_dic: :return: ''' if os.path.exists(os.path.join(A, 'db', r_dic['name'])): conn.sendall('用户名已存在,请重新输入'.encode()) else: conn.sendall('注册成功'.encode()) os.makedirs(os.path.join(A, 'server', 'db', r_dic['name'])) n_dict = pickle.load(open(os.path.join(A, 'server', 'db', 'register'), 'rb')) n_dict[r_dic['name']] = r_dic['password'] pickle.dump(n_dict,open(os.path.join(A, 'server','db', 'register'), 'wb')) def upload(dic): ''' 下载 :param dic: :return: ''' print(dic) f_size = int(dic['size']) #上传文件大小 print(f_size) filename = dic['filename'] name = dic['name'] try: f = open(os.path.join(A, 'server','db', name, filename), 'xb') file_size = 0 print(f_size) except Exception: f = open(os.path.join(A, 'server','db', name, filename), 'ab') file_size = os.path.getsize(os.path.join(A, 'server', 'db', name, filename)) if f_size == 0: f.close() return False else: if file_size< f_size: f.write(dic['text']) f.flush() else: f.close() return False def download(conn, dic): ''' 下载 :param conn: :param dic: :return: ''' l_path = os.path.join(A, 'server','db', dic['name'], dic['filename']) if os.path.exists(l_path) and dic['filename']!= '.': #检查文件是否存在,文件名不能等于. f = open(l_path, 'rb') size = os.path.getsize(l_path) #检查文件 conn.sendall(str(size).encode()) #要以字符串格式传数字 for line in f: conn.sendall(line) f.close() else: conn.sendall('该文件不存在'.encode()) sock = socket.socket() sock.bind(('localhost', 9999)) sock.listen(100) sock.setblocking(False) sel.register(sock, selectors.EVENT_READ, accept) while True: events = sel.select() for key, mask in events: callback = key.data callback(key.fileobj, mask)