IO多路复用版FTP

需求:服务器

  1. 实现文件上传及下载功能
  2. 支持多链接并发传文件
  3. 使用select or selectors

流程图并发

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)
client
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)
server