Как обрабатывать 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 не будут закрыты.