热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:基于线程开发一个FTP服务器

篇首语:本文由编程笔记#小编为大家整理,主要介绍了基于线程开发一个FTP服务器相关的知识,希望对你有一定的参考价值。 一,项目题目:基于线程开发一个FTP服务器二,项目要求:基本要求:1.用户加密认证

篇首语:本文由编程笔记#小编为大家整理,主要介绍了基于线程开发一个FTP服务器相关的知识,希望对你有一定的参考价值。



一,项目题目:基于线程开发一个FTP服务器
二,项目要求:

基本要求:


1.用户加密认证

2.允许同时多用户登录

3.每个用户有自己的家目录 ,且只能访问自己的家目录

4.对用户进行磁盘配额,每个用户的可用空间不同

5.允许用户在ftp server上随意切换目录

6.允许用户查看当前目录下文件

7.允许上传和下载文件,保证文件一致性(md5)

8.文件传输过程中显示进度条

9.附加功能:支持文件的断点续传


扩展需求:


作业需求:

1.在之前开发的FTP基础上,开发支持多并发的功能

2.不能使用SocketServer模块,必须自己实现多线程

3.必须用到队列Queue模块,实现线程池

4.允许配置最大并发数,比如允许只有10个并发用户

 


三,注意事项:

基本操作:满足需求1,2,3,4
大神要求:代码写的健壮、清晰

 


四,项目分析:

1,用户加密认证


  这个肯定需要用到configparser 和hashlib模块,用md5进行加密,服务端与用户端进行交互前,肯定需要进行认证,在服务端进行认证,客户端需要发送用户名及密码,但是为了安全起见,服务端数据库中的密码应该是加密后的密文,客户端登陆认证时也应该发送密文到服务端,服务端接受到密文与数据库中对应的密文进行比较。


2,查看自己的当前目录下的文件


这个只需要写一个dir就ok简单的说,使用configparse模块就可以完成


3,文件传输中显示进度条


下载的进度条比较好实现,我们可以从服务端受到将要下载的文件的大小,

上传的进度条,我们可以利用文件操作的tell()方法,获取当前指针位置(字节)


4,在之前的基础上实现多并发的功能


  并发,是伪并行,,即看起来是同时运行的,单个CPU+多道技术就可以实现并发
  多道技术概念回顾:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并发,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)


5,不能使用SocketServer模块,必须自己实现多线程 


多线程(即多个控制线程)的概念是,在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多个流水线,都共有一个车间的资源。例如,北京地铁与上海地铁是不同的进程,而北京地铁里的1号线是一个线程,北京地铁所有的线路共享该地铁的所有资源。

为什么使用多线程?
  1,同一个进程内可以共享该进程内的地址资源
  2,创建线程的开销远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内至少创建一个流水线,但是创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小)


6,必须用到队列Queue模块,实现线程池

  使用线程池即可,如果不会可以参考此博客的练习题:http://www.cnblogs.com/wj-1314/p/9039970.html


7,允许配置最大并发数,比如允许只有10个并发用户


8,主要思路


- 1 对于此项目,最初的想法是写出上传,和下载文件的程序,包括客户端和服务端。

- 2 在此基础上扩展程序,包括提出开始程序到bin里面,配置文件在config里面

- 3 然后把上传文件和下载文件的程序进行断点续传的程序重构

- 4 在此基础上,对文件进行加密

- 5 增加功能,包括设置进度条,增加查看功能,增加目录功能,删除文件功能,切换目录功能等

- 6 然后再设置磁盘分配功能,完善内容

- 7 然后添加用户登陆,包括对用户的密码加密等功能

- 8 写完后检查程序

 


五,README文件

## 程序介绍:
- 实现了基于线程开发一个FTP服务器的常用功能
- 基本功能全部用python的基础知识实现,用到了sockethashlibconfigparseossyspickle函数模块类知识
- 在保证了支持多并发的功能上,不使用SocketServer模块,自己实现了多线程,而且使用了队列

## 概述
本次作业文件夹一共包含了以下4个文件:
- 程序结构图:整个Thread_based_FTP_homework的程序文件结构
- 程序结构文件:整个Thread_based_FTP_homework的程序文件结构
- 程序文件: Thread_based_FTP_homework
- 程序说明文件:README.md

## 程序要求
- 1.用户加密认证
- 2.允许同时多用户登录
- 3.每个用户有自己的家目录 ,且只能访问自己的家目录
- 4.对用户进行磁盘配额,每个用户的可用空间不同
- 5.允许用户在ftp server上随意切换目录
- 6.允许用户查看当前目录下文件
- 7.允许上传和下载文件,保证文件一致性(md5)
- 8.文件传输过程中显示进度条
- 9.附加功能:支持文件的断点续传

- 10.在之前开发的FTP基础上,开发支持多并发的功能
- 11.不能使用SocketServer模块,必须自己实现多线程
- 12.必须用到队列Queue模块,实现线程池
- 13.允许配置最大并发数,比如允许只有10个并发用户

## 本项目思路
- 1 对于此次项目,在上次作业的基础上完成
- 2 本次首要任务,为了降低程序的耦合性,将把server端和client端的许多东西分出来,保证一个函数只做一件事情
- 3 发现了上次作业里面出现的小问题,进行了解决
- 4 使用队列Queue模块,实现多线程
- 5 设置配置最大的并发数,此处设置在settings里面,最大并发用户设置为3


##### 备注(程序结构)
> 目前还不会把程序树放在README.md里面,所以做出程序结构的txt版本和图片版本,放在文件外面方便查看

## 对几个实例文件的说明
### 几个实例文件全是为了上传和下载使用,自己随便找的素材,没有把视频,照片上传

## 不足及其改进的方面
### 每次程序从用户登陆到使用只能完成一次功能,不能重复使用

## 程序结构


│ Thread_based_FTP_homework
│ __init__.py

├─client # 客户端程序入口
│ │ __init__.py
│ ├─bin # 可执行程序入口目录
│ │ run.py
│ │ __init__.py
│ ├─config # 配置文件目录
│ │ │ settings.py # 配置文件
│ │ │ __init__.py
│ ├─core # 主要逻辑程序目录
│ │ │ file_func.py # client端文件操作功能模块
│ │ │ ftp_client.py # client端主程序模块
│ │ │ md5_func.py # client端对文件加密操作功能模块
│ │ │ progress_bar_func_func.py # client端文件下载进度条操作功能模块
│ │ │ __init__.py
│ ├─download # 下载内容模块
│ │ a.txt
│ │ b.txt
│ │ c.txt
│ └─upload # 上传内容模块
│ a.txt
│ b.txt
└─server # 服务端程序入口
├─bin
│ run.py # 可执行程序入口目录
│ __init__.py
├─config # 配置文件目录
│ │ accounts.ini # 账号密码配置文件
│ │ settings.py # 配置文件
│ │ __init__.py
├─core # 主要逻辑程序目录
│ │ ftp_server.py # server端主程序模块
│ │ main.py # 主程序模块
│ │ user_handle.py # 用户注册登录模块
│ │ file_func.py # server端文件操作功能模块
│ │ auth_func.py # server端用户认证功能模块
│ │ md5_func.py # server端对文件加密操作功能模块
└─home # 家目录
│ __init__.py
├─curry # curry用户的家目录
│ │ a.txt
│ │ b.txt
│ │ c.txt
├─durant # durant用户的家目录
│ └─test3
│ └─test4
└─james # james用户的家目录
│ a.txt
│ b.txt
│ c.txt
│ test1
│ test2
└─test3

 


六,程序结构图

 技术图片

 


七,程序代码

1,server


1.1 bin

run.py


# _*_ coding: utf-8 _*_
import os
import sys


BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)


from core import ftp_server
from core import main
from config import settings

if __name__ == ‘__main__‘:
a = main.Manager()
a.interactive()

 


1.2config

settings.py


# _*_ coding: utf-8 _*_
import os
import sys
import socket
import logging

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)

ACCOUNTS_FILE = os.path.join(BASE_DIR,‘config‘,‘accounts.ini‘)

address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM

BIND_HOST = ‘127.0.0.1‘
BIND_PORT = 9999
ip_port = (BIND_HOST,BIND_PORT)
coding = ‘utf-8‘
listen_count = 5
max_recv_bytes = 8192
allow_reuser_address = False
# 允许配置最大并发数,这里设置为3
MAX_CONCURRENT_COUNT =3

 


1.3core

auth.py


# _*_ coding: utf-8 _*_
import pickle
import hashlib
import os
import struct

from config import settings
from core.user_handle import UserHandle
from core.file_func import File_func

class Auth_func():

def auth(self, conn):
‘‘‘
处理用户的认证请求
1,根据username读取accounts.ini文件,然后查看用户是否存在
2,将程序运行的目录从bin.user_auth修改到用户home/username方便之后查询
3,把客户端返回用户的详细信息
:return:
‘‘‘
while True:
user_dic = self.get_recv(conn)
username = user_dic[‘username‘]
password = user_dic[‘password‘]
md5_obj = hashlib.md5(password.encode(‘utf-8‘))
check_password = md5_obj.hexdigest()
user_handle = UserHandle(username)
# 判断用户是否存在 返回列表,
user_data = user_handle.judge_user()
if user_data:
if user_data[0][1] == check_password:
conn.send(struct.pack(‘i‘, 1)) # 登录成功返回 1
self.homedir_path = os.path.join(settings.BASE_DIR, ‘home‘, username)
# 将程序运行的目录名修改到 用户home目录下
os.chdir(self.homedir_path)
# 将用户配额的大小从M 改到字节
self.quota_bytes = int(user_data[2][1]) * 1024 * 1024
user_info_dic = {
‘username‘: username,
‘homedir‘: user_data[1][1],
‘quota‘: user_data[2][1]
}
# 用户的详细信息发送到客户端
conn.send(pickle.dumps(user_info_dic))
return True
else:
conn.send(struct.pack(‘i‘, 0)) # 登录失败返回 0
else:
conn.send(struct.pack(‘i‘, 0)) # 登录失败返回 0


def get_recv(self, conn):
‘‘‘从client端接收发来的数据‘‘‘
return pickle.loads(conn.recv(settings.max_recv_bytes))

def current_home_size(self):
"""得到当前用户目录的大小,字节/M"""
self.home_bytes_size = 0
File_func().recursion_file(self.homedir_path, self.home_bytes_size)
home_m_size = round(self.home_bytes_size / 1024 / 1024, 1)

 

file_func.py


# _*_ coding: utf-8 _*_
import os
import sys
import struct
import pickle

from config import settings
from core import ftp_server
from core import md5_func


class File_func(object):
def readfile(self,file_path):
‘‘‘读取文件,得到文件内容的bytes类型‘‘‘
with open(file_path, ‘rb‘) as f:
filedata = f.read()
return filedata

def send_filedata(self,file_path,conn,exist_file_size=0):
"""下载时,将文件打开,send(data)"""
with open(file_path, ‘rb‘) as f:
f.seek(exist_file_size)
while True:
data = f.read(1024)
if data:
conn.send(data)
else:
break

def write_file(self,conn,f,recv_size,file_size):
‘‘‘上传文件时,将文件内容写入到文件中‘‘‘
while recv_size res = conn.recv(settings.max_recv_bytes)
f.write(res)
recv_size += len(res)
conn.send(struct.pack(‘i‘, recv_size)) # 为了进度条的显示

def recursion_file(self, homedir_path,home_bytes_size):
"""递归查询用户目录下的所有文件,算出文件的大小"""
res = os.listdir(homedir_path)
for i in res:
path = os.path.join(homedir_path,i)
if os.path.isdir(path):
self.recursion_file(path,home_bytes_size)
elif os.path.isfile(path):
home_bytes_size += os.path.getsize(path)

def current_home_size(self,homedir_path):
"""得到当前用户目录的大小,字节/M"""
self.home_bytes_size =0
self.recursion_file(self.home_bytes_size,homedir_path)
home_m_size = round(self.home_bytes_size / 1024 / 1024, 1)

 

ftp_server.py


# _*_ coding: utf-8 _*_
import socket
import struct
import json
import os
import pickle
import subprocess
import hashlib
import queue
from threading import Thread
from threading import currentThread

from config import settings
from core.user_handle import UserHandle
from core.file_func import File_func
from core.md5_func import Md5_func
from core.auth_func import Auth_func
class FTPServer():

def __init__(self,server_address,bind_and_listen = True):
self.server_address = server_address
self.socket = socket.socket(settings.address_family,settings.socket_type)
self.q = queue.Queue(settings.MAX_CONCURRENT_COUNT)
if bind_and_listen:
try:
self.server_bind()
self.server_listen()
except Exception:
self.server_close()

def server_bind(self):
allow_reuse_address = False
if allow_reuse_address:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)

def server_listen(self):
self.socket.listen(settings.listen_count)

def server_close(self):
self.socket.close()

def server_accept(self):
return self.socket.accept()

def conn_close(self, conn):
conn.close()

def server_link(self):
print("33[31;1mwaiting client .....33[0m")
while True: # 链接循环
conn,self.client_addr = self.server_accept()
print(‘客户端地址:‘, self.client_addr)
# while True: # 通信循环
try:
t = Thread(target=self.server_handle,args=(conn,))
self.q.put(t)
t.start()
except Exception as e:
print(e)
self.conn_close(conn)
self.q.get()

def server_handle(self,conn):
‘‘‘处理与用户的交互指令‘‘‘
if Auth_func().auth(conn):
print("33[32;1m-------user authentication successfully-------33[0m")
try:
res = conn.recv(settings.max_recv_bytes)
if not res:
self.conn_close(conn)
self.q.get()
# 解析命令,提取相应的参数
self.cmds = res.decode(settings.coding).split()
if hasattr(self, self.cmds[0]):
func = getattr(self, self.cmds[0])
func(conn)
except Exception as e:
print(e)
self.conn_close(conn)
self.q.get()

def get(self, conn):
‘‘‘
下载,首先查看文件是否存在,然后上传文件的报头大小,上传文件,以读的方式发开文件
找到下载的文件
发送 header_size
发送 header_bytes file_size
读文件 rb 发送 send(line)
若文件不存在,发送0 client提示:文件不存在
:param cmds:
:return:
‘‘‘
if len(self.cmds) > 1:
filename = self.cmds[1]
self.file_path = os.path.join(os.getcwd(), filename)
if os.path.isfile(self.file_path):
file_size = os.path.getsize(self.file_path)
obj = conn.recv(4)
exist_file_size = struct.unpack(‘i‘, obj)[0]
header = {
‘filename‘: filename,
‘filemd5‘: Md5_func().getfile_md5(self.file_path),
‘file_size‘: file_size
}
header_bytes = pickle.dumps(header)
conn.send(struct.pack(‘i‘, len(header_bytes)))
conn.send(header_bytes)
if exist_file_size: # 表示之前被下载过 一部分
if exist_file_size != file_size:
File_func().send_filedata(self.file_path, exist_file_size)
else:
print(‘33[31;1mbreakpoint and file size are the same33[0m‘)
else: # 文件第一次下载
File_func().send_filedata(self.file_path, conn)
else:
print(‘33[31;1merror33[0m‘)
conn.send(struct.pack(‘i‘, 0))

else:
print("33[31;1muser does not enter file name33[0m")

def put(self, conn):
"""从client上传文件到server当前工作目录下
"""
if len(self.cmds) > 1:
obj = conn.recv(4)
state_size = struct.unpack(‘i‘, obj)[0]
if state_size:
# 算出了home下已被占用的大小self.home_bytes_size
self.current_home_size()
header_bytes = conn.recv(struct.unpack(‘i‘, conn.recv(4))[0])
header_dic = pickle.loads(header_bytes)
filename = header_dic.get(‘filename‘)
file_size = header_dic.get(‘file_size‘)
file_md5 = header_dic.get(‘file_md5‘)
self.file_path = os.path.join(os.getcwd(), filename)
if os.path.exists(self.file_path):
conn.send(struct.pack(‘i‘, 1))
has_size = os.path.getsize(self.file_path)
if has_size == file_size:
print("33[31;1mfile already does exist!33[0m")
conn.send(struct.pack(‘i‘, 0))
else:
print(‘33[31;1mLast time file not finished,this time continue33[0m‘)
conn.send(struct.pack(‘i‘, 1))
if self.home_bytes_size + int(file_size - has_size) > self.quota_bytes:
print(‘33[31;1mSorry exceeding user quotas33[0m‘)
conn.send(struct.pack(‘i‘, 0))
else:
conn.send(struct.pack(‘i‘, 1))
conn.send(struct.pack(‘i‘, has_size))
with open(self.file_path, ‘ab‘) as f:
f.seek(has_size)
File_func().write_file(conn, f, has_size, file_size)
Md5_func().verification_filemd5(self.file_path, conn, file_md5)

else:
conn.send(struct.pack(‘i‘, 0))
print(‘33[31;1mfile does not exist, now first put33[0m‘)
if self.home_bytes_size + int(file_size) > self.quota_bytes:
print(‘33[31;1mSorry exceeding user quotas33[0m‘)
conn.send(struct.pack(‘i‘, 0))
else:
conn.send(struct.pack(‘i‘, 1))
with open(self.file_path, ‘wb‘) as f:
recv_size = 0
File_func().write_file(conn, f, recv_size, file_size)
Md5_func().verification_filemd5(self.file_path,conn, file_md5)


else:
print("33[31;1mfile does not exist!33[0m")
else:
print("33[31;1muser does not enter file name33[0m")

def ls(self, conn):
‘‘‘查看当前工作目录下,先返回文件列表的大小,在返回查询的结果‘‘‘
print("33[34;1mview current working directory33[0m")
subpro_obj = subprocess.Popen(‘dir‘, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = subpro_obj.stdout.read()
stderr = subpro_obj.stderr.read()
conn.send(struct.pack(‘i‘, len(stdout + stderr)))
conn.send(stdout)
conn.send(stderr)
print(‘33[31;1mCongratulations view directory success33[0m‘)

def mkdir(self, conn):
‘‘‘增加目录
在当前目录下,增加目录
1.查看目录名是否已经存在
2.增加目录成功,返回 1
2.增加目录失败,返回 0‘‘‘
print("33[34;1madd working directory33[0m")
if len(self.cmds) > 1:
mkdir_path = os.path.join(os.getcwd(), self.cmds[1])
if not os.path.exists(mkdir_path):
os.mkdir(mkdir_path)
print(‘33[31;1mCongratulations add directory success33[0m‘)
conn.send(struct.pack(‘i‘, 1))
else:
print("33[31;1muser directory already does exist33[0m")
conn.send(struct.pack(‘i‘, 0))
else:
print("33[31;1muser does not enter file name33[0m")

def cd(self, conn):
‘‘‘切换目录
1.查看是否是目录名
2.拿到当前目录,拿到目标目录,
3.判断homedir是否在目标目录内,防止用户越过自己的home目录 eg: ../../....
4.切换成功,返回 1
5.切换失败,返回 0‘‘‘
print("33[34;1mSwitch working directory33[0m")
if len(self.cmds) > 1:
dir_path = os.path.join(os.getcwd(), self.cmds[1])
if os.path.isdir(dir_path):
# os.getcwd 获取当前工作目录
previous_path = os.getcwd()
# os.chdir改变当前脚本目录
os.chdir(dir_path)
target_dir = os.getcwd()
if self.homedir_path in target_dir:
print(‘33[31;1mCongratulations switch directory success33[0m‘)
conn.send(struct.pack(‘i‘, 1))
else:
print(‘33[31;1mSorry switch directory failed33[0m‘)
# 切换失败后,返回到之前的目录下
os.chdir(previous_path)
conn.send(struct.pack(‘i‘, 0))
else:
print(‘33[31;1mSorry switch directory failed,the directory is not current directory33[0m‘)
conn.send(struct.pack(‘i‘, 0))
else:
print("33[31;1muser does not enter file name33[0m")

def remove(self, conn):
"""删除指定的文件,或者空文件夹
1.删除成功,返回 1
2.删除失败,返回 0
"""
print("33[34;1mRemove working directory33[0m")
if len(self.cmds) > 1:
file_name = self.cmds[1]
file_path = os.path.join(os.getcwd(), file_name)
if os.path.isfile(file_path):
os.remove(file_path)
conn.send(struct.pack(‘i‘, 1))
elif os.path.isdir(file_path): # 删除空目录
if not len(os.listdir(file_path)):
os.removedirs(file_path)
print(‘33[31;1mCongratulations remove success33[0m‘)
conn.send(struct.pack(‘i‘, 1))
else:
print(‘33[31;1mSorry remove directory failed33[0m‘)
conn.send(struct.pack(‘i‘, 0))
else:
print(‘33[31;1mSorry remove directory failed33[0m‘)
conn.send(struct.pack(‘i‘, 0))
else:
print("33[31;1muser does not enter file name33[0m")

 

main.py


# _*_ coding: utf-8 _*_
from core.user_handle import UserHandle
from core.ftp_server import FTPServer
from config import settings


class Manager():
‘‘‘
主程序,包括启动server,创建用户,退出
:return:
‘‘‘
def __init__(self):
pass

def start_ftp(self):
‘‘‘启动server端‘‘‘
server = FTPServer(settings.ip_port)
server.server_link()
server.close()

def create_user(self):
‘‘‘创建用户,执行创建用户的类‘‘‘
username = input("33[32;1mplease input your username>>>33[0m").strip()
UserHandle(username).add_user()

def logout(self):
‘‘‘
退出登陆
:return:
‘‘‘
print("33[32;1m-------Looking forward to your next login-------33[0m")
exit()

def interactive(self):
‘‘‘交互函数‘‘‘
msg = ‘‘‘33[32;1m
1 启动ftp服务端
2 创建用户
3 退出
33[0m‘‘‘
menu_dic = {
"1": ‘start_ftp‘,
"2": ‘create_user‘,
"3": ‘logout‘,
}
exit_flag = False
while not exit_flag:
print(msg)
user_choice = input("Please input a command>>>").strip()
if user_choice in menu_dic:
getattr(self,menu_dic[user_choice])()
else:
print("33[31;1myou choice doesn‘t exist33[0m")

 

md5_func.py


# _*_ coding: utf-8 _*_
import hashlib
import struct

from core import ftp_server
from core import file_func

class Md5_func(object):

def getfile_md5(self,file_path):
‘‘‘获取文件的md5‘‘‘
md5 = hashlib.md5(file_func.File_func().readfile(file_path))
print("md5是:
",md5.hexdigest())
return md5.hexdigest()

def handle_data(self):
‘‘‘处理接收到的数据,主要是将密码转化为md5的形式‘‘‘
user_dic = ftp_server.FTPServer().get_recv()
username = user_dic[‘username‘]
password = user_dic[‘password‘]
md5_obj = hashlib.md5()
md5_obj.update(password)
check_password = md5_obj.hexdigest()

def verification_filemd5(self,file_path,conn,filemd5):
# 判断文件内容的md5
if self.getfile_md5(file_path) == filemd5:
print(‘33[31;1mCongratulations download success33[0m‘)
conn.send(struct.pack(‘i‘, 1))
else:
print(‘33[31;1mSorry download failed33[0m‘)
conn.send(struct.pack(‘i‘, 0))

 

user_handle.py


#_*_coding:utf-8_*_
import configparser
import hashlib
import os

from config import settings

class UserHandle():
‘‘‘
创建用户名称,密码
如果用户存在,则返回,如果用户不存在,则注册成功
‘‘‘
def __init__(self,username):
self.username = username
self.cOnfig= configparser.ConfigParser()
self.config.read(settings.ACCOUNTS_FILE)

def password(self):
‘‘‘生成用户的密码,然后加密‘‘‘
password_inp = input("33[32;1mplease input your password>>>33[0m").strip()
md5_obj = hashlib.md5()
md5_obj.update(password_inp.encode(‘utf-8‘))
md5_password = md5_obj.hexdigest()
return md5_password

def disk_quota(self):
‘‘‘生成每个用户的磁盘配额‘‘‘
quota = input(‘33[32;1mplease input Disk quotas>>>:33[0m‘).strip()
if quota.isdigit():
return quota
else:
exit(‘33[31;1mdisk quotas must be integer33[0m‘)

def add_user(self):
‘‘‘创建用户,存到accounts.ini‘‘‘
if not self.config.has_section(self.username):
print(‘33[31;1mcreating username is :%s 33[0m‘ % 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.disk_quota())
self.write_config()
self.create_userhome()
print(‘33[1;32msuccessfully create userdata33[0m‘)
else:
print(‘33[1;31musername already existing33[0m‘)

def create_userhome(self):
‘‘‘创建用户的home文件夹‘‘‘
os.mkdir(os.path.join(settings.BASE_DIR, ‘home‘, self.username))

def write_config(self):
‘‘‘写入文档‘‘‘
with open(settings.ACCOUNTS_FILE,‘w‘) as f:
self.config.write(f)

def judge_user(self):
‘‘‘判断用户是否存在‘‘‘
if self.config.has_section(self.username):
return self.config.items(self.username)

 


2,client


2.1bin

run.py


# _*_ coding: utf-8 _*_
import os
import sys


BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)


from core import ftp_client
from config import settings
if __name__ == ‘__main__‘:
run = ftp_client.FTPClient(settings.ip_port)
run.execute()

 


2.2config

settings.py


# _*_ coding: utf-8 _*_
import os
import sys
import socket

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
# 下载的文件存放路径
down_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ‘download‘)
# 上传的文件存放路径
upload_filepath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ‘upload‘)
#绑定的IP地址
BIND_HOST = ‘127.0.0.1‘
#绑定的端口号
BIND_PORT = 9999
ip_port = (BIND_HOST,BIND_PORT)
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM

coding = ‘utf-8‘
listen_count = 5
max_recv_bytes = 8192
allow_reuser_address = False

 


2.3core

file_func.py


# _*_ coding: utf-8 _*_
import os
import sys
import struct
import pickle
from config import settings
from core import ftp_client
from core import md5_func
from core import progress_bar_func

class File_func(object):

def readfile(self,file_path):
‘‘‘读取文件‘‘‘
with open(file_path,‘rb‘) as f:
filedata = f.read()
return filedata

def appendfile_content(self,socket,file_path,temp_file_size,file_size):
‘‘‘追加文件内容‘‘‘
with open(file_path,‘ab‘) as f:
f.seek(temp_file_size)
get_size = temp_file_size
while get_size res = socket.recv(settings.max_recv_bytes)
f.write(res)
get_size += len(res)
progress_bar_func.progress_bar(1,get_size,file_size) #1表示下载

def write_file(self,socket,f,get_size,file_size):
‘‘‘下载文件,将内容写入文件中‘‘‘
while get_size res = socket.recv(settings.max_recv_bytes)
f.write(res)
get_size += len(res)
progress_bar_func.progress_bar(1,get_size,file_size) #1表示下载

def recv_file_header(self,socket,header_size):
"""接收文件的header, filename file_size file_md5"""
header_types = socket.recv(header_size)
header_dic = pickle.loads(header_types)
print(header_dic, type(header_dic))
total_size = header_dic[‘file_size‘]
filename = header_dic[‘filename‘]
filemd5 = header_dic[‘filemd5‘]
return (filename,total_size,filemd5)

def open_sendfile(self,file_size,file_path,socket,recv_size =0):
‘‘‘打开要上传的文件(由于本程序上传文件的原理是先读取本地文件,再写到上传地址的文件)‘‘‘
with open(file_path, ‘rb‘) as f:
f.seek(recv_size)
while True:
data = f.read(1024)
if data:
socket.send(data)
obj = socket.recv(4)
recv_size = struct.unpack(‘i‘, obj)[0]
progress_bar_func.progress_bar(2, recv_size, file_size)
else:
break
success_state = struct.unpack(‘i‘, socket.recv(4))[0]
if success_state:
print(‘33[31;1mCongratulations upload success33[0m‘)
else:
print(‘33[31;1mSorry upload directory failed33[0m‘)

 

ftp_client.py


# _*_ coding: utf-8 _*_
import socket
import struct
import json
import os
import sys
import pickle
import hashlib

from config import settings
from core.md5_func import Md5_func
from core.file_func import File_func
from core import progress_bar_func
class FTPClient:

def __init__(self,server_address,cOnnect= True):
self.server_address = server_address
self.socket = socket.socket(settings.address_family,settings.socket_type)
if connect:
try:
self.client_connect()
except Exception:
self.client_close()

def client_connect(self):
try:
self.socket.connect(self.server_address)
except Exception as e:
print("33[31;1merror:%s33[0m"%e)
exit("33[31;1m
The server is not activated 33[0m")

def client_close(self):
self.socket.close()

def get(self,cmds):
"""从server下载文件到client
"""
if len(cmds) >1:
filename = cmds[1]
self.file_path = os.path.join(settings.down_filepath, filename)
if os.path.isfile(self.file_path): #如果文件存在,支持断电续传
temp_file_size = os.path.getsize(self.file_path)
self.socket.send(struct.pack(‘i‘,temp_file_size))
header_size = struct.unpack(‘i‘,self.socket.recv(4))[0]
if header_size:
filename,file_size,filemd5 = File_func().recv_file_header(self.socket,header_size)
if temp_file_size == file_size:
print(‘33[34;1mFile already does exist33[0m‘)
else:
print(‘33[34;1mFile now is breakpoint continuation33[0m‘)
File_func().appendfile_content(self.socket,self.file_path,temp_file_size,file_size)
Md5_func().verification_filemd5(self.file_path,filemd5)
else:
print("33[34;1mFile was downloaded before,but now server‘s file is not exist33[0m")
else:#如果文件不存在,则是直接下载
self.socket.send(struct.pack(‘i‘,0))
obj = self.socket.recv(1024)
header_size = struct.unpack(‘i‘, obj)[0]
if header_size==0:
print("33[31;1mfile does not exist!33[0m")
else:
filename, file_size, filemd5 = File_func().recv_file_header(self.socket,header_size)
download_filepath = os.path.join(settings.down_filepath, filename)
with open(download_filepath, ‘wb‘) as f:
get_size = 0
File_func().write_file(self.socket,f, get_size, file_size)
Md5_func().verification_filemd5(self.file_path,filemd5)
else:
print("33[31;1muser does not enter file name33[0m")

def ls(self,cmds):
‘‘‘查看当前工作目录,文件列表‘‘‘
print("33[34;1mview current working directory33[0m")
obj = self.socket.recv(4)
dir_size = struct.unpack(‘i‘,obj)[0]
recv_size = 0
recv_bytes = b‘‘
while recv_size temp_bytes = self.socket.recv(settings.max_recv_bytes)
recv_bytes +=temp_bytes
recv_size += len(temp_bytes)
print(recv_bytes.decode(‘gbk‘))
print(‘33[31;1mCongratulations view directory success33[0m‘)

def mkdir(self,cmds):
‘‘‘增加目录
1,server返回1 增加成功
2,server返回2 增加失败‘‘‘
print("33[34;1madd working directory33[0m")
obj = self.socket.recv(4)
res = struct.unpack(‘i‘,obj)[0]
if res:
print(‘33[31;1mCongratulations add directory success33[0m‘)
else:
print(‘33[31;1mSorry add directory failed33[0m‘)

def cd(self,cmds):
‘‘‘切换目录‘‘‘
print("33[34;1mSwitch working directory33[0m")
if len(cmds) >1:
obj = self.socket.recv(4)
res = struct.unpack(‘i‘, obj)[0]
if res:
print(‘33[31;1mCongratulations switch directory success33[0m‘)
else:
print(‘33[31;1mSorry switch directory failed33[0m‘)
else:
print("33[31;1muser does not enter file name33[0m")

def remove(self,cmds):
‘‘‘表示删除文件或空文件夹‘‘‘
print("33[34;1mRemove working directory33[0m")
obj = self.socket.recv(4)
res = struct.unpack(‘i‘, obj)[0]
if res:
print(‘33[31;1mCongratulations remove success33[0m‘)
else:
print(‘33[31;1mSorry remove directory failed33[0m‘)

def put_situation(self,file_size,cOndition=0):
‘‘‘上传的时候有两种情况,文件已经存在,文件不存在‘‘‘
quota_state= struct.unpack(‘i‘, self.socket.recv(4))[0]
if quota_state:
if condition:
obj = self.socket.recv(4)
recv_size = struct.unpack(‘i‘, obj)[0]
File_func().open_sendfile(file_size, self.file_path, self.socket, recv_size=0)
else:
File_func().open_sendfile(file_size, self.file_path, self.socket, recv_size=0)
else:
print(‘33[31;1mSorry exceeding user quotas33[0m‘)

def put(self,cmds):
"""往server端登录的用户目录下上传文件
"""
if len(cmds) > 1:
filename = cmds[1]
file_path = os.path.join(settings.upload_filepath, filename)
if os.path.isfile(file_path): # 如果文件存在,支持断电续传
self.socket.send(struct.pack(‘i‘, 1))
self.file_path = file_path
file_size = os.path.getsize(self.file_path)
header_dic = {
‘filename‘: os.path.basename(filename),
‘file_md5‘: Md5_func().getfile_md5(self.file_path),
‘file_size‘: file_size
}
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(4))[0]
if state: #已经存在
has_state = struct.unpack(‘i‘, self.socket.recv(4))[0]
if has_state:
self.put_situation(file_size, 1)
else: # 存在的大小 和文件大小一致 不必再传
print("33[31;1mfile already does exist!33[0m")
else: # 第一次传
self.put_situation(file_size)
else: # 文件不存在
print("33[31;1mfile does not exist!33[0m")
self.socket.send(struct.pack(‘i‘, 0))
else:
print("33[31;1muser does not enter file name33[0m")

def get_recv(self):
‘‘‘从client端接受发来的数据‘‘‘
return pickle.loads(self.socket.recv(settings.max_recv_bytes))

def login(self):
‘‘‘
登陆函数,当登陆失败超过三次,则退出
用户密码发送到server短
接受server端返回的信息,如果成功返回1,失败返回0
:return: 如果用户账号密码正确,则返回用户数据的字典
‘‘‘
retry_count = 0
while retry_count <3:
username = input(‘33[34;1mplease input Username:33[0m‘).strip()
if not username:
continue
password = input(‘33[34;1mplease input Password:33[0m‘).strip()
user_dic = {
‘username‘:username,
‘password‘:password
}
#将用户信息发送到客户端,然后接受客户端的数据
data = pickle.dumps(user_dic)
self.socket.send(pickle.dumps(user_dic))
#为了防止出现黏包问题,所以先解压报头,读取报头,再读数据
obj = self.socket.recv(4)
res = struct.unpack(‘i‘,obj)[0]
#此处,如果返回的是代码4001,则成功 4002则失败
if res:
print("33[32;1m-----------------welcome to ftp client-------------------33[0m")
user_info_dic = self.get_recv()
recv_username = user_info_dic[‘username‘]
return True
else:
print("33[31;1mAccount or Passwordoes not correct!33[0m")
retry_count +=1

def execute(self):
‘‘‘
执行,或者实施
:return:
‘‘‘
if self.login():
while True:
try:
self.help_info()
inp = input("Please input a command>>>").strip()
if not inp:
continue
self.socket.send(inp.encode(settings.coding))
cmds = inp.split()
if hasattr(self, cmds[0]):
func = getattr(self, cmds[0])
func(cmds)
break
else:
print(‘33[31;1mNo such command ,please try again33[0m‘)
except Exception as e: # server关闭了
print(‘33[31;1m%s33[0m‘%e)
break

def help_info(self):
print (‘‘‘33[34;1m
get + (文件名) 表示下载文件
put + (文件名) 表示上传文件
ls 表示查询当前目录下的文件列表(只能访问自己的文件列表)
mkdir + (文件名) 表示创建文件夹
cd + (文件名) 表示切换目录(只能在自己的文件列表中切换)
remove + (文件名) 表示删除文件或空文件夹
33[0m‘‘‘)

 

md5_func.py


# _*_ coding: utf-8 _*_
import hashlib
from core import ftp_client
from core.file_func import File_func

class Md5_func(object):
def getfile_md5(self,file_path):
‘‘‘对文件内容进行加密,也就是保持文件的一致性‘‘‘
md5 = hashlib.md5(File_func().readfile(file_path))
print("md5是:
",md5.hexdigest())
return md5.hexdigest()

def verification_filemd5(self,file_path,filemd5):
# 判断下载下来的文件MD5值和server传过来的MD5值是否一致
if self.getfile_md5(file_path) == filemd5:
print(‘33[31;1mCongratulations download success33[0m‘)
else:
print(‘33[31;1mSorry download failed,download again support breakpoint continuation33[0m‘)

 

progress_bar_fun.py


# _*_ coding: utf-8 _*_
‘‘‘进度条的表示功能‘‘‘
import sys

def progress_bar(num, get_size, file_size):
float_rate = float(get_size) / float(file_size)
rate_num = round(float_rate * 100, 2)
if num == 1: # 1表示下载
sys.stdout.write(‘33[31;1m
finish downloaded perentage:{0}%33[0m‘.format(rate_num))
elif num == 2: # 2表示上传
sys.stdout.write(‘33[31;1m
finish uploaded perentage:{0}%33[0m‘.format(rate_num))
sys.stdout.flush()


推荐阅读
author-avatar
icanfly2502872173_635
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有