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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章