要求:
1
.用戶加密認(rèn)證
2
.允許同時(shí)多用戶登錄
3
.每個(gè)用戶有自己的家目錄,且只能訪問(wèn)自己的家目錄
4
.對(duì)用戶進(jìn)行磁盤配額,每個(gè)用戶的可用空間不同
5
.允許用戶在ftp server上隨意切換目錄
6
.允許用戶查看當(dāng)前目錄下的文件
7
.允許上傳和下載文件,并保證文件的一致性md5
8
.文件傳輸過(guò)程中顯示進(jìn)度條
9.支持文件的斷點(diǎn)續(xù)傳
使用:
1.啟動(dòng)ftp_server.py
2.創(chuàng)建用戶,輸入:用戶名(默認(rèn)密碼是zhurui)
3.啟動(dòng)FTP服務(wù)器
4.啟動(dòng)客戶端ftp_client.py
5.輸入用戶名和密碼:alex zhurui | william zhurui
6.與服務(wù)器server交互:
?
server服務(wù)端
bin下的文件?
ftp_server.py
#
_*_ coding:utf-8 _*_
#
Author :simon
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(這個(gè)可以在執(zhí)行中創(chuàng)建)
[william]
password
=
39da56d2e7a994d38b9aaf329640fc6e
homedir
= home/
william
quota
= 10
[zhurui]
password
=
39da56d2e7a994d38b9aaf329640fc6e
homedir
= home/
zhurui
quota
= 10
[simon]
password
=
39da56d2e7a994d38b9aaf329640fc6e
homedir
= home/
simon
quota
= 10
settings.py
#
_*_ coding:utf-8 _*_
#
Author:Simon
#
Datetime:2019/8/14 11:00
#
Software:PyCharm
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
= 8080
MAX_CONCURRENT_COUNT
= 10
core下的文件
main.py
#
_*_ coding:utf-8 _*_
#
Author:Simon
from
core.user_handle
import
UserHandle
from
core.server
import
Ftpserver
class
Manager():
def
__init__
(self):
pass
def
start_ftp(self):
'''
啟動(dòng)ftp_server端
'''
server
=
Ftpserver()
server.run()
server.close()
def
create_user(self):
'''
創(chuàng)建用戶
'''
username
= input(
'
請(qǐng)輸入要?jiǎng)?chuàng)建的用戶>:
'
).strip()
UserHandle(username).add_user()
def
quit_func(self):
quit(
'
get out...
'
)
def
run(self):
msg
=
'''
\033[31;0m
1、啟動(dòng)ftp服務(wù)器
2、創(chuàng)建用戶
3、退出\033[0m\n
'''
msg_dic
= {
'
1
'
:
'
start_ftp
'
,
'
2
'
:
'
create_user
'
,
'
3
'
:
'
quit_func
'
}
while
True:
print
(msg)
num
= input(
'
請(qǐng)輸入數(shù)字num>>>>:
'
).strip()
if
num
in
msg_dic:
getattr(self,msg_dic[num])()
else
:
print
(
'
\033[1;31m請(qǐng)重新選擇\033[0m
'
)
server.py
#
-*- coding:utf-8 -*-
#
Author:Simon
#
Datetime:2019/8/13 21:02
#
Software:PyCharm
import
os
import
socket
import
struct
import
pickle
import
hashlib
import
subprocess
import
queue
from
conf
import
settings
#
from core.user_handle import UserHandle
from
core.user_handle
import
UserHandle
from
threading
import
Thread, Lock
class
Ftpserver():
MAX_SOCKET_LISTEN
= 5
MAX_RECV_SIZE
= 8192
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)
self.q
= queue.Queue(settings.MAX_CONCURRENT_COUNT)
#
可以配置最大并發(fā)數(shù)
def
server_accept(self):
'''
等待client連接
'''
print
(
'
starting...
'
)
while
True:
self.conn,self.client_addr
=
self.socket.accept()
print
(
'
客戶端地址:
'
, self.client_addr)
#
pool.submit(self.get_recv, self.conn)
#
self.server_accept.close()
try
:
#
t = Thread(target=self.server_handle, args=(self.conn, )) #報(bào)這個(gè)錯(cuò)(TypeError: server_handle() takes 1 positional argument but 2 were given)
t = Thread(target=self.server_handle(), args=
(self.conn, ))
self.q.put(t)
t.start()
except
Exception as e:
print
(e)
self.conn.close()
self.q.get()
def
get_recv(self):
'''
接收client發(fā)來(lái)的數(shù)據(jù)
'''
return
pickle.loads(self.conn.recv(self.MAX_RECV_SIZE))
def
auth(self):
'''
處理用戶的認(rèn)證請(qǐng)求
1、根據(jù)username讀取accounts.ini文件,password相比,判斷用戶是否存在
2、將程序運(yùn)行的目錄從bin/ftp_server.py修改到用戶home/alice,方便之后查詢ls
3、給client返回用戶的詳細(xì)信息
'''
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','100')]
if
user_data:
if
user_data[0][1] == hashlib.md5(user_dic.get(
'
password
'
).encode(
'
utf-8
'
)).hexdigest():
#
密碼也相同
self.conn.send(struct.pack(
'
i
'
, 1))
#
登錄成功返回
self.username =
username
self.homedir_path
=
'
%s %s %s
'
%(settings.BASE_DIR,
'
home
'
, self.username)
os.chdir(self.homedir_path)
#
將程序運(yùn)行的目錄名修改到用戶home目錄下
self.quota_bytes = int(user_data[2][1]) * 1024 * 1024
#
將用戶配額大小從M改到字節(jié)
user_info_dic =
{
'
username
'
: username,
'
homedir
'
: user_data[1][1
],
'
quota
'
: user_data[2][1
]
}
self.conn.send(pickle.dumps(user_info_dic))
#
用戶的詳細(xì)信息發(fā)送到客戶端
return
True
else
:
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
self.conn.send(struct.pack(
'
i
'
, 0))
def
readfile(self):
'''
讀取文件,得到文件內(nèi)容的bytes型
'''
with open(self.filepath,
'
rb
'
) as f:
filedata
=
f.read()
return
filedata
def
getfile_md5(self):
'''
對(duì)文件內(nèi)容md5
'''
return
hashlib.md5(self.readfile()).hexdigest()
def
get(self):
'''
從server下載文件到client
'''
if
len(self.cmds) > 1
:
filename
= self.cmds[1
]
filepath
= os.path.join(os.getcwd(),filename)
#
os.getcwd()得到當(dāng)前工作目錄
if
os.path.isfile(filepath):
#
判斷文件是否存在
exist_file_size = struct.unpack(
'
i
'
, self.conn.recv(4
))[0]
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:
#
表示之前被下載過(guò)一部分
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
(
'
當(dāng)前目錄下文件不存在
'
)
self.conn.send(struct.pack(
'
i
'
,0))
else
:
print
(
'
用戶沒(méi)用輸入文件名
'
)
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):
#
判斷指定對(duì)象是否為目錄
self.recursion_file(path)
elif
os.path.isfile(path):
self.home_bytes_size
+=
os.path.getsize(path)
def
current_home_size(self):
'''
得到當(dāng)前用戶home/alice目錄的大小,字節(jié)/M
'''
self.home_bytes_size
=
0
self.recursion_file(self.homedir_path)
print
(
'
字節(jié):
'
, self.home_bytes_size)
#
單位是字節(jié)
home_m_size = round(self.home_bytes_size / 1024 /1024, 1
)
print
(
'
單位M:
'
, home_m_size)
#
單位是: M
def
put(self):
'''
從client上傳文件到server當(dāng)前工作目錄下
'''
if
len(self.cmds) > 1
:
state_size
= struct.unpack(
'
i
'
,self.conn.recv(4
))[0]
if
state_size:
self.current_home_size()
#
算出了home下已被占用的大小self.home_bytes_size
header_bytes = self.conn.recv(struct.unpack(
'
i
'
, self.conn.recv(4
))[0])
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時(shí)方便
if
os.path.exists(upload_filepath):
#
文件已經(jīng)存在
self.conn.send(struct.pack(
'
i
'
, 1
))
has_size
=
os.path.getsize(upload_filepath)
if
has_size ==
file_size:
print
(
'
文件已經(jīng)存在
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
#
上次沒(méi)有傳完,接著繼續(xù)傳
self.conn.send(struct.pack(
'
i
'
, 1
))
if
self.home_bytes_size + int(file_size - has_size) >
self.quota_bytes:
print
(
'
超出了用戶的配額
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
self.conn.send(struct.pack(
'
i
'
,1
))
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))
#
為了顯示進(jìn)度條
if
self.getfile_md5() == file_md5:
#
判斷下載下來(lái)的文件MD5值和server傳過(guò)來(lái)的MD5值是否一致
print
(
'
\033[1;32m上傳成功\033[0m
'
)
self.conn.send(struct.pack(
'
i
'
, 1
))
else
:
print
(
'
\033[1;32m上傳失敗\033[0m
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
#
第一次上傳
self.conn.send(struct.pack(
'
i
'
, 0))
if
self.home_bytes_size + int(file_size) >
self.quota_bytes:
print
(
'
\033[1;32m超出了用戶的配額\033[0m
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
self.conn.send(struct.pack(
'
i
'
, 1
))
with open(upload_filepath,
'
wb
'
) as f:
recv_size
=
0
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))
#
為了進(jìn)度條的顯示
if
self.getfile_md5() == file_md5:
#
判斷下載下來(lái)的文件MD5值和server傳過(guò)來(lái)的MD5值是否一致
print
(
'
\033[1;32m上傳成功\033[0m
'
)
self.conn.send(struct.pack(
'
i
'
, 1
))
else
:
print
(
'
\033[1;32m上傳失敗\033[0m
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
print
(
'
待傳的文件不存在
'
)
else
:
print
(
'
用戶沒(méi)有輸入文件名
'
)
def
ls(self):
'''
查詢當(dāng)前工作目錄下,先返回文件列表的大小,再返回查詢的結(jié)果
'''
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):
'''
在當(dāng)前目錄下,增加目錄
'''
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
(
'
增加目錄成功
'
)
self.conn.send(struct.pack(
'
i
'
, 1))
#
增加目錄成功,返回1
else
:
print
(
'
目錄名已存在
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
#
失敗返回0
else
:
print
(
'
用戶沒(méi)有輸入目錄名
'
)
def
cd(self):
'''
切換目錄
'''
if
len(self.cmds) > 1
:
dir_path
= os.path.join(os.getcwd(), self.cmds[1
])
if
os.path.isdir(dir_path) :
#
查看是否是目錄名
previous_path = os.getcwd()
#
拿到當(dāng)前工作的目錄
os.chdir(dir_path)
#
改變工作目錄到 . . .
target_dir =
os.getcwd()
if
self.homedir_path
in
target_dir:
#
判斷homedir_path是否在目標(biāo)目錄
print
(
'
切換成功
'
)
self.conn.send(struct.pack(
'
i
'
, 1))
#
切換成功返回1
else
:
print
(
'
切換失敗
'
)
#
切換失敗后,返回到之前的目錄下
os.chdir(previous_path)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
print
(
'
要切換的目錄不在該目錄下
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
print
(
'
沒(méi)有傳入切換的目錄名
'
)
def
remove(self):
'''
刪除指定的文件,或者空文件夾
'''
if
len(self.cmds) > 1
:
file_name
= self.cmds[1
]
file_path
=
'
%s\%s
'
%
(os.getcwd(), file_name)
if
os.path.isfile(file_path):
os.remove(file_path)
self.conn.send(struct.pack(
'
i
'
, 1
))
elif
os.path.isdir(file_path):
#
刪除空目錄
if
not
len(os.listdir(file_path)):
os.removedirs(file_path)
print
(
'
刪除成功
'
)
self.conn.send(struct.pack(
'
i
'
, 1
))
else
:
print
(
'
文件夾非空,不能刪除
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
print
(
'
不是文件也不是文件夾
'
)
self.conn.send(struct.pack(
'
i
'
, 0))
else
:
print
(
'
沒(méi)有輸入要?jiǎng)h除的文件
'
)
def
server_handle(self):
'''
處理與用戶的交互指令
'''
if
self.auth():
print
(
'
\033[1;32m用戶登陸成功\033[0m
'
)
while
True:
try
:
#
try ...except 適合windows client斷開(kāi)
user_input = self.conn.recv(self.MAX_RECV_SIZE).decode(
'
utf-8
'
)
#
if not user_input: continue #這里適合 linux client斷開(kāi)
self.cmds =
user_input.split()
if
hasattr(self,self.cmds[0]):
getattr(self,self.cmds[0])()
else
:
print
(
'
\033[1;31請(qǐng)用戶重復(fù)輸入\033[0m
'
)
except
Exception:
break
def
run(self):
self.server_accept()
def
close(self):
self.socket.close()
#
if __name__ == '__main__':
#
pool = ThreadPoolExecutor(10)
user_handle.py
#
_*_ coding:utf-8 _*_
#
Author:Simon
#
Datetime:2019/8/14 10:26
#
Software:PyCharm
import
configparser
import
hashlib
import
os
from
conf
import
settings
class
UserHandle():
def
__init__
(self,username):
self.username
=
username
self.config
= configparser.ConfigParser()
#
先生成一個(gè)對(duì)象
self.config.read(settings.ACCOUNTS_FILE)
@property
def
password(self):
'''
生成用戶的默認(rèn)密碼 zhurui
'''
return
hashlib.md5(
'
zhurui
'
.encode(
'
utf-8
'
)).hexdigest()
@property
def
quota(self):
'''
生成每個(gè)用戶的磁盤配額
'''
quota
= input(
'
請(qǐng)輸入用戶的磁盤配額大小>>>:
'
).strip()
if
quota.isdigit():
return
quota
else
:
exit(
'
\033[1;31m磁盤配額必須是整數(shù)\033[0m
'
)
def
add_user(self):
'''
創(chuàng)建用戶,存到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))
#
創(chuàng)建用戶的home文件夾
print
(
'
\033[1;32m創(chuàng)建用戶成功\033[0m
'
)
else
:
print
(
'
\033[1;32m用戶已存在\033[0m
'
)
def
judge_user(self):
'''
判斷用戶是否存在
'''
if
self.config.has_section(self.username):
return
self.config.items(self.username)
else
:
return
client客戶端
download文件是儲(chǔ)存下載的文件;upload是上傳文件的儲(chǔ)存庫(kù)(download里邊可以不放東西,等待下載即可;upload里邊放你準(zhǔn)備上傳給服務(wù)端的文件)
ftp_client.py
#
_*_ coding:utf-8 _*_
#
Author:Simon
#
Datetime:2019/8/14 11:12
#
Software:PyCharm
import
os
import
sys
import
socket
import
struct
import
pickle
import
hashlib
class
Ftpclient():
HOST
=
'
127.0.0.1
'
#
服務(wù)器IP
PORT = 8080
#
服務(wù)端的端口
MAX_RECV_SIZE = 8192
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):
'''
連接服務(wù)端server
'''
try
:
self.socket.connect((self.HOST, self.PORT))
except
Exception:
exit(
'
\033[1;31mserver還未啟動(dòng)\033[0m
'
)
def
get_recv(self):
'''
獲取server返回的數(shù)據(jù)
'''
return
pickle.loads(self.socket.recv(self.MAX_RECV_SIZE))
def
auth(self):
'''
用戶認(rèn)證
'''
count
=
0
while
count < 3
:
name
= input(
'
請(qǐng)輸入用戶名>>:
'
).strip()
if
not
name:
continue
password
= input(
'
請(qǐng)輸入密碼>>:
'
).strip()
user_dic
=
{
'
username
'
:name,
'
password
'
:password
}
self.socket.send(pickle.dumps(user_dic))
#
把用戶名和密碼發(fā)送給server
res = struct.unpack(
'
i
'
,self.socket.recv(4
))[0]
if
res:
#
接收返回的信息,并判斷
print
(
'
welcome
'
.center(20,
'
-
'
))
user_info_dic
=
self.get_recv()
self.username
= user_info_dic.get(
'
username
'
)
print
(user_info_dic)
return
True
else
:
print
(
'
\033[1;31m用戶名或者密碼不對(duì)!\033[0m
'
)
count
+= 1
def
readfile(self):
'''
讀取文件,得到的文件內(nèi)容的bytes型
'''
with open(self.filepath,
'
rb
'
) as f:
filedata
=
f.read()
return
filedata
def
getfile_md5(self):
'''
對(duì)文件內(nèi)容md5
'''
return
hashlib.md5(self.readfile()).hexdigest()
def
progress_bar(self, num, get_size, file_size):
'''
進(jìn)度條顯示
'''
float_rate
= get_size /
file_size
#
rate = str(float_rate * 100)[:5] # 95.85%
rate = round(float_rate * 100,2)
#
95.85%
if
num == 1:
#
1表示下載
sys.stdout.write(
'
\r已下載:\033[1;32m{0}%\033[0m
'
.format(rate))
elif
num == 2:
#
2 表示上傳
sys.stdout.write(
'
\r已上傳:\033[1;32m{0}%\033[0m
'
.format(rate))
sys.stdout.flush()
def
get(self):
'''
從server下載文件到client
'''
if
len(self.cmds) > 1
:
filename
= self.cmds[1
]
self.filepath
= os.path.join(self.DOWNLOAD_PATH, filename)
#
結(jié)合目錄名和文件名
if
os.path.isfile(self.filepath):
#
如果文件存在,支持?jǐn)帱c(diǎn)續(xù)傳
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(4
))[0]
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文件已經(jīng)存在\033[0m
'
)
else
:
print
(
'
\033[1;33m正在進(jìn)行斷點(diǎn)續(xù)傳....\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(
1, get_size, file_size)
#
1表示下載
if
self.getfile_md5() == file_md5:
#
判斷下載下來(lái)的文件MD5值和server傳過(guò)來(lái)的MD5值是否一致
print
(
'
\n\033[1;32m下載成功\033[0m
'
)
else
:
print
(
'
\n\033[1;32m下載文件大小與源文件大小不一致,請(qǐng)重新下載,將會(huì)支持?jǐn)帱c(diǎn)續(xù)傳033[0m
'
)
else
:
print
(
'
\n\033[1;31m該文件,之前被下載了一部分,但是server端的該文件,已被刪除,無(wú)法再次下載\033[0m
'
)
else
:
#
文件第一次下載
self.socket.send(struct.pack(
'
i
'
, 0))
#
0 表示之前沒(méi)有下載過(guò)
header_size = struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
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
=
0
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(
1, get_size, file_size)
#
1表示下載
print
(
'
總大小:%s已下載:%s
'
%
(file_size, get_size))
if
self.getfile_md5() == file_md5:
#
判斷下載下來(lái)的文件MD5值和server傳過(guò)來(lái)的MD5值是否一致
print
(
'
\n\033[1;32m恭喜你,下載成功\033[0m
'
)
else
:
print
(
'
\n\033[1;32m下載失敗,再次下載支持?jǐn)帱c(diǎn)續(xù)傳\033[0m
'
)
else
:
print
(
'
\n\033[1;32m當(dāng)前目錄下,文件不存在\033[0m
'
)
else
:
print
(
'
用戶沒(méi)有輸入文件名
'
)
def
put(self):
'''
往server自己的home/alice目錄下,當(dāng)前工作的目錄下上傳文件
'''
if
len(self.cmds) > 1:
#
確保用戶輸入了文件名
filename = self.cmds[1
]
filepath
=
os.path.join(self.UPLOAD_PATH, filename)
if
os.path.isfile(filepath):
self.socket.send(struct.pack(
'
i
'
, 1
))
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(4
))[0]
if
state:
#
已經(jīng)存在了
has_state = struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
if
has_state:
quota_state
= struct.unpack(
'
i
'
,self.socket.recv(4
))[0]
if
quota_state:
has_size
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
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(4
))[0]
self.progress_bar(
2
, recv_size, filesize)
success_state
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
'''
這里一定要判斷,因?yàn)樽詈笠淮蝧end(line)之后等待server返回,
server返回,最后一次的recv_size==file_size,但client已經(jīng)跳出了循環(huán),
所以在for外面接收的success_state其實(shí)時(shí)file_size,這種情況只針對(duì)大文件
'''
if
success_state ==
filesize:
success_state
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
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當(dāng)前目錄下,文件已經(jīng)存在\033[0m
'
)
else
:
#
第一次傳
quota_state = struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
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(4
))[0]
self.progress_bar(
2
, recv_size, filesize)
succes_state
= struct.unpack(
'
i
'
,self.socket.recv(4
))[0]
if
succes_state ==
filesize:
succes_state
= struct.unpack(
'
i
'
,self.socket.recv(4
))[0]
if
succes_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
'
, 0))
else
:
print
(
'
用戶沒(méi)有輸入文件名
'
)
def
ls(self):
'''
查詢當(dāng)前工作目錄下,文件列表
'''
dir_size
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
recv_size
=
0
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-8適合linux
def
mkdir(self):
'''
增加目錄
'''
if
len(self.cmds) > 1
:
res
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
if
res:
print
(
'
\033[1;32m在當(dāng)前目錄下,增加目錄: %s 成功\033[0m
'
% self.cmds[1
])
else
:
print
(
'
\033[1;31m增加目錄失敗\033[0m
'
)
else
:
print
(
'
沒(méi)有輸入要增加的目錄名
'
)
def
cd(self):
'''
切換目錄
'''
if
len(self.cmds) > 1
:
res
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
if
res:
print
(
'
\033[1;32m切換成功\033[0m
'
)
else
:
print
(
'
\033[1;32m切換失敗\033[0m
'
)
else
:
print
(
'
沒(méi)有輸入要切換的目錄名
'
)
def
remove(self):
'''
刪除指定的文件,或者文件夾
'''
if
len(self.cmds) > 1
:
res
= struct.unpack(
'
i
'
, self.socket.recv(4
))[0]
if
res:
print
(
'
\033[1;32m刪除成功\033[0m
'
)
else
:
print
(
'
\033[1;31m刪除失敗\033[0m
'
)
else
:
print
(
'
沒(méi)有輸入要?jiǎng)h除的文件
'
)
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[0]):
getattr(self, self.cmds[0])()
else
:
print
(
'
請(qǐng)重新輸入
'
)
except
Exception as e:
#
server關(guān)閉了
print
(e)
break
def
close(self):
self.socket.close()
if
__name__
==
'
__main__
'
:
ftp_client
=
Ftpclient()
ftp_client.interactive()
ftp_client.close()
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

