socketserver版FTP

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了socketserver版FTP相关的知识,希望对你有一定的参考价值。

作者:赵海华
开发环境:windows7 64位,python3.5

要求:

  1. 用户加密认证
  2. 多用户同时登陆
  3. 每个用户有自己的家目录且只能访问自己的家目录
  4. 对用户进行磁盘配额、不同用户配额可不同
  5. 用户可以登陆server后,可切换目录
  6. 查看当前目录下文件
  7. 上传下载文件,保证文件一致性
  8. 传输过程中现实进度条
  9. 支持断点续传(未实现)

操作说明:
1.本程序仅支持windows环境演示
2.支持的系统命令有:
dir 查看当前目录下文件
cd 切换目录
rd 删除目录
del 删除文件
md 创建目录
等等windows原生命令
3.ftp命令:put 上传文件;get 下载文件
4.基于单机环境演示,IP:127.0.0.1 端口:9998

程序目录结构:
├── Ftp_Client
│ ├── bin
│ │ ├── ftp_client.py #客户端入口程序
│ │ └── init.py
│ └── core
│ ├── ftp.py #ftp client端核心程序
| ├── auth.py #用户输入密码后进行md5加密
│ ├── init.py
├── Ftp_Server
│ ├── bin
│ │ ├── ftp_server.py #服务端入口程序,使用socketserver进行多用户并发登录
│ │ └── init.py
│ ├── conf
│ │ ├── acount.conf #用户账号文件[用户名 {hash}密码 磁盘配额{20MB}] eg:[alex 202cb962ac59075b964b07152d234b70 20000000]
│ │ └── init.py
│ ├── core
│ │ ├── ftp.py #ftp server端核心程序
│ │ ├── init.py
│ │ └── quota.py #磁盘限额
│ └── logs #日志目录
└── README

程序运行:
./ftp_client.py #输入用户名:alex 密码:123 或用户名:zhaohh 密码:123
./ftp_server.py
ftp_client.py

import os,sys,time
import getpass
import socket
import platform
if platform.system() == "Windows":
    BASE_DIR = "\\".join(os.path.abspath(os.path.dirname(__file__)).split("\\")[:-1])
else:
    BASE_DIR = "/".join(os.path.abspath(os.path.dirname(__file__)).split("/")[:-1])
sys.path.insert(0,BASE_DIR)
from core import ftp,auth

def login(username,password):
    client = socket.socket()
    host = ‘127.0.0.1‘
    port = 9998
    username = username
    password = password
    client.connect((host, port))
    # 登录时将账号发送到服务端进行验证,验证通过后进入循环命令输入
    client.send((username + " " + password).encode("utf-8"))
    auth_res = client.recv(1024)
    if auth_res.decode("utf-8") == "success":
        home_dir = client.recv(1024)  # 获取用户登录成功后的家目录
        welcom = client.recv(1024)
        print(welcom.decode("utf-8"))
        while True:
            command = input("[%s]$ " % home_dir.decode("utf-8")).strip()
            if len(command) == 0: continue
            client.send(command.encode("utf-8"))
            if command.split()[0] == "get":
                f = open(command.split()[1], "wb")
                fsize = int(client.recv(1024).decode("utf-8"))
                client.send(b"ok")
                n = 0.1 #下载文件与总文件比较大小
                while True:
                    data = client.recv(102400)
                    f.write(data)
                    f.flush()
                    f_size = int(os.path.getsize(command.split()[1]))
                    #以下为进度条
                    digit = float(f_size / fsize) * 100
                    sys.stdout.write("\r")
                    sys.stdout.write("%d%% [%s>]" % (digit,int(digit) * ‘#‘))
                    sys.stdout.flush()
                    if os.path.getsize(command.split()[1]) == int(fsize):
                        sys.stdout.write((‘ 已保存 "%s" [%d/%d]‘ + ‘\n‘) %(command.split()[1],f_size,fsize))
                        break
                f.close()
            elif command.split()[0] == "put":
                if len(command.split()) != 2:
                    print("%s命令格式为:%s FileName" % (command.split()[0], command.split()[0]))
                    continue
                else:
                    Ftp = ftp.FTP(client, command.split()[0], command.split()[1])  # 调用FTP模块传输文件
                    Ftp.upload()
            elif command.split()[0] == "cd":
                home_dir = client.recv(1024)
            elif command == "q":
                exit("已退出登录!")
            else:
                res = client.recv(102400)
                print(res.decode())
    else:
        input("账号错误!")

if __name__ == "__main__":
    username = input("请输入用户名:").strip()
    password = input("请输入密码:").strip()
    #password = getpass.getpass("请输入密码:").strip()
    md = auth.Auth(password)
    password = md.md5_passwd()
    login(username,password)

Ftp_Client/core/ftp.py

import os
class FTP(object):
    def __init__(self,conn,command,filename):
        self.command = command
        self.filename = filename
        self.conn = conn

    #上传文件
    def upload(self):
        f = open(self.filename,"rb")
        data = f.read()
        fsize = os.path.getsize(self.filename)
        self.conn.send(str(fsize).encode("utf-8"))
        if self.conn.recv(1024).decode() == "err":
            print("用户空间不足,上传失败!")
        else:
            print("上传文件:%s" % self.filename)
            self.conn.sendall(data)

Ftp_Client/core/auth.py

import hashlib
class Auth(object):
    def __init__(self,password):
        self.password = password
    def md5_passwd(self):
        m = hashlib.md5()
        m.update(self.password.encode())
        return m.hexdigest()

Ftp_Server/bin/ftp_server.py

import socketserver
import os,sys
import platform

if platform.system() == "Windows":
    BASE_DIR = "\\".join(os.path.abspath(os.path.dirname(__file__)).split("\\")[:-1])
else:
    BASE_DIR = "/".join(os.path.abspath(os.path.dirname(__file__)).split("/")[:-1])
sys.path.insert(0,BASE_DIR)
from core import ftp,quota

class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):  #和客户端操作进行处理
        while True:
            try:
                conn = self.request
                self.auth = conn.recv(1024).strip()
                if not self.auth: #当客户端使用exit()函数退出程序时,判断接受的数据如果为空则不进行往下的运行
                    conn.close()
                    break
                self.username = self.auth.decode("utf-8").split()[0]
                self.password = self.auth.decode("utf-8").split()[1]
                self.acount = get_acount()
                if self.username in self.acount and self.password == self.acount[self.username][0]:#判断用户账户
                    conn.send(b"success")
                    if os.path.exists(("C:\\Users\%s" % self.username)):
                        home_dir = "C:\\Users\%s" % self.username
                        conn.send(home_dir.encode("utf-8"))
                    else:
                        os.mkdir("C:\\Users\%s" % self.username)
                        conn.send(home_dir)
                    os.chdir(home_dir)
                    conn.send("登录成功!命令帮助请输入h或?".encode("utf-8"))
                    help = ‘‘‘
                            put [file_name]  上传本地文件到ftp服务器。例如:put file.txt
                            get [file_name]  下载ftp服务器文件到本地。例如:get file.txt
                            command 执行操作系统命令。例如:dir cd rd del
                            quit|exit|q 退出登录。例如:q
                            ‘‘‘
                    while True:  # 循环接收客户端请求的数据
                        self.command = conn.recv(1024).decode("utf-8")
                        if not self.command: break
                        if self.command == "?" or self.command == "h":
                            conn.send(help.encode("utf-8"))
                        elif self.command.split()[0] == "md":
                            os.popen(self.command)
                            if os.path.exists("C:\\Users\%s" % self.command.split()[1]):
                                conn.send("directory already exist!".encode("utf-8"))
                            else:
                                conn.send("directory creat success".encode("utf-8"))
                        elif self.command.split()[0] == "rd":
                            if os.path.isdir(self.command.split()[1]):
                                os.popen(self.command)
                                conn.send("directory delete success".encode("utf-8"))
                            else:
                                conn.send(("%s not a directory" %self.command.split()[1]).encode("utf-8"))
                        elif self.command.split()[0] == "del":
                            if os.path.isfile(self.command.split()[1]):
                                os.popen(self.command)
                                conn.send("file delete success".encode("utf-8"))
                            else:
                                conn.send(("%s not a file" %self.command.split()[1]).encode("utf-8"))
                        elif self.command.split()[0] == "cd":
                            if len(self.command.split()) == 2:
                                if self.command.split()[1] == "..":
                                    if len(home_dir.split("\\")) == 3:
                                        conn.send(home_dir.encode("utf-8"))
                                    else:
                                        home_dir = "\\".join(home_dir.split("\\")[:-1])
                                        conn.send(home_dir.encode("utf-8"))
                                        os.chdir(home_dir)
                                elif os.path.isdir(self.command.split()[1]):
                                    os.popen(self.command)
                                    home_dir = home_dir + "\\" + self.command.split()[1]
                                    conn.send(home_dir.encode("utf-8"))
                                    os.chdir(home_dir)
                            else:
                                conn.send(home_dir.encode("utf-8"))
                        elif self.command.split()[0] == "get":  # or command.split()[0] == "put":  #判断用户是否执行ftp命令
                            if len(self.command.split()) != 2:
                                conn.send(
                                    ("%s命令格式为:%s FileName" % (self.command.split()[0], self.command.split()[0])).encode("utf-8"))
                                continue
                            else:
                                Ftp = ftp.FTP(conn, self.command.split()[0], self.command.split()[1])  # 调用FTP模块传输文件
                                Ftp.download()
                        elif self.command.split()[0] == "put":  # 判断用户是否执行ftp命令
                            fsize = conn.recv(1024).decode("utf-8")
                            q = quota.Quota(self.username)  # 用户家目录磁盘空间大小判断
                            quota_size = q.home_size()
                            if int(fsize) > int(self.acount[self.username][1]) - quota_size:
                                print("用户空间不足,上次文件失败!")
                                conn.send(b‘err‘)
                                continue
                            f = open(self.command.split()[1], "wb")
                            conn.send(b"ok")
                            while True:
                                data = conn.recv(102400)
                                f.write(data)
                                f.flush()
                                if os.path.getsize(self.command.split()[1]) == int(fsize):
                                    break
                            f.close()
                        elif self.command == "q" or self.command == "quit" or self.command == "exit":
                            break
                        else:
                            res = os.popen(self.command).read()
                            if len(res) == 0:  # 如果是不存在的系统命令,则提醒用户输入错误
                                conn.send(("%s:command not found" % self.command).encode("utf-8"))
                            else:  # 以上条件都不符合后执行此步骤,此块内容为执行系统命令
                                conn.sendall(res.encode("utf-8"))
                    continue
                else:
                    conn.close()
                    continue
            except ConnectionResetError as e:
                print("info:",e)
                break

#从文件获取用户账号信息
def get_acount():
    acount_dict = {}
    f = open(BASE_DIR+"\conf\\acount.conf")
    for account in f:
        acount_dict[account.split()[0]] = [account.split()[1],account.split()[2]]
    return acount_dict

if __name__ == "__main__":
    HOST,PORT = "127.0.0.1",9998
    server = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler)
    server.serve_forever()

Ftp_Server/core/ftp.py

import os
class FTP(object):
    def __init__(self,conn,command,filename):
        self.command = command
        self.filename = filename
        self.conn = conn

    #下载文件
    def download(self):
        f = open(self.filename,"rb")
        # data = f.read()
        # fsize = os.path.getsize(self.filename)
        # self.conn.send(str(fsize).encode("utf-8"))
        # self.conn.recv(1024)
        # self.conn.sendall(data)
        fsize = os.path.getsize(self.filename)
        self.conn.send(str(fsize).encode("utf-8"))
        self.conn.recv(1024)
        for data in f:
            self.conn.sendall(data)

Ftp_Server/core/quota.py

import os
import platform

class Quota(object):
    def __init__(self,path):
        self.path = path
    def home_size(self):
        f_size = 0
        if platform.system() == "Windows":
            for path, dirs, files in os.walk("C:\\Users\%s" % self.path):
                for i in files:
                    new_path = path + "\\" + i
                    f_size += os.path.getsize(new_path)
            return f_size
        else:
            for path, dirs, files in os.walk("/home/%s" % self.path):
                for i in files:
                    new_path = path + "/" + i
                    f_size += os.path.getsize(new_path)
            return f_size

acount.conf: alex 202cb962ac59075b964b07152d234b70 20000000
下载文件进度条演示:
技术分享图片

以上是关于socketserver版FTP的主要内容,如果未能解决你的问题,请参考以下文章

FTP上传下载文件(面向对象版)

进阶版socket套接字ftp文件传输可并发

socketserver 模块实现ftp功能

使用 SocketServer 实现 FTP Server

Python-Socketserver实现FTP,文件上传下载

socketserver实现多用户同时在线ftp