用 Python实现一个ftp+CRT

  转载请注明出处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)]:这样。

五、总结

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

  

  

时间: 2024-08-12 21:42:44

用 Python实现一个ftp+CRT的相关文章

用Python写一个ftp下载脚本

用Python写一个ftp下载脚本 ----基于Red Hat Enterprise Linux Server release 6.4 (Santiago):python 2.6.6 Ps:少侠我接触Python半个月以来接到的第一个需求,虽然如此简单的一个脚本,少侠我磕磕绊绊却用了将近一天半的时间才写出来,但还是很开心,毕竟也粗来了,废话不多说,切入正题.因为一开始没有用过ftplib模块,所以各种谷歌度娘一堆资料杂乱不堪,话不清,理不乱的,本文实现的功能简单,下面介绍一下,以免误导读者. 需

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

### 作者介绍:* author:lzl### 博客地址:* http://www.cnblogs.com/lianzhilei/p/5813986.html### 功能实现 作业:开发一个支持多用户在线的FTP程序 要求: 用户加密认证 允许同时多用户登录 每个用户有自己的家目录 ,且只能访问自己的家目录 对用户进行磁盘配额,每个用户的可用空间不同 允许用户在ftp server上随意切换目录 允许用户查看当前目录下文件 允许上传和下载文件,保证文件一致性 文件传输过程中显示进度条 附加功能

python实现从FTP下载文件通过多线程同时分发到多台机器

python非常厉害的一门编程语言,被称之为编程语言中的万能粘合剂,它可以和现有的大部分编程语言来完美对接,今天来为大家说说使用python写一个从ftp上下载文件,然后通过python的多线程模块threading同时分发到多台机器,甚至上百台机器上,多了不说了,直接上代码,代码里会详细讲解每一步的操作. 可根据你的实际情况来修改脚本,实现多线程远程无缝隙操作服务器. #!/usr/bin/python #coding: utf-8 #加载我们需要使用到的模块 from ftplib impo

python之实现ftp上传下载代码(含错误处理)

# -*- coding: utf-8 -*- #python 27 #xiaodeng #python之实现ftp上传下载代码(含错误处理) #http://www.cnblogs.com/kaituorensheng/p/4480512.html#_label2 import ftplib import socket import os def ftpconnect(ftp_info): try: ftp = ftplib.FTP(ftp_info[0]) except (socket.er

利于Wininet创建一个FTP客户端的步骤

Wininet是Win32关于网络的API,MFC也有对于Wininet的封装,可以利用这组API实现FTP和HTTP通信. Wininet API的头文件:Wininet.下面是Wininet建立FTP客户端的一般步骤.第一步:初始话Wininet,实际上就是设置一些关于是否使用代理,访问方式等的参数.第二步:建立一个FTP链接.第三步:操作ftp服务器上的文件.第四步:关闭各种句柄. 作用 函数原型 说明 初始Wininet函数 HINTERNET InternetOpen( LPCTSTR

用python 发送一个smtp邮件

用python写一个简单的邮件,需要发送的邮件内容自定义,可用于监控警告邮件发送. #!/usr/bin/env python import smtplib    //内置smtp库 import string HOST = "smtp.163.com"    //定义用于发送邮件的主机,这里用网易163 SUBJECT = "Test email from Python"    //定义邮件标题 TO = "[email protected]"

python 登陆一个网站

今天想用python写一个登陆的脚本,搜了一下,网上挺多的,看了一些后写了个登陆虎扑论坛的脚本. 原理: 只要在发送http请求时,带上含有正常登陆的cookie就可以了. 1.首先我们要先了解cookie的工作原理. Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie).Cookie名称和值可以由服务器端开发自己定义,

python中一个简单的webserver

python中一个简单的webserver 2013-02-24 15:37:49 分类: Python/Ruby 支持多线程的webserver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/usr/bin/python from SocketServer import ThreadingMixIn from BaseHTTPServer import HTTPServer,BaseHTTPRequestHandler cla

python是一个解释器

python是一个解释器 利用pip安装python插件的时候,观察到python的运作方式是逐步解释执行的 适合作为高级调度语言: 异常的处理以及效率应该是主要的问题