Python ftplib模块解决socket交互阻塞问题

使用ftplib模块操作FTP服务器时很容易阻塞,原因各种各样,有时还与ftp服务器有关。

下面的demo重载了ftplib模块,主要是使用了非阻塞的socket(self.sock.setblocking(0)),以及使用conn.recv接收数据的方式代替conn.makefile生成文件句柄接收数据,解决了阻塞问题,同时增加了一些功能。

#!usr/bin/env python
import os
import re
import time
import socket
import ftplib
import logging
import traceback
from ftplib import FTP, CRLF, Error, _SSLSocket


class FTP(FTP):
    blocksize = 8192

    def connect(self, host='', port=0, timeout=-999, source_address=None):
        if host != '':
            self.host = host
        if port > 0:
            self.port = port
        if timeout != -999:
            self.timeout = timeout
        if source_address is not None:
            self.source_address = source_address
        self.sock = socket.create_connection((self.host, self.port), self.timeout,
                                             source_address=self.source_address)
        self.sock.setblocking(0)  # 设置为非阻塞式连接
        self.af = self.sock.family
        self.file = self.sock.makefile('r', encoding=self.encoding)
        self.welcome = self.getresp()
        return self.welcome

    def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
        self.voidcmd('TYPE I')
        with self.transfercmd(cmd, rest) as conn:
            while 1:
                try:
                    data = conn.recv(blocksize)
                except BlockingIOError:
                    time.sleep(0.05)
                    continue
                if not data:
                    break
                callback(data)
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def retrlines(self, cmd, callback=None):
        self.sendcmd('TYPE A')
        with self.transfercmd(cmd) as conn:
            result = b''
            while 1:
                try:
                    received = conn.recv(self.blocksize, socket.MSG_DONTWAIT)
                except BlockingIOError:
                    time.sleep(0.05)
                    continue
                if not received:
                    break
                result += received
            lines = result.decode(self.encoding).split('\n')
            for line in lines:
                if len(line) + 1 > self.maxline:
                    raise Error("got more than %d bytes" % self.maxline)
                if self.debugging > 2:
                    print('*retr*', repr(line + '\n'))
                if not line.strip():
                    break
                elif line[-1:] == '\r':
                    line = line[:-1]
                callback(line)
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def getline(self):
        start_time = time.time()
        while True:
            line = self.file.readline(self.maxline + 1)
            if line:
                break
            if time.time() - start_time > 3:
                raise EOFError
            time.sleep(0.05)
        if len(line) > self.maxline:
            raise Error("got more than %d bytes" % self.maxline)
        if self.debugging > 1:
            print('*get*', self.sanitize(line))
        if not line:
            raise EOFError
        if line[-2:] == CRLF:
            line = line[:-2]
        elif line[-1:] in CRLF:
            line = line[:-1]
        return line

    def ftp_login(self, host, port, username, password):  # 封装登录功能
        while 1:
            try:
                self.connect(host=host, port=int(port))
                self.login(user=username, passwd=password)
                return True  # 登录成功直接退出
            except (socket.error, socket.gaierror):
                logging.error('无法连接FTP主机%s\n%s' % (username, traceback.format_exc()))
            except ftplib.error_perm:
                logging.error('登录失败,请检查用户名和密码\n%s' % traceback.format_exc())
            except EOFError:
                self.exit()  # 登录失败断开连接
                logging.info('空网络IO导致登录失败,正在重试...')
                time.sleep(0.05)
                continue
            except:
                logging.error('登录失败,未知异常\n%s' % traceback.format_exc())
            self.exit()  # 登录失败断开连接
            return False

    def get_files(self, dirname, regex=''):  # 获取指定目录下文件,支持正则匹配
        files = []
        for file_path in self.nlst(dirname):
            try:
                self.cwd(file_path)  # 能进入的是目录
            except ftplib.error_perm:
                if not regex or re.match(r'%s' % regex, os.path.basename(file_path), re.S):  # 根据规则匹配文件
                    files.append(file_path)
        return files

    def isfile(self, file_path):  # 判断是不是文件
        try:
            self.cwd(file_path)
            return False  # 能进入的是目录
        except ftplib.error_perm as e:
            if 'Not a directory' in str(e):  # 提示不是一个目录
                return True
            else:
                return False  # 提示没有这个文件或目录

    def upload(self, upload_from, file_path):  # 上传文件
        try:
            with open(upload_from, 'rb') as file:
                self.storbinary('STOR %s' % file_path, file, self.blocksize)
        except:
            logging.error('文件[%s]上传失败\n%s' % (file_path, traceback.format_exc()))

    # 下载到mqt服务器
    def download(self, file_path, save_to):  # 下载文件
        try:
            with open(save_to, 'wb') as file:
                self.retrbinary('RETR %s' % file_path, file.write, self.blocksize)
            return True
        except:
            logging.error('文件[%s]下载失败\n%s' % (file_path, traceback.format_exc()))
            return False

    def exit(self):
        try:
            self.quit()
        except:
            pass


def upload_file(host, port, username, password, local_file_path, upload_to):
    try:
        ftp = FTP()
        ftp.set_debuglevel(0)  # 设置ftp日志级别
        if not ftp.ftp_login(host, port, username, password):
            return False
        ftp.upload(local_file_path, upload_to)
        ftp.exit()
        logging.info('文件[%s]上传成功' % local_file_path)
        return True
    except:
        ftp.exit()
        logging.error('文件[%s]上传失败\n%s' % (local_file_path, traceback.format_exc()))
        return False


def download_file(host, port, username, password, remote_file_path, save_to):
    try:
        ftp = FTP()
        ftp.set_debuglevel(0)  # 设置ftp日志级别
        if not ftp.ftp_login(host, port, username, password):
            return False
        if not ftp.isfile(remote_file_path):
            ftp.exit()
            logging.info('文件[%s]不存在' % remote_file_path)
            return False
        if not ftp.download(remote_file_path, save_to):
            ftp.exit()
            return False
        logging.info('文件[%s]下载成功' % remote_file_path)
        return True
    except:
        ftp.exit()
        logging.error('文件[%s]下载失败\n%s' % (remote_file_path, traceback.format_exc()))
        return False


def get_files(host, port, username, password, remote_path):
    try:
        ftp = FTP()
        ftp.set_debuglevel(0)  # 设置ftp日志级别
        if not ftp.ftp_login(host, port, username, password):
            return False
        # ftp.set_pasv(False)  # 切换主动和被动连接方式
        # 提取匹配的文件信息
        files = ftp.get_files(remote_path, r'^.*\.log$')  # 提取匹配的文件路径
        ftp.exit()
        return files
    except:
        ftp.exit()
        return False

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值