Создание функции проверки ошибок в скрипте 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()
(этот конкретный код я скопировал прямо у кого-то здесь)
И вуаля. Теперь и сервер, и клиент работают и имеют собственную функцию проверки ошибок.
Я помещаю этот ответ здесь, если кто-то сталкивается с подобной проблемой.
Спасибо!