Как обрабатывать asyncore внутри класса в python, не блокируя ничего?
Мне нужно создать класс, который может принимать и хранить SMTP-сообщения, то есть E-Mails. Для этого я использую asyncore
в соответствии с примером, размещенным здесь. Однако asyncore.loop()
блокируется, поэтому я не могу ничего сделать в коде.
Итак, я подумал об использовании потоков. Вот пример-код, который показывает, что я имею в виду:
class MyServer(smtpd.SMTPServer):
# derive from the python server class
def process_message(..):
# overwrite a smtpd.SMTPServer method to be able to handle the received messages
...
self.list_emails.append(this_email)
def get_number_received_emails(self):
"""Return the current number of stored emails"""
return len(self.list_emails)
def start_receiving(self):
"""Start the actual server to listen on port 25"""
self.thread = threading.Thread(target=asyncore.loop)
self.thread.start()
def stop(self):
"""Stop listening now to port 25"""
# close the SMTPserver from itself
self.close()
self.thread.join()
Надеюсь, вы получите картину. Класс MyServer
должен иметь возможность запускать и останавливать прослушивание порта 25 неблокирующим способом, который может быть запрошен для сообщений во время прослушивания (или нет). Метод start
запускает прослушиватель asyncore.loop()
, который при приеме сообщения электронной почты добавляется к внутреннему списку. Аналогично, метод stop
должен иметь возможность остановить этот сервер, как предложено здесь.
Несмотря на то, что этот код не работает, как я ожидаю (asyncore, похоже, работает вечно, даже я называю метод stop
выше. error
Я поднимаю, задерживается в stop
, но не внутри target
, содержащая asyncore.loop()
), я не уверен, что мой подход к проблеме имеет смысл. Любые предложения по исправлению вышеуказанного кода или предложения о более надежной реализации (без использования стороннего программного обеспечения) приветствуются.
Ответы
Ответ 1
Предоставленное решение может быть не самым сложным решением, но оно работает разумно и проверено.
Прежде всего, вопрос с asyncore.loop()
заключается в том, что он блокируется до тех пор, пока все каналы asyncore
не будут закрыты, как указал пользователь Wessie в комментарии раньше. Ссылаясь на пример smtp, упомянутый ранее, выясняется, что smtpd.SMTPServer
наследует от asyncore.dispatcher
(как описано в smtpd документация), которая отвечает на вопрос о том, какой канал будет закрыт.
Следовательно, на исходный вопрос может быть дан ответ с помощью следующего обновленного примера кода:
class CustomSMTPServer(smtpd.SMTPServer):
# store the emails in any form inside the custom SMTP server
emails = []
# overwrite the method that is used to process the received
# emails, putting them into self.emails for example
def process_message(self, peer, mailfrom, rcpttos, data):
# email processing
class MyReceiver(object):
def start(self):
"""Start the listening service"""
# here I create an instance of the SMTP server, derived from asyncore.dispatcher
self.smtp = CustomSMTPServer(('0.0.0.0', 25), None)
# and here I also start the asyncore loop, listening for SMTP connection, within a thread
# timeout parameter is important, otherwise code will block 30 seconds after the smtp channel has been closed
self.thread = threading.Thread(target=asyncore.loop,kwargs = {'timeout':1} )
self.thread.start()
def stop(self):
"""Stop listening now to port 25"""
# close the SMTPserver to ensure no channels connect to asyncore
self.smtp.close()
# now it is save to wait for the thread to finish, i.e. for asyncore.loop() to exit
self.thread.join()
# now it finally it is possible to use an instance of this class to check for emails or whatever in a non-blocking way
def count(self):
"""Return the number of emails received"""
return len(self.smtp.emails)
def get(self):
"""Return all emails received so far"""
return self.smtp.emails
....
Итак, в конце концов, у меня есть метод start
и stop
для запуска и прекращения прослушивания на порту 25 в неблокирующей среде.
Ответ 2
Исходя из другого вопроса asyncore.loop не заканчивается, когда больше нет подключений
Я думаю, вы немного передумали, думая о потоке. Используя код из другого вопроса, вы можете запустить новый поток, который запускает asyncore.loop
с помощью следующего фрагмента кода:
import threading
loop_thread = threading.Thread(target=asyncore.loop, name="Asyncore Loop")
# If you want to make the thread a daemon
# loop_thread.daemon = True
loop_thread.start()
Это запустит его в новом потоке и будет продолжаться до тех пор, пока все каналы asyncore
не будут закрыты.
Ответ 3
Вместо этого вам следует использовать Twisted. http://twistedmatrix.com/trac/browser/trunk/doc/mail/examples/emailserver.tac демонстрирует, как настроить SMTP-сервер с настраиваемым подключением к доставке.