python 播放视频 ftp_Python实现FTP

一、程序介绍:

需求:

支持多用户在线的FTP程序

要求:

1、用户加密认证

2、允许同时多用户登录

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

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

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

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

7、允许上传和下载文件,保证文件一致性

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

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

实现功能:

用户加密认证

允许同时多用户登录

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

允许上传和下载文件,保证文件一致性

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

程序结构:

FTP服务端

FtpServer #服务端主目录

├── bin #启动目录

│   └── ftp_server.py #启动文件

├── conf #配置文件目录

│   ├── accounts.cfg #用户存储

│   └── settings.py #配置文件

├── core #程序主逻辑目录

│   ├── ftp_server.py #功能文件

│   └── main.py #主逻辑文件

├── home #用户家目录

│   ├── test001 #用户目录

│   └── test002 #用户目录

└── log #日志目录

FTP客户端

FtpClient #客户端主目录

└── ftp_client.py #客户端执行文件

二、流程图

三、代码

#FtpServer代码

bin/ftp_server.py

#!/usr/bin/env python

#_*_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 main

if __name__ == '__main__':

main.ArvgHandler()

conf/accounts.cfg

[DEFAULT]

[test001]

Password = 123

Quotation = 100

[test002]

Password = 123

Quotation = 100

conf/settings.py

#!/usr/bin/env python

#_*_coding:utf-8_*_

import os

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

USER_HOME = "%s/home" % BASE_DIR

LOG_DIR = "%s/log" % BASE_DIR

LOG_LEVEL = "DEBUG"

ACCOUNT_FILE = "%s/conf/accounts.cfg" % BASE_DIR

HOST = "127.0.0.1"

PORT = 9999

core/ftp_server.py

#!/usr/bin/env python

#_*_coding:utf-8_*_

import socketserver

import json

import configparser

import os

import hashlib

from conf import settings

STATUS_CODE = {

250:"Invalid cmd format, e.g:{'action':'get','filename':'test.py','size':344}",

251:"Invalid cmd",

252:"Invalid auth data",

253:"Wrong username or password",

254:"Passed authentication",

255:"filename doesn't provided",

256:"File doesn't exist on server",

257:"ready to send file",

258:"md5 verification",

}

'''

250:“无效的cmd格式,例如:{'action':'get','filename':'test.py','size':344}”,

251:“无效的CMD”,

252:“验证数据无效”,

253:“错误的用户名或密码”,

254:“通过身份验证”,

255:“文件名不提供”,

256:“服务器上不存在文件”,

257:“准备发送文件”,

258:“md5验证”,

'''

class FTPHandler(socketserver.BaseRequestHandler):

def handle(self):

'''接收客户端消息(用户,密码,action)'''

while True:

self.data = self.request.recv(1024).strip()

print(self.client_address[0])

print(self.data)

# self.request.sendall(self.data.upper())

if not self.data:

print("client closed...")

break

data = json.loads(self.data.decode()) #接收客户端消息

if data.get('action') is not None: #action不为空

print("---->", hasattr(self, "_auth"))

if hasattr(self, "_%s" % data.get('action')): #客户端action 符合服务端action

func = getattr(self, "_%s" % data.get('action'))

func(data)

else: #客户端action 不符合服务端action

print("invalid cmd")

self.send_response(251) # 251:“无效的CMD”

else: #客户端action 不正确

print("invalid cmd format")

self.send_response(250) # 250:“无效的cmd格式,例如:{'action':'get','filename':'test.py','size':344}”

def send_response(self,status_code,data=None):

'''向客户端返回数据'''

response = {'status_code':status_code,'status_msg':STATUS_CODE[status_code]}

if data:

response.update(data)

self.request.send(json.dumps(response).encode())

def _auth(self,*args,**kwargs):

'''核对服务端 发来的用户,密码'''

# print("---auth",args,kwargs)

data = args[0]

if data.get("username") is None or data.get("password") is None: #客户端的用户和密码有一个为空 则返回错误

self.send_response(252) # 252:“验证数据无效”

user = self.authenticate(data.get("username"),data.get("password")) #把客户端的用户密码进行验证合法性

if user is None: #客户端的数据为空 则返回错误

self.send_response(253) # 253:“错误的用户名或密码”

else:

print("password authentication",user)

self.user = user

self.send_response(254) # 254:“通过身份验证”

def authenticate(self,username,password):

'''验证用户合法性,合法就返回数据,核对本地数据'''

config = configparser.ConfigParser()

config.read(settings.ACCOUNT_FILE)

if username in config.sections(): #用户匹配成功

_password = config[username]["Password"]

if _password == password: #密码匹配成功

print("pass auth..",username)

config[username]["Username"] = username

return config[username]

def _put(self,*args,**kwargs):

"client send file to server"

data = args[0]

base_filename = data.get('filename')

file_obj = open(base_filename, 'wb')

data = self.request.recv(4096)

file_obj.write(data)

file_obj.close()

def _get(self,*args,**kwargs):

'''get 下载方法'''

data = args[0]

if data.get('filename') is None:

self.send_response(255) # 255:“文件名不提供”,

user_home_dir = "%s/%s" %(settings.USER_HOME,self.user["Username"]) #当前连接用户的目录

file_abs_path = "%s/%s" %(user_home_dir,data.get('filename')) #客户端发送过来的目录文件

print("file abs path",file_abs_path)

if os.path.isfile(file_abs_path): #客户端目录文件名 存在服务端

file_obj = open(file_abs_path,'rb') # 用bytes模式打开文件

file_size = os.path.getsize(file_abs_path) #传输文件的大小

self.send_response(257,data={'file_size':file_size}) #返回即将传输的文件大小 和状态码

self.request.recv(1) #等待客户端确认

if data.get('md5'): #有 --md5 则传输时加上加密

md5_obj = hashlib.md5()

for line in file_obj:

self.request.send(line)

md5_obj.update(line)

else:

file_obj.close()

md5_val = md5_obj.hexdigest()

self.send_response(258,{'md5':md5_val})

print("send file done....")

else: #没有 --md5 直接传输文件

for line in file_obj:

self.request.send(line)

else:

file_obj.close()

print("send file done....")

else:

self.send_response(256) # 256:“服务器上不存在文件”=

def _ls(self,*args,**kwargs):

pass

def _cd(self,*args,**kwargs):

pass

if __name__ == '__main__':

HOST, PORT = "127.0.0.1", 9999

core/main.py

#!/usr/bin/env python

#_*_coding:utf-8_*_

import optparse

from core.ftp_server import FTPHandler

import socketserver

from conf import settings

class ArvgHandler(object):

def __init__(self):

self.parser = optparse.OptionParser()

# parser.add_option("-s","--host",dest="host",help="server binding host address")

# parser.add_option("-p","--port",dest="port",help="server binding port")

(options, args) = self.parser.parse_args()

# print("parser",options,args)

# print(options.host,options.port)

self.verify_args(options, args)

def verify_args(self,options,args):

'''校验并调用相应功能'''

if hasattr(self,args[0]):

func = getattr(self,args[0])

func()

else:

self.parser.print_help()

def start(self):

print('---going to start server---')

server = socketserver.ThreadingTCPServer((settings.HOST, settings.PORT), FTPHandler)

server.serve_forever()

#FtpClient代码

ftp_client.py

#!/usr/bin/env python

#_*_coding:utf-8_*_

import socket

import os

import sys

import optparse

import json

import hashlib

STATUS_CODE = {

250:"Invalid cmd format, e.g:{'action':'get','filename':'test.py','size':344}",

251:"Invalid cmd",

252:"Invalid auth data",

253:"Wrong username or password",

254:"Passed authentication",

255:"filename doesn't provided",

256:"File doesn't exist on server",

257:"ready to send file",

}

class FTPClient(object):

def __init__(self):

parser = optparse.OptionParser()

parser.add_option("-s","--server",dest="server",help="ftp server ip_addr")

parser.add_option("-P","--port",type="int",dest="port",help="ftp server port")

parser.add_option("-u","--username",dest="username",help="username")

parser.add_option("-p","--password",dest="password",help="password")

self.options,self.args = parser.parse_args()

self.verify_args(self.options,self.args)

self.make_connection()

def make_connection(self):

'''远程连接'''

self.sock = socket.socket()

self.sock.connect((self.options.server,self.options.port))

def verify_args(self,options,args):

'''校验参数合法性'''

if options.username is not None and options.password is not None: #用户和密码,两个都不为空

pass

elif options.username is None and options.password is None: #用户和密码,两个都为空

pass

else: #用户和密码,有一个为空

# options.username is None or options.password is None: #用户和密码,有一个为空

exit("Err: username and password must be provided together...")

if options.server and options.port:

# print(options)

if options.port >0 and options.port <65535:

return True

else:

exit("Err:host port must in 0-65535")

def authenticate(self):

'''用户验证,获取客户端输入信息'''

if self.options.username: #有输入信息 发到远程判断

print(self.options.username,self.options.password)

return self.get_auth_result(self.options.username,self.options.password)

else: #没有输入信息 进入交互式接收信息

retry_count = 0

while retry_count <3:

username = input("username: ").strip()

password = input("password: ").strip()

return self.get_auth_result(username,password)

# retry_count +=1

def get_auth_result(self,user,password):

'''远程服务器判断 用户,密码,action '''

data = {'action':'auth',

'username':user,

'password':password,}

self.sock.send(json.dumps(data).encode()) #发送 用户,密码,action 到远程服务器 等待远程服务器的返回结果

response = self.get_response() #获取服务器返回码

if response.get('status_code') == 254: #通过验证的服务器返回码

print("Passed authentication!")

self.user = user

return True

else:

print(response.get("status_msg"))

def get_response(self):

'''得到服务器端回复结果,公共方法'''

data = self.sock.recv(1024)

data = json.loads(data.decode())

return data

def interactive(self):

'''交互程序'''

if self.authenticate(): #认证成功,开始交互

print("--start interactive iwth u...")

while True: #循环 输入命令方法

choice = input("[%s]:"%self.user).strip()

if len(choice) == 0:continue

cmd_list = choice.split()

if hasattr(self,"_%s"%cmd_list[0]): #反射判断 方法名存在

func = getattr(self,"_%s"%cmd_list[0]) #反射 方法名

func(cmd_list) #执行方法

else:

print("Invalid cmd.")

def _md5_required(self,cmd_list):

'''检测命令是否需要进行MD5的验证'''

if '--md5' in cmd_list:

return True

def show_progress(self,total):

'''进度条'''

received_size = 0

current_percent = 0

while received_size < total:

if int((received_size / total) * 100) > current_percent :

print("#",end="",flush=True)

current_percent = (received_size / total) * 100

new_size = yield

received_size += new_size

def _get(self,cmd_list):

''' get 下载方法'''

print("get--",cmd_list)

if len(cmd_list) == 1:

print("no filename follows...")

return

#客户端操作信息

data_header = {

'action':'get',

'filename':cmd_list[1],

}

if self._md5_required(cmd_list): #命令请求里面有带 --md5

data_header['md5'] = True #将md5加入 客户端操作信息

self.sock.send(json.dumps(data_header).encode()) #发送客户端的操作信息

response = self.get_response() #接收服务端返回的 操作信息

print(response)

if response["status_code"] ==257: #服务端返回的状态码是:传输中

self.sock.send(b'1') # send confirmation to server

base_filename = cmd_list[1].split('/')[-1] #取出要接收的文件名

received_size = 0 #本地接收总量

file_obj = open(base_filename,'wb') #bytes模式写入

if self._md5_required(cmd_list): #命令请求里有 --md5

md5_obj = hashlib.md5()

progress = self.show_progress(response['file_size'])

progress.__next__()

while received_size < response['file_size']: #当接收的量 小于 文件总量 就循环接收文件

data = self.sock.recv(4096) #一次接收4096

received_size += len(data) #本地接收总量每次递增

try:

progress.send(len(data))

except StopIteration as e:

print("100%")

file_obj.write(data) #把接收的数据 写入文件

md5_obj.update(data) #把接收的数据 md5加密

else:

print("--->file rece done

file_obj.close() #关闭文件句柄

md5_val = md5_obj.hexdigest()

md5_from_server = self.get_response() #获取服务端发送的 md5

if md5_from_server['status_code'] ==258: #状态码为258

if md5_from_server['md5'] == md5_val: #两端 md5值 对比

print("%s 文件一致性校验成功!" %base_filename)

# print(md5_val,md5_from_server)

else: #没有md5校验 直接收文件

progress = self.show_progress(response['file_size'])

progress.__next__()

while received_size < response['file_size']: #当接收的量 小于 文件总量 就循环接收文件

data = self.sock.recv(4096) #一次接收4096

received_size += len(data) #本地接收总量每次递增

file_obj.write(data) #把接收的数据 写入文件

try:

progress.send(len(data))

except StopIteration as e:

print("100%")

else:

print("--->file rece done

file_obj.close() #关闭文件句柄

def _put(self,cmd_list):

''' put 下载方法'''

print("put--", cmd_list)

if len(cmd_list) == 1:

print("no filename follows...")

return

# 客户端操作信息

data_header = {

'action': 'put',

'filename': cmd_list[1],

}

self.sock.send(json.dumps(data_header).encode()) # 发送客户端的操作信息

self.sock.recv(1)

file_obj = open(cmd_list[1],'br')

for line in file_obj:

self.sock.send(line)

if __name__ == '__main__':

ftp = FTPClient()

ftp.interactive()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值