转载请注明出处http://www.cnblogs.com/Wxtrkbc/p/5590004.html   

  本来最初的想法是实现一个ftp服务器,用来实现用户的登陆注册和文件的断点上传下载等,结果做着做着就连CRT也顺带着跟着完成了,然后就变成了这样一个'不伦不类'的工具。用到的知识有hashlib加密密码传输,面向对象,sockeserver支持多客户访问,os.subprocess来处理系统自带的命令,然后自定义上传下载命令,以及如何实现断点续传,传输过程中的粘包问题,以及如何反射处理用户的输入。这里仅仅是为了娱乐,下面先来看一下效果图

      

        

一、客户端

首先定义以个客户端类,大致在类里面定义了这些方法,以后有需要的话,可以对其进行扩充,下面的类一初始化就会去执行start方法,如果在服务端注册登录成功的话,就会接下来执行internet方法,等待用户的输入,

class Client:
def __init__(self, address):
self.address = Client.get_ip_port(address)
self.help_message = [
'目前自定义的命令仅支持以下操作:\n'
'\tput|filename',
'\tget|filename',
]
self.start()
self.cwd = '' @staticmethod
def get_ip_port(address):
ip, port = address.split(':')
return (ip, int(port)) def register(self):
try_counts = 0
while try_counts < 3:
user = input('请输入用户名:')
if len(user) == 0:
continue
passwd = input('请输入用密码:')
if len(passwd) == 0:
continue
pd = hashlib.sha256()
pd.update(passwd.encode())
self.socket.sendall('register|{}:{}'.format(user, pd.hexdigest()).encode()) # 发送加密后的账户信息
ret = self.socket.recv(1024).decode()
if ret == '202':
print('注册成功请登录')
os.mkdir(os.path.join(settings.USER_HOME_DIR, user)) # 在客户端也创建一个用户家目录
os.mkdir(os.path.join(settings.USER_HOME_DIR, user, 'download_file'))
os.mkdir(os.path.join(settings.USER_HOME_DIR, user, 'upload_file'))
return True
else:
try_counts += 1
sys.exit("Too many attemps") def login(self):
try_counts = 0
while try_counts < 3:
user = input('请输入用户名:')
self.user = user
if len(user) == 0:
continue
passwd = input('请输入用密码:')
if len(passwd) == 0:
continue
pd = hashlib.sha256()
pd.update(passwd.encode())
self.socket.sendall('login|{}:{}'.format(user, pd.hexdigest()).encode()) # 发送加密后的账户信息
ret = self.socket.recv(1024).decode()
if ret == '200':
print('登陆成功!')
self.cwd = self.socket.recv(1024).decode()
return True
else:
print('用户或密码错误,请从新登陆:')
try_counts += 1
sys.exit("Too many attemps") def internet(self):
pass<br>
def process(self, cmd, argv): # 处理自定义的命令
pass<br>
def help(self, argv=None):
pass def put(self, argv=None):
pass def get(self, argv=None):
pass def start(self):
self.socket = socket.socket()
try:
self.socket.connect(self.address)
except Exception as e:
sys.exit("Failed to connect server:%s" % e)
print(self.socket.recv(1024).decode())
inp = input('1、注册,2、登录,3、离开: ')
if inp == '1':
if self.register():
if self.login(): # 登陆成功后进行交互操作
self.internet()
elif inp == '2':
if self.login():
self.internet()
else:
sys.exit() if __name__ == '__main__':
# address = input('请输入FTP服务端地址(ip:port):')
address = '127.0.0.1:9999'
client = Client(address)

二、服务端  

  服务端是用socketserver来写的,以便出来多用户请求,每当用户来来请求的时候,先让其注册或登陆,注册完后,以用户的名字为其创建一个家目录,并将用户名和密码保存起来,将来用户登录的时候从db中取出数据和用户输入的密码进行对比,正确后让其进行下一步操作,用户密码输入三次以后,退出程序。服务端为了响应客户端的每一个操作,定义了一个函数专门接受客户端传来的每一次命令,然后对其分解,反射到具体的服务端具体的函数中去,这里需要先定义客户端传过来的数据的格式是 cmd|args。大家可以看到我上面客户端登陆认证的代码传输的数据格式 ('login|{}:{}'.format(user, pd.hexdigest())) ,这么做的道理就是为了服务端好统一进行处理。下面来一下代码具体怎么做的,

def handle(self):
self.request.sendall('欢迎来到FTP服务器!'.encode())
while True:
data = self.request.recv(1024).decode()
if '|' in data:
cmd, argv = data.split('|')
else:
cmd = data
argv = None
self.process(cmd, argv) # 将接受道德数据出来后在经过 process def process(self, cmd, argv=None): # 使用反射处理客户端传过来的命令(自定义的命令)
if hasattr(self, cmd):
func = getattr(self, cmd)
func(argv)          
else:
pass

这么一写后,就只需在服务端写上相应的函数,比如,注册的话,我就只需写一个函数名为register的函数,专门来处理注册,同理登陆的话,我也只需写一个login函数即可,当初我第一次写的时候,都是用if,else来判断,当时的代码写下来,自己看着都恐怖,写着写着,自己都不知道判断到哪里去了,想想就泪奔。而用反射的话,就不需要这些繁琐的步骤了,而且以后要添加功能的话,只需要写一个简单的函数即可。客户端的反射也这样做的,就不再重复了。

三、断点上传

如果只是文件上传的话,比较好写,但是如果要断点上传的话,就有些麻烦了,这里提供一种思路,那就是服务端纪录以上传文件的大小,下次上传的时候,如果要断点续传的话,先将已上传的文件大小发给客户端,然后客户端从断点的位置在上传。下面来看一下代码的实现,

# 客户端

def put(self, argv=None):
if len(argv) == None:
print("Please add the file path that you want to upload")
return
print('上传之前请确保的文件在用户upload文件夹下')
file_path = os.path.join(settings.USER_HOME_DIR, self.user, 'upload_file', argv)
if os.path.exists(file_path): #判断文件存不存在
file_size = os.stat(file_path).st_size
file_info = {
'file_name': argv,
'file_size': file_size,
}
has_sent = 0 self.socket.sendall(('put|{}'.format(json.dumps(file_info))).encode())  # 将上传的文件信息作为参数发给服务端
ret = self.socket.recv(1024).decode()
if ret == '204':
inp = input("文件存在,是否续传?Y/N:").strip()
if inp.upper() == "Y":
self.socket.sendall('205'.encode())
has_sent = int(self.socket.recv(1024).decode())
else:
self.socket.sendall('207'.encode())
with open(file_path, 'rb') as f:
f.seek(has_sent)          # 如果要续传的话,has_set为已经上传的大小,否则 has_set为0,从头开始上传
for line in f:
self.socket.sendall(line)
has_sent += len(line)
k = int((has_sent / file_size * 100)) # 下面的代码是用来显示进度条
table_space = (100 - k) * ' '
flag = k * '*'
time.sleep(0.05)
sys.stdout.write('\r{} {:.0%}'.format((flag + table_space), (has_sent / file_size)))
print() # 显示换行的作用

下面来看一下服务端的代码,服务端和客户端都是一收一发,注意要保持recv要收到信息,否则会阻塞,此外传送的时候可能会发生粘包,解决的办法是在发送文件文件前,先发送一条标志信息,当服务端收到该标志信息,就可以通知客户端发送文件了。

# 服务端

def put(self, argv=None):
file_info = json.loads(argv)          # 获取客户端传来的消息
file_name = file_info['file_name']
file_size = int(file_info['file_size'])
file_path = os.path.join(settings.USER_HOME_DIR, 'kobe', 'upload_file', file_name)
have_send = 0 # 已经上传的位置 if os.path.exists(file_path):
self.request.sendall('204'.encode())
ret = self.request.recv(1024).decode()
if ret == '205': # 续传
have_send = os.stat(file_path).st_size     # 获取已经上传文件的大小
self.request.sendall(bytes(str(have_send), encoding='utf-8'))
f = open(file_path, 'ab')             # 续传的话,以a模式打开文件,
else:  
f = open(file_path, 'wb')             # 不续传的话,以w模式打开,
else:
self.request.sendall('206'.encode())      # 直接上传
f = open(file_path, 'wb') while True:
if have_send == file_size:              # 一旦接受到的内容等于文件大小,直接退出循环  
break
try:
ret = self.request.recv(1024)
except Exception as e:
break
f.write(ret)
have_send += len(ret)

解决了断点续传,那么断点下载也是同样的道理,就不再重复讲了。

4、CRT 

  最后来讲一下怎么实现远程操作服务端主机,这里存粹是为了娱乐。其实实现起来也比较简单,主要使用了subprocess.getoutput获取来处理客户端输入的命令,然后将结果返回给客户端就可以,但是有一点致命的缺陷,那就是不支持cd命令,如果不支持cd命令的话,那还谈什么远程操作了,所以这里对于cd命令就需要特殊对待了,解决的办法,当然是找一个支持cd的命令,下面来看下服务端大代码

def process(self, cmd, argv=None):  # 使用反射处理客户端传过来的命令(自定义的命令)
if hasattr(self, cmd):
func = getattr(self, cmd)
func(argv)
else:
if cmd.startswith('cd'): #(处理cd命令,subprocess不支持cd命令)
os.chdir(cmd.split(' ')[1])   # 获取cd 命令的参数,交给os.chdir来切换目录
pass else:
data = subprocess.getoutput(cmd)  # 其他命令,交给subprocess处理,
data_length = len(data)
if data_length != 0:
pass
else:
pass

处理完相关的命令,将结果返会给客户端显示就可以了。最后吗客户端保存一个变量,用来保存当前的执行路径显示出来,就像[C:\Users\Tab\PycharmProjects\myftp\New_ftp\New_Server (help)]:这样。

五、总结

到这里,就基本将关键性的东西讲完了,其他的都是一些简单的操作,只要自己稍微注意一下,你就可以写出一个类似的东西。

  

  

用 Python实现一个ftp+CRT(不用ftplib)的更多相关文章

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

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

  2. 利用Python制作一个只属于和她的聊天器,再也不用担心隐私泄露啦!

    ------------恢复内容开始------------ 是否担心微信的数据流会被监视?是否担心你和ta聊天的小秘密会被保存到某个数据库里?没关系,现在我们可以用Python做一个只属于你和ta的 ...

  3. 【Python学习 】Python实现的FTP上传和下载功能

    一.背景 最近公司的一些自动化操作需要使用Python来实现FTP的上传和下载功能.因此参考网上的例子,撸了一段代码来实现了该功能,下面做个记录. 二.ftplib介绍 Python中默认安装的ftp ...

  4. python下操作ftp上传

    生产情况:tomcat下业务log备份,目录分多级,然后对应目录格式放到ftp上:所以,结构上 我就是一级一级目录进行判断(因为我没有找到在ftp一次判断其子目录是否存在),还有一个low点就是我没有 ...

  5. python socket编程---从使用Python开发一个Socket示例说到开发者的思维和习惯问题

    今天主要说的是一个开发者的思维和习惯问题. 思维包括编程的思维和解决一个具体问题的分析思维,分析思路,分析方法,甚至是分析工具. 无论是好习惯还是不好的习惯,都是在者一天一天的思维中形成的.那些不好的 ...

  6. 一句python代码搭建FTP服务

    环境搭建: python windows/linux pip install pyftpdlib (安装失败请到这里下载:https://pypi.python.org/pypi/pyftpdlib/ ...

  7. 用python做一个搜索引擎(Pylucene)

    什么是搜索引擎? 搜索引擎是“对网络信息资源进行搜集整理并提供信息查询服务的系统,包括信息搜集.信息整理和用户查询三部分”.如图1是搜索引擎的一般结构,信息搜集模块从网络采集信息到网络信息库之中(一般 ...

  8. 基于线程开发一个FTP服务器

    一,项目题目:基于线程开发一个FTP服务器 二,项目要求: 基本要求: 1.用户加密认证   2.允许同时多用户登录   3.每个用户有自己的家目录 ,且只能访问自己的家目录   4.对用户进行磁盘配 ...

  9. web自动化 基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架

    基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架   by:授客 QQ:1033553122     博客:http://blog.sina.com.cn/ishou ...

随机推荐

  1. 洛谷P1078 文化之旅

    P1078 文化之旅 1.1K通过 3.6K提交 题目提供者洛谷OnlineJudge 标签NOIp普及组2012 难度普及+/提高 时空限制1s / 128MB 提交  讨论  题解 最新讨论更多讨 ...

  2. springboot如何集成mybatis的pagehelper分页插件

    mybatis提供了一个非常好用的分页插件,之前集成的时候需要配置mybatis-config.xml的方式,今天我们来看下它是如何集成springboot来更好的服务的. 只能说springboot ...

  3. 手脱EXE32Pack v1.39

    1.PEID查壳 EXE32Pack v1.39 2.载入OD,先F8跟一下 0040A00C > 3BC0 cmp eax,eax ; //程序入口点 0040A00E je short st ...

  4. Qt ------ QTabWidget

    下图: 1.长方形的 objectName 可写可不写,不写就作用于所有 QTabWidget:椭圆形的 QTabWidget#tabWidget 要么四个都要写,要么四个都不写 2.下图的 CSS ...

  5. 第1章-初识Vue.js

    一.初识Vue 1.1.本次我们学习的内容 常用指令:vue中最基础的内容 交互: 网络请求 组件: 是vue.js 这个框架 最核心,最精华的内容,因为vue呐,它在所有的框架中是把组件化做到了极致 ...

  6. Java集合(4)一 红黑树、TreeMap与TreeSet(下)

    目录 Java集合(1)一 集合框架 Java集合(2)一 ArrayList 与 LinkList Java集合(3)一 红黑树.TreeMap与TreeSet(上) Java集合(4)一 红黑树. ...

  7. Python学习笔记(三十四)—内置模块(3)base64

    摘抄自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431954588 ...

  8. 使用Java代码发送SMTP邮件

    package cn.Douzi.send; import javax.mail.Session; import javax.mail.Transport; import javax.mail.int ...

  9. JAVA多线程提高五:原子性操作类的应用

    当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量i=1,A线程更新i+1,B线程也更新i+1,经过两个线程操作之后可能i不等于3,而是等于2.因为A和B线程在更新变量i ...

  10. JVM学习四:JVM之类加载器之初始化分析

    在经过了前面的加载  和 连接分析之后,这一节我们进入重要的初始化分析过程: 一.认识初始化 初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默 ...