Создание функции проверки ошибок в скрипте Python FTP

Я работаю над сценарием, который соединяет несколько "клиентских" компьютеров в "серверный" компьютер, который затем использует эти клиенты для обработки нескольких файлов, используя FTP (pyftplib и pyftpdlib) для передачи файлов и результатов.

Сценарий работает, создав на сервере 3 папки: Файлы, Обработка и Результаты. Затем клиенты подключаются к серверу по FTP, получают доступ к папке "Файлы", получают файл для обработки и затем передают его в папку "Обработка" во время его обработки. Затем, когда он заканчивает обработку, клиент удаляет файл из папки обработки и копирует результаты в папку "Результаты".

Это работает правильно, как на стороне сервера, так и на стороне клиента. У меня проблема в том, что, если один из клиентов отключается на полпути без генерации ошибки (ПК отключен, отключение питания), сервер будет угрожать этому, как если бы клиент все еще обрабатывал файл, и файл останется в папка "Обработка". Что я хочу, так это функция проверки ошибок, при которой файл в папке "Обработка" возвращается в папку "Файлы".

Вот код FTP сервера

def main():
    authorizer = DummyAuthorizer()
    authorizer.add_user('client', 'password', '.', perm='elradfmwM')
    authorizer.add_anonymous(os.getcwd())

    handler = FTPHandler
    handler.authorizer = authorizer
    handler.banner = "FTP Server."
    address = ('', port)
    server = FTPServer(address, handler)
    server.max_cons = 256
    server.max_cons_per_ip = 50
    server.serve_forever()


if __name__ == '__main__':
    main()

А вот клиентский FTP-код:

while True:
    ftp = ftplib.FTP()
    ftp.connect(arguments.host_ip, arguments.host_port)
    ftp.login("client", "password")
    print ftp.getwelcome()
    ftp.retrlines('LIST')
    ftp.retrbinary('RETR Output.txt', open('Output.txt', 'wb').write)
    ftp.retrbinary('RETR dicionario.json', open('dicionario.json', 'wb').write)
    with open('dicionario.json') as json_file:
        json_data = json.load(json_file)
    receptor_file = json_data['--receptor']
    print 'Retrieving receptor file ' + receptor_file
    ftp.retrbinary('RETR ' + receptor_file, open(receptor_file, 'wb').write)
    ftp.cwd('Files')
    ftp.retrlines('LIST')
    filename = ftp.nlst()[0]
    print 'Getting ' + filename
    ftp.retrbinary('RETR ' + filename, open(filename, 'wb').write)
    with open("Output.txt", "a") as input_file:
        input_file.write('ligand = %s' %filename)
        input_file.close()
    ftp.delete(filename)
    ftp.cwd('../Processing')
    ftp.storbinary('STOR ' + filename, open(filename, 'rb'))
    ftp.quit()

    print "Processing"
    return_code = subprocess.call(calls the program for processing files)
    if return_code == 0:
        print """Done!"""
        ftp.connect(arguments.host_ip, arguments.host_port)
        ftp.login("client", "password")
        ftp.cwd('Results')
        ftp.storbinary('STOR ' + os.path.splitext(filename)[0] + '_out.pdbqt', open (os.path.splitext(filename)[0] + '_out.pdbqt'))
        ftp.cwd('../Processing')
        ftp.delete(filename)


        ftp.quit()
    else:
        print """Something is technically wrong..."""
        ftp.connect(arguments.host_ip, arguments.host_port)
        ftp.login("client", "password")
        ftp.cwd('Files')
        ftp.storbinary('STOR ' + filename, open(filename, 'rb'))
        ftp.cwd('../Processing')
        ftp.delete(filename)
        ftp.quit()

Спасибо за помощь!

1 ответ

Решение

Итак, после полугода возиться с этим кодом, я, наконец, заставил его работать, когда клиент отменяет соединение

Сначала я должен был сделать так, чтобы сервер идентифицировал каждого клиента. Вместо того, чтобы регистрировать их только с одним пользователем, я создал конкретных пользователей для каждого соединения с двумя различными функциями:

def handler_generation(size=9, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for i in range (size))

Это генерирует 9-символьный логин и пароль

Затем я создал собственный обработчик в pyftpdlib и использовал функцию on_login:

class MyHandler(FTPHandler):
    def on_login(self, username):
    if username == "client":
        user_login = handler_generation()
        user_password = handler_generation()
        global authorizer
        authorizer.add_user(user_login, user_password, '.', perm='elradfmwM')
        credentials = open("Credentials.txt",'w')
        credentials.write(user_login)
        credentials.write("\n")
        credentials.write(user_password)
        credentials.close()
    else:
        pass

Таким образом, когда клиент соединяется с "клиентским" логином, сервер генерирует 9-символьный логин и пароль и отправляет его клиенту в файле "Credentials.txt". На стороне клиента это будет сделано так:

ftp.login("client", "password")
    ftp.retrbinary('RETR Credentials.txt', open('Credentials.txt', 'wb').write)
    ftp.quit()
    with open('Credentials.txt') as credential_file:
        lines = credential_file.readlines()
        credential_login = lines[0].split("\n")[0]
        credential_password = lines[1].split("\n")[0]
    ftp.connect(arguments.host_ip, arguments.host_port)
    ftp.login(credential_login, credential_password)

Так что теперь все клиенты подключаются со своим собственным конкретным логином. На стороне клиента я сделал так, чтобы для каждой задачи, которая была выполнена, клиент отправлял файл с именем для своего конкретного логина. Я также заставил клиента добавить свое имя пользователя в файл, который он обрабатывал, чтобы сервер мог легко найти файл:

ftp.rename(filename, credential_login + filename)

Затем я использовал другую функцию класса обработчика, on_disconnect:

def on_disconnect(self):
    if self.username == "client":
        pass
    else:
        if os.path.isfile(self.username):
            pass
        else:
            for fname in os.listdir("Processing"):
                if fname.startswith(self.username):
                    shutil.move("Processing/" + fname, "Files")
                    os.rename("Files/" + fname, "Files/" + fname[9::])

    print self.remote_ip, self.remote_port,self.username, "disconnected"
    pass

Теперь, когда клиент отключается, сервер ищет папку, чтобы проверить, отправил ли клиент файл обработчика. Если его там нет, сервер переместит файл в папку "Файлы", в которой находятся файлы, которые еще не обработаны.

Чтобы неудачный клиент отключился от сервера без отправки команды выхода, я использовал функцию тайм-аута из pyftpdlib. Чтобы убедиться, что активный клиент случайно не истечет, я реализовал поток в клиенте, который будет делать что-то с сервером каждые N секунд:

class perpetualTimer():

def __init__(self,t,hFunction):
    self.t=t
    self.hFunction = hFunction
    self.thread = Timer(self.t,self.handle_function)

def handle_function(self):
    self.hFunction()
    self.thread = Timer(self.t,self.handle_function)
    self.thread.start()

def start(self):
    self.thread.start()

def cancel(self):
    self.thread.cancel()

def NotIdle():
    Doing something here

t = perpetualTimer(10, NotIdle)
t.start()

(этот конкретный код я скопировал прямо у кого-то здесь)

И вуаля. Теперь и сервер, и клиент работают и имеют собственную функцию проверки ошибок.

Я помещаю этот ответ здесь, если кто-то сталкивается с подобной проблемой.

Спасибо!

Другие вопросы по тегам