python【项目】:基于socket的FTP服务器
- 功能要求
1. 用户加密认证
2. 服务端采用 SocketServer实现,支持多客户端连接
3. 每个用户有自己的家目录且只能访问自己的家目录
4. 对用户进行磁盘配额、不同用户配额可不同
5. 用户可以登陆server后,可切换目录
6. 能查看当前目录下文件
7. 上传下载文件,保证文件一致性
8. 传输过程中实现进度条展示
9.用户可在自己家目录进行创建目录、文件、删除目录及文件
10.服务端可实现增加用户、删除用户
11.支持上传时断点续传
- 应用知识点
a) 类的应用
b) 函数的使用
c) 多进程
d) 反射
e) socket、socketserver、hashlib、configparser、logging
f) 文件的读写
- 开发环境
- python 3.6.1
- PyCharm 2016.2.3
- 目录结构
FTPClient
|--bin (主接口目录)
|--ftpclient.py (客户端主程序接口文件)
|--config (配置文件目录)
|--code.py (状态码文件)
|--settings.py (配置文件)
|--template.py (模板文件)
|--download (下载存放目录)
|--lib (模块目录)
|--client.py (客户端各类接口封装)
|--common.py (公共接口)
|--logs (日志目录)
|--ftpclient.log (日志文件)
|--clientRun.py (主执行程序)
FTPServer
|--bin (主接口目录)
|--ftpserver.py (服务端socket接口文件)
|--main.py (主程序接口文件)
|--config (配置目录)
|--settings.py (配置文件)
|--template.py (模板文件)
|--database (数据保存目录)
|--user.ini (用户信息文件)
|--dbhelper (数据目录)
|--dbapi.py (数据操作接口)
|--lib (模块目录)
|--user.py (用户类文件用来实例化对象)
|--server.py (服务端模块,各类所有命令方法)
|--common.py (公共模块文件)
|--logs
|--ftpserver.log (日志文件)
|--upload (上传文件存放的目录)
|--serverRun.py (主执行程序)
- 模块功能系统图
1、思维导图
2、功能接口关系图
客户端:
服务端:
- 关键代码段
1、服务端
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import socketserver,os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings,template
from lib import common,server logger = common.Logger('ftpserver').getlog() class MyServer(socketserver.BaseRequestHandler): def handle(self):
try:
client_socket = self.request
client_addr = self.client_address
logger.info("client {0} connected".format(client_addr))
#发送成功标识给客户端
client_socket.sendall(bytes("OK",encoding='utf-8'))
client_user = None while True:
#获取客户端命令
ret_client_data = str(client_socket.recv(1024),encoding='utf-8') #判断客户端是否退出
if ret_client_data == b'':
logger.info("client {0} is exit".format(client_addr))
client_socket.close() #取出客户端命令
cmd = ret_client_data.split("|")[0] logger.info("client {0} send command {1}".format(client_addr,cmd))
#判断是否登录认证状态
if cmd == 'auth':
client_user = server.client_auth(client_socket, ret_client_data)
else:
try:
#通过反射寻找模块的命令
if hasattr(server,cmd):
func = getattr(server,cmd)
func(client_socket, client_user, ret_client_data)
else:
logger.error("command {0} not found".format(cmd))
except Exception as e:
logger.error(e)
client_socket.close() except Exception as e:
logger.error(e) def process():
"""
启动服务
:return:
"""
server = socketserver.ThreadingTCPServer((settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT),MyServer)
server.serve_forever()
ftpserver
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys,configparser
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings def readall_sections():
"""
读取user.ini文件所有的用户名
:return: 返回所有的用户名列表
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
result = con.sections()
return result def GetValue(key,value):
"""
获取user.ini文件键名值
:param key: 键名
:param value: 键值
:return:
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
result = con.get(key,value)
return result def CheckSections(sections_name):
"""
检查sections项名是否存在
:param sections_name: 用户名
:return:
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
result = con.has_section(sections_name)
return result def AddOption(sections_name, **args):
"""
添加用户信息
:param sections_name:用户名
:param args: 字典格式:('test3',password='aa',totalspace='bb',userspace='cc')
:return:
"""
con = configparser.ConfigParser()
with open(settings.USER_INI,'a+',encoding='utf-8') as f:
con.add_section(sections_name)
for key in args:
con.set(sections_name, key, args[key])
con.write(f) def DelSections(sections_name):
"""
删除用户信息
:param sections_name:
:return:
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
with open(settings.USER_INI,'w') as f:
con.remove_section(sections_name)
con.write(f) def ModifyOption(sections_name, **args):
"""
修改磁盘配额空间
:param sections_name: 用户名
:param args:用户字典信息
:return:
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
for key in args:
con.set(sections_name, key, args[key])
with open(settings.USER_INI, 'w', encoding='utf-8') as f:
con.write(f) def load_info(sections_name):
"""
加载用户信息
:param sections_name: 用户名
:return: 返回字典用户信息
"""
con = configparser.ConfigParser()
con.read(settings.USER_INI, encoding='utf-8')
user_dict = {}
for i, j in con.items(sections_name):
user_dict[i] = j
return user_dict
dbapi
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys,time
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from lib.common import Logger
from lib.user import Users
from lib import common logger = Logger('serverr').getlog() def client_auth(client_socket,args): """
客户端认证
:param client_socket: 客户端socket对象
:param args: 用户发送过来的数据 ex: "auth|test|a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057"
:return: success:认证成功;user_error:用户名不存在;fail:认证失败
"""
recv_data_list = args.split("|")
username = recv_data_list[1]
passwd = recv_data_list[2]
client_user = Users(username)
#判断用户名是否存在
if client_user.check_user():
msg = client_user.load_user_info()
password,totalspace,userspace = msg.strip().split("|")
user_info = "{0}|{1}".format(totalspace, userspace)
#判断密码是否正确
if password == passwd:
auth_status = "success"
else:
auth_status = "fail"
else:
auth_status = "user_error" #将认证状态发送给客户端
client_socket.sendall(bytes(auth_status,encoding='utf-8'))
if auth_status == "success":
# 认证成功将用户空间消息发给客户端
client_socket.sendall(bytes(user_info, encoding='utf-8'))
return client_user def cd(client_socket,client_user,ret_data):
"""
切换目录路径
:param client_socket: 客户端socket对象
:param client_user: 客户端用户对象
:param ret_data: 接收客户命令消息体 例如:cd|..或cd|test或cd|/test/aa/bb
:return:
"""
#获取命令行消息体
cd_folder = ret_data.split("|")[1]
try:
#判断是否当前根目录
if cd_folder == "..":
if client_user.userpath == client_user.homepath:
sed_msg = "0|{0}".format(os.path.basename(client_user.userpath))
else:
#返回上一级目录
client_user.userpath = os.path.dirname(client_user.userpath)
sed_msg = "1|{0}".format(os.path.basename(client_user.userpath))
elif cd_folder == "." or cd_folder == "":
sed_msg = "3|{0}".format(cd_folder)
else:
#组合路径目录
tmp_path = os.path.join(client_user.userpath, cd_folder)
if os.path.isdir(tmp_path):
client_user.userpath = tmp_path
sed_msg = "1|{0}".format(os.path.basename(client_user.userpath))
else:
# 不是文件夹
sed_msg = "2|{0}".format(cd_folder)
# 开始发送结果
client_socket.sendall(bytes(sed_msg,encoding='utf-8'))
except Exception as e:
logger.error(e) def put(client_socket,client_user,ret_data):
"""
上传文件
:param client_socket:
:param client_user:
:param ret_data:
:return:
"""
# 初始化上传文件的基本信息
filename = ret_data.split("|")[1]
filesize = int(ret_data.split("|")[2])
filemd5 = ret_data.split("|")[3]
put_folder = client_user.userpath
check_filename = os.path.isfile(os.path.join(put_folder,filename))
save_path = os.path.join(put_folder, filename)
fmd5 = common.md5sum(save_path)
#不存在文件名,正常传输
if not check_filename:
client_socket.sendall(bytes("ok",encoding='utf-8'))
# 全新的文件的话,更新用户使用空间大小
client_user.update_quota(filesize)
# 已经接收的文件大小
has_recv = 0
with open(save_path,'wb') as f:
while True:
# 如果文件总大小等于已经接收的文件大小,则退出
if filesize == has_recv:
break
data = client_socket.recv(1024)
f.write(data)
has_recv += len(data)
else:
#存在文件名条件,做判断分析是否存在断点
if fmd5 == filemd5:
client_socket.sendall(bytes("full", encoding='utf-8'))
# 已经接收的文件大小
has_recv = 0
with open(save_path, 'wb') as f:
while True:
# 如果文件总大小等于已经接收的文件大小,则退出
if filesize == has_recv:
break
data = client_socket.recv(1024)
f.write(data)
has_recv += len(data)
else:
#存在断点文件,发起请求续签标志
recv_size = os.stat(save_path).st_size
ready_status = "{0}|{1}".format("continue", str(recv_size))
client_socket.sendall(bytes(ready_status, encoding='utf-8'))
# 已经接收的文件大小
has_recv = 0
with open(save_path, 'wb') as f:
while True:
# 如果文件总大小等于已经接收的文件大小,则退出
if filesize == has_recv:
break
data = client_socket.recv(1024)
f.write(data)
has_recv += len(data) def get(client_socket,client_user,ret_data):
"""
下载文件
:param client_socket:
:param client_user:
:param ret_data:
:return:
"""
# 获取文件名
filename = ret_data.split("|")[1]
# 文件存在吗
file = os.path.join(client_user.userpath, filename)
if os.path.exists(file):
# 先告诉客户端文件存在标识
client_socket.send(bytes("", 'utf8'))
# 得到客户端回应
client_socket.recv(1024)
# 发送文件的基本信息 "filesize|file_name|file_md5"
filesize = os.stat(file).st_size
file_md5 = common.md5sum(file)
sent_data = "{fsize}|{fname}|{fmd5}".format(fsize=str(filesize),
fname=filename,
fmd5=file_md5)
client_socket.sendall(bytes(sent_data, 'utf8')) # 客户端收到ready
if str(client_socket.recv(1024), 'utf-8') == "ready":
# 开始发送数据了
with open(file, 'rb') as f:
new_size = 0
for line in f:
client_socket.sendall(line)
new_size += len(line)
if new_size >= filesize:
break
else:
# 文件不存在
client_socket.send(bytes("", 'utf8')) def mk(client_socket,client_user,ret_data):
"""
创建目录
:param client_socket: 客户端socket对象
:param client_user: 客户端用户对象
:param ret_data: 接收客户命令消息体 例如:mk|test
:return:
"""
mk_folder = ret_data.split("|")[1]
if mk_folder:
try:
folder_path = os.path.join(client_user.homepath, mk_folder)
os.makedirs(folder_path)
client_socket.sendall(bytes("",encoding='utf-8'))
except Exception as e:
client_socket.sendall(bytes("",encoding='utf-8'))
logger.error("create directory failure: %s" % e)
else:
client_socket.sendall(bytes("", encoding='utf-8')) def delete(client_socket,client_user,ret_data):
"""
删除目录或文件
:param client_socket:客户端socket对象
:param client_user:客户端用户对象
:param ret_data:接收消息体:样本格式:delete|/test/aa
:return:
"""
del_folder = ret_data.split("|")[1]
if del_folder:
try:
#判断文件名是否存在
folder_path = os.path.join(client_user.homepath, del_folder)
filesize = os.stat(folder_path).st_size
if os.path.isfile(folder_path):
os.remove(folder_path)
client_user.update_down_quota(filesize)
sent_data = "{staus}|{fsize}".format(staus="",
fsize=str(filesize)
) client_socket.sendall(bytes(sent_data, encoding='utf-8'))
#判断目录是否存在
elif os.path.isdir(folder_path):
os.removedirs(folder_path)
client_socket.sendall(bytes("", encoding='utf-8'))
else:
#目录或文件名不存在情况,删除失败
client_socket.sendall(bytes("", encoding='utf-8'))
except Exception as e:
#当前路径目录下不是空目录,不能删除
client_socket.sendall(bytes("", encoding='utf-8'))
logger.error("Delete directory or filename failure: %s" % e)
else:
#命令行后是空白目录
client_socket.sendall(bytes("", encoding='utf-8')) def ls(client_socket,client_user,ret_data):
"""
显示当前文件目录及文件名
:param client_socket: 客户端socket对象
:param client_user: 客户端用户对象
:param ret_data: 接收消息体样本格式:ls|
:return:
"""
check_folder = client_user.userpath
#获取用户目录下的文件目录或文件名列表
file_list = os.listdir(check_folder)
#目录下的文件个数
file_count = len(file_list)
if file_count > 0:
return_list = "{filecount}|".format(filecount=file_count)
for rootpath in file_list:
file = os.path.join(check_folder,rootpath)
stat = os.stat(file)
create_time = time.strftime('%Y:%m-%d %X', time.localtime(stat.st_mtime))
file_size = stat.st_size
if os.path.isfile(file):
return_list += "{ctime} {fsize} {fname}\n".format(ctime=create_time,
fsize=str(file_size).rjust(10, " "),
fname=rootpath)
if os.path.isdir(file):
return_list += "{ctime} <DIR> {fsize} {fname}\n".format(ctime=create_time,
fsize=str(file_size).rjust(10, " "),
fname=rootpath)
else:
return_list = "0|" try:
# 开始发送信息到客户端
# 1 先把结果串的大小发过去
str_len = len(return_list.encode("utf-8"))
client_socket.sendall(bytes(str(str_len), encoding='utf-8'))
# 2 接收客户端 read 标识,防止粘包
read_stat = client_socket.recv(1024).decode()
if read_stat == "ready":
client_socket.sendall(bytes(return_list, encoding='utf-8'))
else:
logger.error("client send show command,send 'ready' status fail")
except Exception as e:
logger.error(e)
server
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings
from dbhelper import dbapi
from lib.common import Logger logger = Logger('user').getlog() """
服务端用户信息类
""" class Users(object):
def __init__(self,username):
self.username = username
self.password = ""
self.totalspace = 0
self.userspace = 0
self.homepath = os.path.join(settings.USER_HOME_FOLDER, self.username)
self.userpath = self.homepath def create_user(self):
"""
创建用户
:return: True:创建用户成功; False: 创建用户失败
"""
args = dict(password=str(self.password), totalspace=str(self.totalspace), userspace=str(self.userspace))
dbapi.AddOption(self.username, **args)
self.__create_folder() def del_user(self):
"""
删除用户
:return: True;删除用户成功;False: 删除用户失败
"""
dbapi.DelSections(self.username)
self.__del_folder() def check_user(self):
"""
判断用户是否存在
:return:
"""
if dbapi.CheckSections(self.username):
return True
return False def load_user_info(self):
"""
加载用户信息,赋值给属性
:return:
"""
user_info = dbapi.load_info(self.username)
self.password = user_info["password"]
self.totalspace = int(user_info["totalspace"])
self.userspace = int(user_info["userspace"])
msg = "{0}|{1}|{2}".format(self.password, self.totalspace, self.userspace)
return msg def __create_folder(self):
"""
创建用户的目录
:return:
"""
os.mkdir(self.homepath) def __del_folder(self):
"""
删除用户目录
:return:
"""
os.removedirs(self.homepath) def update_quota(self,filesize):
"""
更新用户磁盘配额数据
:param filesize: 上传文件大小
:return: True: 更新磁盘配额成功;False:更新磁盘配额失败
"""
if dbapi.CheckSections(self.username):
self.userspace += filesize
args = dict(userspace=str(self.userspace))
dbapi.ModifyOption(self.username, **args)
return True
return False def update_down_quota(self,filesize):
"""
用户删除文件情况,自动减少对应文件大小并更新用户磁盘配额空间
:param filesize:
:return:
"""
if dbapi.CheckSections(self.username):
self.userspace -= filesize
args = dict(userspace=str(self.userspace))
dbapi.ModifyOption(self.username, **args)
return True
return False
user
2、客户端
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings,template,code
from lib import common
from lib.client import client logger = common.Logger('ftpclient').getlog() def run():
common.message(template.START_MENU,"INFO")
common.message("正在连接服务器 {0}:{1}......".format(settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT),"INFO") #创建对象
client_obj = client(settings.FTP_SERVER_IP,settings.FTP_SERVER_PORT)
#连接服务器,返回结果
conn_result = client_obj.connect()
if conn_result == code.CONN_SUCC:
common.message("连接成功!", "INFO")
#客户端登录
login_result = client_obj.login()
if login_result:
exit_flag = False
while not exit_flag:
show_menu = template.LOGINED_MENU.format(client_obj.username,
str(int(client_obj.totalspace / 1024 / 1024)),
str(int(client_obj.userspace / 1024 / 1024)))
common.message(show_menu,"INFO")
inp_command = common.input_command("[请输入命令]:")
if inp_command == "quit":
exit_flag = True
else:
#获取命令
func = inp_command.split("|")[0]
try:
if hasattr(client, func):
#从模块寻找到函数
target_func = getattr(client, func)
#执行函数
target_func(client_obj, inp_command)
else:
common.message("Client {0} 未找到".format(inp_command), "ERROR")
except Exception as e:
logger.error(e)
else:
common.message("连接失败!", "ERROR")
ftpclient
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys,socket
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings
from config import code
from lib import common logger = common.Logger('client').getlog() class client(object):
def __init__(self,server_addr, server_port):
self.username =""
self.totalspace = 0
self.userspace = 0
self.client = socket.socket()
self.__server = (server_addr, server_port) def connect(self):
"""
客户端连接验证
:return: 连接成功返回1000;连接失败返回1001
"""
try:
self.client.connect(self.__server)
ret_bytes = self.client.recv(1024)
#接收服务端消息
ret_str = str(ret_bytes, encoding='utf-8')
if ret_str == "OK":
return code.CONN_SUCC
else:
return code.CONN_FAIL
except Exception as e:
logger.error(e) def check_auth(self,user, passwd):
"""
客户端状态发送给服务端验证,并返回结果
:param user: 用户名
:param passwd: 密码
:return:
"""
sendmsg = "{cmd}|{user}|{passwd}".format(cmd="auth",
user=user,
passwd=passwd)
self.client.sendall(bytes(sendmsg,encoding='utf-8'))
ret_bytes = self.client.recv(1024)
#获取服务端返回的认证状态信息
auth_info = str(ret_bytes, encoding='utf-8')
if auth_info == "success":
self.username = user
#获取服务端返回用户空间信息
user_info = str(self.client.recv(1024),encoding='utf-8')
self.totalspace = int(user_info.split("|")[0])
self.userspace = int(user_info.split("|")[1])
return code.AUTH_SUCC
if auth_info == "user_error":
return code.AUTH_USER_ERROR
if auth_info == "fail":
return code.AUTH_FAIL def login(self):
while True:
username = str(input("请输入用户名:")).strip()
password = str(input("请输入密码:")).strip()
#对密码进行md5加密
password = common.md5(password)
#登录认证
auth_status = self.check_auth(username,password)
if auth_status == code.AUTH_SUCC:
common.message(">>>>>>>登录成功","INFO")
return True
elif auth_status == code.AUTH_USER_ERROR:
common.message(">>>>>>>用户名不存在","ERROR")
return False
else:
common.message(">>>>>>>用户名或密码错误!","ERROR")
return False def mk(self,command):
"""
创建目录
:param command: 发送命令消息格式;mk|test或mk|/test/yj
:return:
"""
#发送命令消息给服务端
self.client.sendall(bytes(command,encoding='utf-8'))
#接收服务端发来的回应消息
mk_msg = str(self.client.recv(1024), encoding='utf-8')
mk_msg = int(mk_msg)
if mk_msg == code.FILE_MK_SUCC:
common.message(">>>>>>>创建目录成功","INFO")
elif mk_msg == code.FILE_MK_FAIL:
common.message(">>>>>>>创建目录失败","ERROR")
else:
common.message(">>>>>>>请输入文件夹名","ERROR") def delete(self,command):
"""
删除目录或文件名
:param command: delete|PycharmProjects/untitled/project/FTPv1/FTPServer/upload/admin/test/aa
:return:
"""
# 发送命令消息给服务端
self.client.sendall(bytes(command, encoding='utf-8'))
# 接收服务端发来的回应消息
del_msg = str(self.client.recv(1024), encoding='utf-8')
reve_status = int(del_msg.split("|")[0])
reve_delfilename_fsize = int(del_msg.split("|")[1]) if del_msg == code.FOLDER_DEL_SUCC:
common.message(">>>>>>>删除目录成功","INFO")
elif reve_status == code.FILE_DEL_SUCC:
#更新用户空间配额大小
self.userspace -= reve_delfilename_fsize
common.message(">>>>>>>删除文件名成功","INFO")
elif reve_status == code.FILE_DEL_FAIL:
common.message(">>>>>>>删除目录或文件名失败","ERROR")
elif reve_status == code.FILE_DEL_EMPTY:
common.message(">>>>>>>当前目录下不是空目录,无法删除!","ERROR")
else:
common.message(">>>>>>>命令行请输入需要删除的路径目录或文件名!","ERROR") def cd(self,command):
"""
切换目录路径
:param command: cd|.. 或cd|foldername
:return: 返回状态信息
"""
# 发送命令消息给服务端
self.client.sendall(bytes(command, encoding='utf-8'))
# 接收服务端发来的回应消息
cd_msg = str(self.client.recv(1024), encoding='utf-8')
result_status,result_folder = cd_msg.split("|")
if result_status == "":
result_value = "当前是根目录"
elif result_status == "":
result_value = "目录已切换到:{0}".format(result_folder)
elif result_status == "":
result_value = "切换失败, {0} 不是一个目录".format(result_folder)
elif result_status == "":
result_value = "命令无效:{0}".format(result_folder)
common.message(result_value,"INFO") def ls(self,*args):
"""
显示客户端的文件列表详细信息
:param args:
:return: 返回文件列表
"""
try:
# 发送命令到服务端
self.client.send(bytes("ls|", encoding='utf-8'))
# 接收服务端发送结果的大小
total_data_len = self.client.recv(1024).decode()
# 收到了并发送一个ready标识给服务端
self.client.send(bytes("ready", 'utf-8')) # 开始接收数据
total_size = int(total_data_len) # 文件总大小
has_recv = 0 # 已经接收的文件大小
exec_result = bytes("", 'utf8')
while True:
# 如果文件总大小等于已经接收的文件大小,则退出
if total_size == has_recv:
break
data = self.client.recv(1024)
has_recv += len(data)
exec_result += data
# 获取结果中文件及文件夹的数量
return_result = str(exec_result, 'utf-8')
file_count = int(return_result.split("|")[0])
if file_count == 0:
return_result = "目前无上传记录"
else:
return_result = return_result.split("|")[1]
common.message(return_result,"INFO")
except Exception as e:
logger.error("client ls:{0}".format(e)) def put(self,command):
"""
上传文件
:param command: put|folderfile
:return:
"""
file_name = command.split("|")[1]
if os.path.isfile(file_name):
filename = os.path.basename(file_name)
fsize = os.stat(file_name).st_size
fmd5 = common.md5sum(file_name) # 将基本信息发给服务端
file_msg = "{cmd}|{file}|{filesize}|{filemd5}".format(cmd='put',
file=filename,
filesize=fsize,
filemd5=fmd5)
self.client.send(bytes(file_msg, encoding='utf8'))
logger.info("send file info: {0}".format(file_msg))
#接收来自服务端数据
put_msg = str(self.client.recv(1024),encoding='utf-8')
try:
#正常上传文件
if put_msg == "ok":
#判断是否超过用户空间配额
if self.userspace + fsize > self.totalspace:
common.message("用户磁盘空间不足,无法上传文件,请联系管理员!","ERROR")
else:
self.userspace += fsize
new_size = 0
with open(file_name,'rb') as f:
for line in f:
self.client.sendall(line)
new_size += len(line)
# 打印上传进度条
common.progress_bar(new_size,fsize)
if new_size >= fsize:
break
#断点续传文件
if put_msg.split("|")[0] == "continue":
send_size = int(put_msg.split("|")[1])
common.message("服务端存在此文件,但未上传完,开始断点续传......","INFO")
new_size = 0
with open(file_name,'rb') as f:
#用seek来进行文件指针的偏移,实现断点续传的功能
f.seek(send_size)
while fsize - send_size > 1024:
revedata = f.read(1024)
self.client.sendall(revedata)
new_size += len(revedata)
#打印上传进度条
common.progress_bar(new_size, fsize)
else:
revedata = f.read(fsize - send_size)
self.client.sendall(revedata)
# 打印上传进度条
common.progress_bar(new_size, fsize) #不存在断点文件情况,询问是否覆盖掉原文件
if put_msg == "full":
inp_msg = common.message("服务端存在完整文件,是否覆盖掉原文件[输入y或n]:","INFO")
inp = str(input(inp_msg)).strip().lower()
if inp == "y":
with open(file_name, 'rb') as f:
new_size = 0
for line in f:
self.client.sendall(line)
new_size += len(line)
#打印上传进度条
common.progress_bar(new_size, fsize)
if new_size >= fsize:
break
elif inp == "n":
sys.exit()
else:
common.message("无效命令", "ERROR")
logger.info("upload file<{0}> successful".format(file_name))
common.message("文件上传成功", "INFO")
except Exception as e:
logger.error("文件上传失败:{0}".format(e))
common.message("文件上传失败!", "ERROR")
else:
common.message("文件不存在!", "ERROR") def get(self,command):
"""
下载文件
:param command:
:return:
"""
return_result = ""
# 发送基本信息到服务端 (command,username,file)
self.client.send(bytes(command, encoding='utf-8'))
# 先接收到命令是否正确标识,1 文件存在, 0 文件不存在
ack_by_server = self.client.recv(1024)
try:
# 文件名错误,当前路径下找不到
if str(ack_by_server, encoding='utf-8') == "":
return_result = "\n当前目录下未找到指定的文件,请到存在目录下执行get操作!"
else:
# 给服务端回应收到,防止粘包
self.client.send(bytes("ok", 'utf8')) # 文件存在,开始接收文件基本信息(大小,文件名)
file_info = self.client.recv(1024).decode()
file_size = int(file_info.split("|")[0])
file_name = file_info.split("|")[1]
file_md5 = file_info.split("|")[2] # 2 发送 ready 标识,准备开始接收文件
self.client.send(bytes("ready", 'utf8')) # 3 开始接收数据了
has_recv = 0
with open(os.path.join(settings.DOWNLOAD_FILE_PATH, file_name), 'wb') as f:
while True:
# 如果文件总大小等于已经接收的文件大小,则退出
if file_size == has_recv:
break
data = self.client.recv(1024)
f.write(data)
has_recv += len(data)
# 打印下载进度条
common.progress_bar(has_recv, file_size)
return_result = "\n文件下载成功"
logger.info("download file<{0}> from server successful".format(file_name))
# md5文件验证
check_md5 = common.md5sum(os.path.join(settings.DOWNLOAD_FILE_PATH, file_name))
if check_md5 == file_md5:
logger.info("md5 check for file<{0}> succ!".format(file_name))
return_result += ", MD5 验证成功! "
else:
return_result += ", MD5 验证文件不匹配! "
common.message(return_result,"INFO")
except Exception as e:
logger.error(e)
client
#!/usr/bin/env python
#coding=utf-8
__author__ = 'yinjia' import os,sys,logging,hashlib,time
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from config import settings class Logger(object):
"""
日志记录,写入指定日志文件
"""
def __init__(self,logger): create_time = time.strftime('%Y-%m-%d %H:%M:%S')
format = '[%(name)s]:[%(asctime)s] [%(filename)s|%(funcName)s] [line:%(lineno)d] %(levelname)-8s: %(message)s' # 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.INFO) # 创建一个handler,用于写入日志文件
fp = logging.FileHandler(settings.LOGS) # 定义handler的输出格式formatter
fpmatter = logging.Formatter(format)
fp.setFormatter(fpmatter) # 给logging添加handler
self.logger.addHandler(fp) def getlog(self):
return self.logger def md5(arg):
"""
密码进行md5加密
:param arg: 用户的密码
:return: 返回进行加密后的密码
"""
result = hashlib.md5()
result.update(arg.encode())
return result.hexdigest() def md5sum(filename):
"""
用于获取文件的md5值
:param filename: 文件名
:return: MD5码
"""
if not os.path.isfile(filename): # 如果校验md5的文件不是文件,返回空
return False
myhash = hashlib.md5()
f = open(filename, 'rb')
while True:
b = f.read(1024)
if not b:
break
myhash.update(b)
f.close()
return myhash.hexdigest() def message(msg,type):
"""
根据不同的消息类型,打印出消息内容以不同的颜色显示
:param msg: 消息内容
:param type: 消息类型
:return: 返回格式化后的消息内容
"""
if type == "CRITICAL":
show_msg = "\n\033[1;33m{0}\033[0m\n".format(msg)
elif type == "ERROR":
show_msg = "\n\033[1;31m{0}\033[0m\n".format(msg)
elif type == "WARNING":
show_msg = "\n\033[1;32m{0}\033[0m\n".format(msg)
elif type == "INFO":
show_msg = "\n\033[1;36m{0}\033[0m\n".format(msg)
else:
show_msg = "\n{0}\n".format(msg)
print(show_msg) def progress_bar(cache, totalsize):
"""
打印进度条
:param cache: 缓存字节大小
:param totalsize: 文件总共字节
:return:
"""
ret = cache / totalsize
num = int(ret * 100)
view = '\r%d%% |%s' % (num, num * "*")
sys.stdout.write(view)
sys.stdout.flush() def input_command(msg):
flag = False
while not flag:
command_list = ["put","get","ls","cd","mk","delete","quit"]
command_inp = input(msg).strip()
if command_inp == "ls":
return_command = "{0}|".format(command_inp)
flag = True
elif command_inp == "quit":
return_command = command_inp
flag = True
else:
if command_inp.count("|") != 1:
message("输入命令不合法!","ERROR")
else:
#获取命令
cmd,args = command_inp.strip().lower().split("|")
if cmd not in command_list:
message("输入命令不合法!", "ERROR")
else:
return_command = "{0}|{1}".format(cmd, args)
flag = True
return return_command
common
- 部分效果展示图
- [备注]:完整代码详见:
Hithub: https://github.com/yingoja/FTPServer
python【项目】:基于socket的FTP服务器的更多相关文章
- day-1 用python编写一个简易的FTP服务器
从某宝上购买了一份<Python神经网络深度学习>课程,按照视频教程,用python语言,写了一个简易的FTP服务端和客户端程序,以前也用C++写过聊天程序,编程思路差不多,但是pytho ...
- 【项目】手写FTP服务器-C++实现FTP服务器
X_FTP_server 手写FTP服务器-C++实现FTP服务器 项目Gitee链接:https://gitee.com/hsby/ftp_Server 简介 一个基于libevent的高并发FTP ...
- C#通过socket判断FTP服务器是否通畅并判断用户名密码是否正确
private static ManualResetEvent timeoutObject; private static Socket socket = null; private static b ...
- Python练习-基于socket的FTPServer
# 编辑者:闫龙 import socket,json,struct class MySocket: with open("FtpServiceConfig","r&qu ...
- 用 Python 快速实现 HTTP 和 FTP 服务器
用 Python 快速实现 HTTP 服务器 有时你需临时搭建一个简单的 Web Server,但你又不想去安装 Apache.Nginx 等这类功能较复杂的 HTTP 服务程序时.这时可以使用 ...
- 【腾讯云服务器】基于centos7搭建ftp服务器(vsftpd)
该博客分为三部分设置,1.ftp服务器搭建.2.防火墙设置 3.腾讯云安全组 一.ftp服务器搭建 1.1 安装vsftpd yum install vsftpd -y 1.2 启动vsftpd服 ...
- 基于CentOS安装FTP服务器
操作系统环境: CentOS Linux release 7.4.1708 (Core) 使用yum安装ftp服务: yum install -y vsftpd 添加系统用户作为登录ftp服务器并修改 ...
- 用Python在Android手机上架FTP服务器
当我们没有带数据线却将手机上的文件共享到电脑上时,架个简单的FTP服务器 可以帮我们快速解决问题.以共享手机里的照片为例: 首先将电脑.手机接入同一个wifi. 然后,手机上用QPython执行以下脚 ...
- python学习之socket创建html服务器
#coding:utf-8 import socket def process_request(request): request.recv(1024)#读取接受的字节 request.send(&q ...
随机推荐
- PHP关于VC9和VC6以及Thread Safe和Non Thread Safe版本选择的问题
一.如何选择PHP5.3的VC9版本和VC6版本 VC6版本是使用Visual Studio 6编译器编译的,如果你的PHP是用Apache来架设的,那你就选择VC6版本. VC9版本是使用 ...
- Mac连接HDMI后没有声音
Mac连接HDMI后,会自动切换到HDMI设备进行发声,若HDMI设备没有声音,则不会发声.必须切换回内置扬声器才能有声音,或者拔出HDMI设备. 系统偏好设置 - 声音 - 输出 - 选择内置扬声 ...
- 前端基础----jquery
一.jQuery是什么? <1> jQuery由美国人John Resig创建,至今已吸引了来自世界各地的众多 javascript高手加入其team. <2>jQuery是继 ...
- 使用log4net将日志文件输出为csv格式
我们在编写程序时,会在程序运行过程中记录一些日志.log4net作为一款经久耐用的日志组件,值得我们信赖.在中小型公司中,往往没有专业的日志服务器来处理应用程序产生的日志,而格式化不好的日志文件又为上 ...
- Windows服务器下用IIS Rewrite组件为IIS设置伪静态方法
1.将下载的IIS Rewrite 组件解压,放到适当的目录(如 C:Rewrite)下,IIS Rewrite 组件下载 http://www.helicontech.com/download- ...
- centos中设置swap交换空间的大小设置和swappiness的比例设置
首先使用free -m命令查看内存使用情况和swap的大小 关闭swap: 设置swap的大小: bs指的是Block Size,就是每一块的大小.这里的例子是1M,意思就是count的数字,是以1M ...
- CountUp.js让页面数字跳动起来
CountUp.js 无依赖的.轻量级的 JavaScript 类,可以用来快速创建以一种更有趣的动画方式显示数值数据.尽管它的名字叫 countUp,但其实可以在两个方向进行变化,这是根据你传递的 ...
- thinkphp 5 where 组合条件map数组or
if($inviterId>0) { $arr = Db::table("tablename")-> where("pid=$inviterId") ...
- 谷歌地图 API 开发之添加标记(解析以及补充)
今天又看了下官网,发现官网上有地图标记的详细说明.当时居然眼瞎看不见,还琢磨了好久...#$%^&,一定是项目太急,没看到(^o^)/~地址:https://developers.google ...
- HDU 4978 计算 凸包
有以无限间隔$D$的水平线分割的平面,在上面随机投下一个圆,圆中有一些点,点之间两两成一条线段,问随机投下至少有一条线段于平行线相交的概率. 以下是不严(luan)谨(lai)的思路. 首先都知道对于 ...