socketserver版FTP
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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]
│ │ └── 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的主要内容,如果未能解决你的问题,请参考以下文章