FTP

要求:
.用户加密认证
.允许同时多用户登录
.每个用户有自己的家目录,且只能访问自己的家目录
.对用户进行磁盘配额,每个用户的可用空间不同
.允许用户在ftp server上随意切换目录
.允许用户查看当前目录下的文件
.允许上传和下载文件,并保证文件的一致性md5
.文件传输过程中显示进度条
.支持文件的断点续传
使用:
1.启动ftp_server.py
2.创建用户,输入:用户名(默认密码是123)
3.启动FTP服务器
4.启动客户端ftp_client.py
5.输入用户名和密码:alex 123 | kris 123
6.与服务器server交互:
6.1. get 1.jpg 下载图片
6.2. put test.txt 上传文件
6.3. ls 查询当前目录下的文件列表
6.4. mkdir test 创建文件夹
6.5. cd test 切换目录
6.6. remove test 删除文件或空文件夹

Git:https://github.com/kris-2018/task

流程图:

  

client客户端

download文件是储存下载的文件;upload是上传文件的储存库(download里边可以不放东西,等待下载即可;upload里边放你准备上传给服务端的文件)

ftp_client.py

# -*- coding:utf- -*-
#Author:Kris import os,sys,socket,struct,pickle,hashlib class FTPClient():
HOST = '127.0.0.1' # 服务端的IP
PORT = # 服务端的端口
MAX_RECV_SIZE =
DOWNLOAD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),'download')
UPLOAD_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),'upload') def __init__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect() def connect(self):
"""连接服务端server"""
try:
self.socket.connect((self.HOST,self.PORT))
except Exception:
exit('\033[1;31mserver还未启动\033[0m') def get_recv(self):
"""获取server返回的数据"""
return pickle.loads(self.socket.recv(self.MAX_RECV_SIZE)) def auth(self):
"""用户认证"""
count =
while count < :
name = input('username>>>:').strip()
if not name: continue
password = input('password>>>:').strip()
user_dic = {
'username': name,
'password': password
}
self.socket.send(pickle.dumps(user_dic)) #把用户名和密码发送给server
res = struct.unpack('i',self.socket.recv())[]
if res: #接收返回的信息,并判断
print('welcome'.center(,'-'))
user_info_dic = self.get_recv()
self.username = user_info_dic.get('username')
print(user_info_dic)
return True
else:
print('\033[1;31m用户名或密码不对!\033[0m')
count += def readfile(self):
"""读取文件,得到文件内容的bytes型"""
with open(self.filepath,'rb') as f:
filedata = f.read()
return filedata def getfile_md5(self):
"""对文件内容md5"""
return hashlib.md5(self.readfile()).hexdigest() def progress_bar(self,num,get_size,file_size):
"""进度条显示"""
float_rate = get_size / file_size
# rate = str(float_rate * )[:] # 95.85%
rate = round(float_rate * ,) # 95.85% if num == : #1表示下载
sys.stdout.write('\r已下载:\033[1;32m{0}%\033[0m'.format(rate))
elif num == : # 表示上传
sys.stdout.write('\r已上传:\033[1;32m{0}%\033[0m'.format(rate))
sys.stdout.flush() def get(self):
"""从server下载文件到client"""
if len(self.cmds) > :
filename = self.cmds[]
self.filepath = os.path.join(self.DOWNLOAD_PATH, filename) #结合目录名和文件名
if os.path.isfile(self.filepath): #如果文件存在 支持断点续传
temp_file_size = os.path.getsize(self.filepath)
self.socket.send(struct.pack('i',temp_file_size))
header_size = struct.unpack('i', self.socket.recv())[]
if header_size: #如果存在
header_dic = pickle.loads(self.socket.recv(header_size))
print(header_dic)
filename = header_dic.get('filename')
file_size = header_dic.get('file_size')
file_md5 = header_dic.get('file_md5') if temp_file_size == file_size:
print('\033[1;32m文件已存在\033[0m')
else:
print('\033[1;33m正在进行断点续传...\033[0m')
download_filepath = os.path.join(self.DOWNLOAD_PATH, filename)
with open(download_filepath, 'ab') as f:
f.seek(temp_file_size)
get_size = temp_file_size
while get_size < file_size:
file_bytes = self.socket.recv(self.MAX_RECV_SIZE)
f.write(file_bytes)
get_size += len(file_bytes)
self.progress_bar(, get_size, file_size) # 1表示下载 if self.getfile_md5() == file_md5: #判断下载下来的文件MD5值和server传过来的MD5值是否一致
print('\n\033[1;32m下载成功\033[0m')
else:
print('\n\033[1;32m下载文件大小与源文件大小不一致,请重新下载,将会支持断点续传\033[0m')
else:
print('\033[1;31m该文件,之前被下载了一部分,但是server端的该文件,已被删除,无法再次下载\033[0m')
else: #文件第一次下载
self.socket.send(struct.pack('i',)) # 0表示之前没有下载过
header_size = struct.unpack('i', self.socket.recv())[]
if header_size:
header_dic = pickle.loads(self.socket.recv(header_size))
print(header_dic)
filename = header_dic.get('filename')
file_size = header_dic.get('file_size')
file_md5 = header_dic.get('file_md5') download_filepath = os.path.join(self.DOWNLOAD_PATH, filename)
with open(download_filepath, 'wb') as f:
get_size =
while get_size < file_size:
file_bytes = self.socket.recv(self.MAX_RECV_SIZE)
f.write(file_bytes)
get_size += len(file_bytes)
self.progress_bar(, get_size, file_size) #1表示下载
print('总大小:%s已下载:%s'% (file_size, get_size))
if self.getfile_md5() == file_md5: #判断下载下来的文件MD5值和server传过来的MD5值是否一致
print('\n\033[1;32m恭喜您,下载成功\033[0m')
else:
print('\n\033[1;32m下载失败,再次下载支持断点续传\033[0m')
else:
print('\033[1;31m当前目录下,文件不存在\033[0m')
else:
print('用户没有输入文件名') def put(self):
"""往server自己的home/alice目录下,当前工作的目录下上传文件"""
if len(self.cmds) > : #确保用户输入了文件名
filename = self.cmds[]
filepath = os.path.join(self.UPLOAD_PATH, filename)
if os.path.isfile(filepath):
self.socket.send(struct.pack('i', ))
self.filepath = filepath
filesize = os.path.getsize(self.filepath)
header_dic = {
'filename': filename,
'file_md5': self.getfile_md5(),
'file_size': filesize
}
header_bytes = pickle.dumps(header_dic)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes) state = struct.unpack('i', self.socket.recv())[]
if state: #已经存在了
has_state = struct.unpack('i', self.socket.recv())[]
if has_state:
quota_state = struct.unpack('i', self.socket.recv())[]
if quota_state:
has_size = struct.unpack('i', self.socket.recv())[]
with open(self.filepath, 'rb') as f:
f.seek(has_size)
for line in f:
self.socket.send(line)
recv_size = struct.unpack('i', self.socket.recv())[]
self.progress_bar(, recv_size, filesize)
success_state = struct.unpack('i', self.socket.recv())[]
# 这里一定要判断,因为最后一次send(line)之后等待server返回,
# server返回,最后一次的recv_size==file_size,但client已经跳出了循环,
# 所以在for外面接收的success_state其实时file_size,这种情况只针对大文件
if success_state == filesize:
success_state = struct.unpack('i', self.socket.recv())[] if success_state:
print('\n\033[1;32m恭喜您,上传成功\033[0m')
else:
print('\n\033[1;32m上传失败\033[0m')
else: #超出了配额
print('\033[1;31m超出了用户的配额\033[0m')
else: # 存在的大小 和文件大小一致 不必再传
print('\033[1;31m当前目录下,文件已经存在\033[0m')
else: #第一次传
quota_state = struct.unpack('i', self.socket.recv())[]
if quota_state:
with open(self.filepath, 'rb') as f:
send_bytes = b''
for line in f:
self.socket.send(line)
send_bytes += line
print('总大小:%s 已上传:%s' % (filesize, len(send_bytes))) recv_size = struct.unpack('i', self.socket.recv())[]
self.progress_bar(, recv_size, filesize) success_state = struct.unpack('i', self.socket.recv())[] if success_state == filesize:
success_state = struct.unpack('i', self.socket.recv())[] if success_state:
print('\n\033[1;32m恭喜您,上传成功\033[0m')
else:
print('\n\033[1;32m上传失败\033[0m')
else: # 超出了配额
print('\033[1;31m超出了用户的配额\033[0m')
else: #文件不存在
print('\033[1;31m文件不存在\033[0m')
self.socket.send(struct.pack('i', ))
else:
print('用户没有输入文件名') def ls(self):
"""查询当前工作目录下,文件列表"""
dir_size = struct.unpack('i', self.socket.recv())[]
recv_size =
recv_bytes = b''
while recv_size < dir_size:
temp_bytes = self.socket.recv(self.MAX_RECV_SIZE)
recv_bytes += temp_bytes
recv_size += len(temp_bytes)
print(recv_bytes.decode('gbk')) # gbk适合windows utf- 适合linux def mkdir(self):
"""增加目录"""
if len(self.cmds) > :
res = struct.unpack('i',self.socket.recv())[]
if res:
print('\033[1;32m在当前目录下,增加目录: %s 成功\033[0m'%self.cmds[])
else:
print('\033[1;31m增加目录失败\033[0m')
else:
print('没有输入要增加的目录名') def cd(self):
"""切换目录"""
if len(self.cmds) > :
res = struct.unpack('i', self.socket.recv())[]
if res:
print('\033[1;32m切换成功\033[0m')
else:
print('\033[1;31m切换失败\033[0m')
else:
print('没有输入要切换的目录名') def remove(self):
"""删除指定的文件,或者文件夹"""
if len(self.cmds) > :
res = struct.unpack('i', self.socket.recv())[]
if res:
print('\033[1;32m删除成功\033[0m')
else:
print('\033[1;31m删除失败\033[0m')
else:
print('没有输入要删除的文件') def interactive(self):
"""与server交互"""
if self.auth():
while True:
try:
user_input = input('[%s]>>>:'%self.username)
if not user_input: continue
self.socket.send(user_input.encode('utf-8'))
self.cmds = user_input.split()
if hasattr(self,self.cmds[]):
getattr(self,self.cmds[])()
else:
print('请重新输入')
except Exception as e: # server关闭了
print(e)
break
def close(self):
self.socket.close() if __name__ == '__main__':
ftp_client = FTPClient()
ftp_client.interactive()
ftp_client.close()

server服务端

bin下的文件

  ftp_server.py

# -*- coding:utf- -*-
#Author:Kris
import os
import sys BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR) from core.main import Manager if __name__ == '__main__':
Manager().run()

conf下的文件

  accounts.ini(这个可以在执行中创建)

[alex]
password = 202cb962ac59075b964b07152d234b70
homedir = home/alex
quota = [kris]
password = 202cb962ac59075b964b07152d234b70
homedir = home/kris
quota = [shanshan]
password = 202cb962ac59075b964b07152d234b70
homedir = home/shanshan
quota =

  settings.py

# -*- coding:utf- -*-
#Author:Kris
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ACCOUNTS_FILE = os.path.join(BASE_DIR,'conf','accounts.ini') HOST = '127.0.0.1'
PORT =

core下的文件

  main.py

# -*- coding:utf- -*-
#Author:Kris
from core.user_handle import UserHandle
from core.server import FTPServer class Manager():
def __init__(self):
pass def start_ftp(self):
"""启动ftp_server端"""
server = FTPServer()
server.run()
server.close() def create_user(self):
"""创建用户"""
username = input('username>>>:').strip()
UserHandle(username).add_user() def quit_func(self):
quit('bye bye ...') def run(self):
msg = '''\033[31;0m
.启动ftp服务器
.创建用户
.退出\[0m\n
'''
msg_dic = {'': 'start_ftp', '': 'create_user', '': 'quit_func'}
while True:
print(msg)
num = input('num>>>:').strip()
if num in msg_dic:
getattr(self,msg_dic[num])()
else:
print('\033[1;31m请重新选择\033[0m')

  server.py

# -*- coding:utf- -*-
#Author:Kris import os,socket,struct,pickle,hashlib,subprocess
from conf import settings
from core.user_handle import UserHandle class FTPServer():
MAX_SOCKET_LISTEN =
MAX_RECV_SIZE = def __init__(self):
self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.socket.bind((settings.HOST,settings.PORT))
self.socket.listen(self.MAX_SOCKET_LISTEN) def server_accept(self):
"""等待client链接"""
print('starting...')
while True:
self.conn,self.client_addr = self.socket.accept()
print('客户端地址:',self.client_addr)
try:
self.server_handle()
except Exception as e:
print(e)
self.conn.close()
def get_recv(self):
"""接收client发来的数据"""
return pickle.loads(self.conn.recv(self.MAX_RECV_SIZE)) def auth(self):
"""处理用户的认证请求
.根据username读取accounts.ini文件,password相比,判断用户是否存在
.将程序运行的目录从bin/ftp_server.py修改到用户home/alice,方便之后查询 ls
.给client返回用户的详细信息
"""
while True:
user_dic = self.get_recv()
username = user_dic.get('username')
user_handle = UserHandle(username)
user_data = user_handle.judge_user()
# 判断用户是否存在 返回列表
#如[('password','202cb962ac59075b964b07152d234b70'),('homedir','home/alex'),('quota','')]
if user_data:
if user_data[][] == hashlib.md5(user_dic.get('password').encode('utf-8')).hexdigest(): # 密码也相同
self.conn.send(struct.pack('i', )) #登录成功返回
self.username = username
self.homedir_path = '%s\%s\%s'%(settings.BASE_DIR,'home',self.username)
os.chdir(self.homedir_path) #将程序运行的目录名修改到用户home目录下
self.quota_bytes = int(user_data[][]) * * #将用户配额的大小从M改到字节
user_info_dic = {
'username': username,
'homedir': user_data[][],
'quota': user_data[][]
}
self.conn.send(pickle.dumps(user_info_dic)) #用户的详细信息发送到客户端
return True
else:
self.conn.send(struct.pack('i', )) #登录失败返回
else:
self.conn.send(struct.pack('i', )) def readfile(self):
"""读取文件,得到文件内容的bytes型"""
with open(self.filepath,'rb') as f:
filedata = f.read()
return filedata def getfile_md5(self):
"""对文件内容md5"""
return hashlib.md5(self.readfile()).hexdigest() def get(self):
"""从server下载文件到client
"""
if len(self.cmds) > :
filename = self.cmds[]
filepath = os.path.join(os.getcwd(),filename) #os.getcwd()得到当前工作目录
if os.path.isfile(filepath): #判断文件是否存在
exist_file_size = struct.unpack('i', self.conn.recv())[]
self.filepath = filepath
header_dic = {
'filename': filename,
'file_md5': self.getfile_md5(),
'file_size': os.path.getsize(self.filepath)
}
header_bytes = pickle.dumps(header_dic)
if exist_file_size: #表示之前被下载过 一部分
self.conn.send(struct.pack('i', len(header_bytes)))
self.conn.send(header_bytes)
if exist_file_size != os.path.getsize(self.filepath):
with open(self.filepath, 'rb') as f:
f.seek(exist_file_size)
for line in f:
self.conn.send(line)
else:
print('断点和文件本身大小一样')
else: #文件第一次下载
self.conn.send(struct.pack('i',len(header_bytes)))
self.conn.send(header_bytes)
with open(self.filepath,'rb') as f:
for line in f:
self.conn.send(line)
else:
print('当前目录下文件不存在')
self.conn.send(struct.pack('i',))
else:
print('用户没有输入文件名') def recursion_file(self,menu):
"""递归查询用户home/alice目录下的所有文件,算出文件的大小"""
res = os.listdir(menu) #指定目录下所有的文件和和目录名
for i in res:
path = '%s\%s' % (menu, i)
if os.path.isdir(path):#判断指定对象是否为目录
self.recursion_file(path)
elif os.path.isfile(path):
self.home_bytes_size += os.path.getsize(path) def current_home_size(self):
"""得到当前用户home/alice目录的大小,字节/M"""
self.home_bytes_size =
self.recursion_file(self.homedir_path)
print('字节:',self.home_bytes_size) # 单位是字节
home_m_size = round(self.home_bytes_size / / , )
print('单位M:', home_m_size) # 单位时 M def put(self):
"""从client上传文件到server当前工作目录下"""
if len(self.cmds) > :
state_size = struct.unpack('i', self.conn.recv())[]
if state_size:
self.current_home_size() #算出了home下已被占用的大小self.home_bytes_size
header_bytes = self.conn.recv(struct.unpack('i', self.conn.recv())[])
header_dic = pickle.loads(header_bytes)
print(header_dic)
filename = header_dic.get('filename')
file_size = header_dic.get('file_size')
file_md5 = header_dic.get('file_md5') upload_filepath = os.path.join(os.getcwd(), filename)
self.filepath = upload_filepath # 为了全局变量读取文件算md5时方便
if os.path.exists(upload_filepath): #文件已经存在
self.conn.send(struct.pack('i', ))
has_size = os.path.getsize(upload_filepath)
if has_size == file_size:
print('文件已经存在')
self.conn.send(struct.pack('i', ))
else: #上次没有传完 接着继续传
self.conn.send(struct.pack('i', ))
if self.home_bytes_size + int(file_size - has_size) > self.quota_bytes:
print('超出了用户的配额')
self.conn.send(struct.pack('i', ))
else:
self.conn.send(struct.pack('i', ))
self.conn.send(struct.pack('i', has_size))
with open(upload_filepath, 'ab') as f:
f.seek(has_size)
while has_size < file_size:
recv_bytes = self.conn.recv(self.MAX_RECV_SIZE)
f.write(recv_bytes)
has_size += len(recv_bytes)
self.conn.send(struct.pack('i', has_size)) # 为了显示 进度条 if self.getfile_md5() == file_md5: #判断下载下来的文件MD5值和server传过来的MD5值是否一致
print('\033[1;32m上传成功\033[0m')
self.conn.send(struct.pack('i', ))
else:
print('\033[1;32m上传失败\033[0m')
self.conn.send(struct.pack('i', ))
else: #第一次上传
self.conn.send(struct.pack('i', ))
if self.home_bytes_size + int(file_size) > self.quota_bytes:
print('超出了用户的配额')
self.conn.send(struct.pack('i', ))
else:
self.conn.send(struct.pack('i', ))
with open(upload_filepath, 'wb') as f:
recv_size =
while recv_size < file_size:
file_bytes = self.conn.recv(self.MAX_RECV_SIZE)
f.write(file_bytes)
recv_size += len(file_bytes)
self.conn.send(struct.pack('i', recv_size)) #为了进度条的显示 if self.getfile_md5() == file_md5: # 判断下载下来的文件MD5值和server传过来的MD5值是否一致
print('\033[1;32m上传成功\033[0m')
self.conn.send(struct.pack('i', ))
else:
print('\033[1;32m上传失败\033[0m')
self.conn.send(struct.pack('i', ))
else:
print('待传的文件不存在')
else:
print('用户没有输入文件名') def ls(self):
"""查询当前工作目录下,先返回文件列表的大小,在返回查询的结果"""
subpro_obj = subprocess.Popen('dir', shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = subpro_obj.stdout.read()
stderr = subpro_obj.stderr.read()
self.conn.send(struct.pack('i', len(stdout + stderr)))
self.conn.send(stdout)
self.conn.send(stderr) def mkdir(self):
"""在当前目录下,增加目录"""
if len(self.cmds) > :
mkdir_path = os.path.join(os.getcwd(),self.cmds[])
if not os.path.exists(mkdir_path): #查看目录名是否存在
os.mkdir(mkdir_path)
print('增加目录成功')
self.conn.send(struct.pack('i', )) #增加目录成功,返回1
else:
print('目录名已存在')
self.conn.send(struct.pack('i', )) #失败返回0
else:
print('用户没有输入目录名') def cd(self):
"""切换目录"""
if len(self.cmds) > :
dir_path = os.path.join(os.getcwd(), self.cmds[])
if os.path.isdir(dir_path): #查看是否是目录名
previous_path = os.getcwd() #拿到当前工作的目录
os.chdir(dir_path) #改变工作目录到...
target_dir = os.getcwd()
if self.homedir_path in target_dir: #判断homedir_path是否在目标目录
print('切换成功')
self.conn.send(struct.pack('i', )) #切换成功返回1
else:
print('切换失败') # 切换失败后,返回到之前的目录下
os.chdir(previous_path)
self.conn.send(struct.pack('i', ))
else:
print('要切换的目录不在该目录下')
self.conn.send(struct.pack('i', ))
else:
print('没有传入切换的目录名') def remove(self):
"""删除指定的文件,或者空文件夹"""
if len(self.cmds) > :
file_name = self.cmds[]
file_path = '%s\%s'%(os.getcwd(),file_name)
if os.path.isfile(file_path):
os.remove(file_path)
self.conn.send(struct.pack('i', ))
elif os.path.isdir(file_path): #删除空目录
if not len(os.listdir(file_path)):
os.removedirs(file_path)
print('删除成功')
self.conn.send(struct.pack('i', ))
else:
print('文件夹非空,不能删除')
self.conn.send(struct.pack('i', ))
else:
print('不是文件也不是文件夹')
self.conn.send(struct.pack('i', ))
else:
print('没有输入要删除的文件') def server_handle(self):
"""处理与用户的交互指令"""
if self.auth():
print('\033[1;32m用户登录成功\033[0m')
while True:
try: #try ...except 适合windows client 断开
user_input = self.conn.recv(self.MAX_RECV_SIZE).decode('utf-8')
#if not user_input: continue # 这里适合 linux client 断开
self.cmds = user_input.split()
if hasattr(self,self.cmds[]):
getattr(self,self.cmds[])()
else:
print('\033[1;31m请用户重复输入\033[0m')
except Exception:
break def run(self):
self.server_accept() def close(self):
self.socket.close()

  user_handle.py

# -*- coding:utf- -*-
#Author:Kris import configparser
import hashlib
import os from conf import settings class UserHandle():
def __init__(self,username):
self.username = username
self.config = configparser.ConfigParser() #先生成一个对象
self.config.read(settings.ACCOUNTS_FILE)
@property
def password(self):
"""生成用户的默认密码 123"""
return hashlib.md5(''.encode('utf-8')).hexdigest()
@property
def quota(self):
"""生成每个用户的磁盘配额"""
quota = input('请输入用户的磁盘配额大小>>>:').strip()
if quota.isdigit():
return quota
else:
exit('\033[1;31m磁盘配额须是整数\033[0m')
def add_user(self):
"""创建用户,存到accounts.ini"""
if not self.config.has_section(self.username):
print('creating username is : ', self.username)
self.config.add_section(self.username)
self.config.set(self.username,'password',self.password)
self.config.set(self.username,'homedir','home/'+self.username)
self.config.set(self.username,'quota',self.quota)
with open(settings.ACCOUNTS_FILE,'w') as f:
self.config.write(f)
os.mkdir(os.path.join(settings.BASE_DIR, 'home', self.username))#创建用户的home文件夹
print('\033[1;32m创建用户成功\033[0m')
else:
print('\033[1;31m用户已存在\033[0m') def judge_user(self):
"""判断用户是否存在"""
if self.config.has_section(self.username):
return self.config.items(self.username)
else:
return

home下的文件

alex、kris为用户自己的家目录;家目录下面可以自己创建新的目录;家目录储存的文件就是服务端用来传输给客户端的文件,执行下载功能;(可以自己添加文件、图片、视频格式等文件)

开发一个支持多用户同时在线的FTP程序的更多相关文章

  1. python 开发一个支持多用户在线的FTP

    ### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用 ...

  2. Python3学习之路~8.6 开发一个支持多用户在线的FTP程序-代码实现

    作业: 开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  3. 开发一个支持多用户在线的FTP程序

    要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目 ...

  4. (转)Python开发程序:支持多用户在线的FTP程序

    原文链接:http://www.itnose.net/detail/6642756.html 作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ...

  5. 老男孩python作业7-开发一个支持多用户在线的FTP程序

    作业6:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp s ...

  6. 使用ASP.NET Core MVC 和 Entity Framework Core 开发一个CRUD(增删改查)的应用程序

    使用ASP.NET Core MVC 和 Entity Framework Core 开发一个CRUD(增删改查)的应用程序 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻 ...

  7. 实现支持多用户在线的FTP程序(C/S)

    1. 需求 1. 用户加密认证 2. 允许多用户登录 3. 每个用户都有自己的家目录,且只能访问自己的家目录 4. 对用户进行磁盘分配,每一个用户的可用空间可以自己设置 5. 允许用户在ftp ser ...

  8. 如何快速开发一个支持高效、高并发的分布式ID生成器

    ID生成器是指能产生不重复ID服务的程序,在后台开发过程中,尤其是分布式服务.微服务程序开发过程中,经常会用到,例如,为用户的每个请求产生一个唯一ID.为每个消息产生一个ID等等,ID生成器也是进行无 ...

  9. 基于c++11新标准开发一个支持多线程高并发的网络库

    背景 新的c++11标准出后,c++语法得到了非常多的扩展,比起以往不论什么时候都要灵活和高效,提高了程序编码的效率,为软件开发者节省了不少的时间. 之前我也写过基于ACE的网络server框架,但A ...

随机推荐

  1. 卷积神经网络(CNN)学习笔记1:基础入门

    卷积神经网络(CNN)学习笔记1:基础入门 Posted on 2016-03-01   |   In Machine Learning  |   9 Comments  |   14935  Vie ...

  2. Django学习手册 - ORM choice字段 如何在页面上显示值

    在module操作过程中使用choice字段: 核心: obj.get_字段名_display 定义module 数据结构: class msg(models.Model): choice = ( ( ...

  3. java Comparable 和 Cloneable接口

    Comparable接口定义了compareTo方法,用于比较对象. 例如,在JavaAPI中,Integer.BigInteger.String以及Date类定义如下 Cloneable接口 Clo ...

  4. Hard Negative Mning

    对于hard negative mining的解释,引用一波知乎: 链接:https://www.zhihu.com/question/46292829/answer/235112564来源:知乎 先 ...

  5. 四、Logisitic Regssion练习(转载)

    转载:http://www.cnblogs.com/tornadomeet/archive/2013/03/16/2963919.html 牛顿法:http://blog.csdn.net/xp215 ...

  6. Linux内核驱动基础(一)常用宏定义【转】

    转自:http://blog.csdn.net/tommy_wxie/article/details/9427081 一: __init和__initdata  : __exit和__exitdata ...

  7. mysql授权报错 ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

    授权用户时报错,ERROR 1819 (HY000): Your password does not satisfy the current policy requirements 原因为其实与val ...

  8. pyspider使用

    #!/usr/bin/env python # -*- encoding: utf-8 -*- # Created on 2018-11-08 22:33:55 # Project: qsbk fro ...

  9. 调用链系列二、Zipkin 和 Brave 实现(springmvc、RestTemplate)服务调用跟踪

    Brave介绍 1.Brave简介 Brave 是用来装备 Java 程序的类库,提供了面向标准Servlet.Spring MVC.Http Client.JAX RS.Jersey.Resteas ...

  10. 查看tomcat运行状态

    实时查看tomcat并发连接数: netstat -na | grep ESTAB | grep 8080 | wc -l 实时查看apache并发连接数: netstat -na | grep ES ...