socketserver版FTP
開發環境:windows7 64位,python3.5
要求:
- 用戶加密認證
- 多用戶同時登陸
- 每個用戶有自己的家目錄且只能訪問自己的家目錄
- 對用戶進行磁盤配額、不同用戶配額可不同
- 用戶可以登陸server後,可切換目錄
- 查看當前目錄下文件
- 上傳下載文件,保證文件一致性
- 傳輸過程中現實進度條
- 支持斷點續傳(未實現)
操作說明:
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]
│ ├── 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