如何在python中处理类内的asyncore,而不阻塞任何内容?

如何在python中处理类内的asyncore,而不阻塞任何内容?,python,multithreading,smtp,asyncore,Python,Multithreading,Smtp,Asyncore,我需要创建一个类来接收和存储SMTP消息,即电子邮件。为了做到这一点,根据发布的一个示例,我使用了asyncore。但是,asyncore.loop()正在阻塞,因此我无法在代码中执行任何其他操作 所以我想到了使用线程。下面是一个示例代码,显示了我的想法: class MyServer(smtpd.SMTPServer): # derive from the python server class def process_message(..): # overw

我需要创建一个类来接收和存储SMTP消息,即电子邮件。为了做到这一点,根据发布的一个示例,我使用了
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
方法。我引发的
错误
stop
中被捕获,但在包含
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
通道关闭。

提供的解决方案可能不是最复杂的解决方案,但它工作合理,并且已经过测试

首先,
asyncore.loop()
的问题是它会阻塞,直到所有
asyncore
频道关闭,正如用户Wessie在之前的评论中指出的那样。参考前面提到的,
smtpd.SMTPServer
继承自
asyncore.dispatcher
(如中所述),它回答了关闭哪个通道的问题

因此,可以使用以下更新的示例代码回答原始问题:

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

Alex的答案是最好的,但对于我的用例来说是不完整的。我想将SMTP作为单元测试的一部分进行测试,这意味着在我的测试对象中构建假SMTP服务器,而服务器不会终止异步IO线程,因此我必须添加一行代码将其设置为守护进程线程,以便在不阻塞等待异步IO线程加入的情况下完成单元测试的其余部分。我还添加了所有电子邮件数据的完整日志记录,以便我可以断言通过SMTP发送的任何内容

这是我的假SMTP类:

class TestingSMTP(smtpd.SMTPServer):
    def __init__(self, *args, **kwargs):
        super(TestingSMTP, self).__init__(*args, **kwargs)
        self.emails = []

    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
        msg = {'peer': peer,
               'mailfrom': mailfrom,
               'rcpttos': rcpttos,
               'data': data}
        msg.update(kwargs)
        self.emails.append(msg)


class TestingSMTP_Server(object):

    def __init__(self):
        self.smtp = TestingSMTP(('0.0.0.0', 25), None)
        self.thread = threading.Thread()

    def start(self):
        self.thread = threading.Thread(target=asyncore.loop, kwargs={'timeout': 1})
        self.thread.daemon = True
        self.thread.start()

    def stop(self):
        self.smtp.close()
        self.thread.join()

    def count(self):
        return len(self.smtp.emails)

    def get(self):
        return self.smtp.emails
下面是unittest类如何调用它:

smtp_server = TestingSMTP_Server()
smtp_server.start()

# send some emails

assertTrue(smtp_server.count() == 1) # or however many you intended to send
assertEqual(self.smtp_server.get()[0]['mailfrom'], 'first@fromaddress.com')

# stop it when done testing
smtp_server.stop()

如果其他人需要更多的充实,下面是我最后使用的。这将smtpd用于电子邮件服务器,smtpblib用于电子邮件客户端,Flask用作http服务器[]:

app.py

smtp_server.py

smtp_client.py


然后使用
python app.py启动服务器,并在另一个请求中使用
curl localhost:5000/send\u email
模拟对
/send\u email
的请求。请注意,要真正发送电子邮件(或短信),您需要跳过此处详述的其他障碍:。

对不起,我不想使用额外的功能。我应该说得更清楚一些。使用“额外”的东西实际上不可能伤害你的项目。这更有可能使它变得更好。您已经在使用Python了,这也是“额外的”。没关系,人们可以安装软件。你甚至可以制作超级光滑的软件包来为他们安装它。通过将自己局限于Python标准库中的内容,您切断了大量非常有用的功能,而Twisted只是其中的一个小例子。最后,你在浪费你自己的时间(以及选择回答你问题的人的时间),并且损害了你的项目。我感到有些困惑。
asyncore.loop()
阻塞有什么问题?你明白为什么要调用
循环
函数以及它的作用吗?@mmgp:
asyncore.loop()
的问题是它被阻塞了。我希望能够在其他代码中随时使用该类。另一方面,我不是
asyncore.loop()
方面的专家,但我认为它可以处理内部
select.select
,在这种情况下,它可以在端口25上查找传入的SMTP消息。您使用过GUI工具包吗?基本上,它们都基于事件循环。您必须安排一些事情,以便它们生成要由“事件循环”处理的事件。我提到的混乱是因为您似乎不知道如何使用事件循环,是吗?@mmgp是的,我完全不知道如何使用事件循环。因此,我问这个问题是为了得到一个有意义和有帮助的答案,以防有人知道事件循环并能为我的问题提供解决方案。@Alex-不幸的是,你似乎也遇到了与我相同的问题,人们认为,因为事情对他们来说是单向的,所以对每个人来说都应该是单向的(即“只使用事件循环”或“使用额外的东西不会有什么坏处”)…好吧,我假设我的方法有点太大了。但为了继续,为了结束这个线程,我需要关闭所有
asyncore
通道。如何做到这一点?我如何“停止”asyncore?我如何停止这个线程?(作为我实际问题的一部分)@Alex调用
asyncore.loop<
from flask import Flask, render_template
from smtp_client import send_email
from smtp_server import SMTPServer

app = Flask(__name__)

@app.route('/send_email')
def email():
  server = SMTPServer()
  server.start()
  try:
    send_email()
  finally:
    server.stop()
  return 'OK'

@app.route('/')
def index():
  return 'Woohoo'

if __name__ == '__main__':
  app.run(debug=True, host='0.0.0.0')
# smtp_server.py
import smtpd
import asyncore
import threading

class CustomSMTPServer(smtpd.SMTPServer):
  def process_message(self, peer, mailfrom, rcpttos, data):
    print('Receiving message from:', peer)
    print('Message addressed from:', mailfrom)
    print('Message addressed to:', rcpttos)
    print('Message length:', len(data))
    return

class SMTPServer():
  def __init__(self):
    self.port = 1025

  def start(self):
    '''Start listening on self.port'''
    # create an instance of the SMTP server, derived from  asyncore.dispatcher
    self.smtp = CustomSMTPServer(('0.0.0.0', self.port), None)
    # 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
    kwargs = {'timeout':1, 'use_poll': True}
    self.thread = threading.Thread(target=asyncore.loop, kwargs=kwargs)
    self.thread.start()

  def stop(self):
    '''Stop listening to self.port'''
    # close the SMTPserver to ensure no channels connect to asyncore
    self.smtp.close()
    # now it is safe to wait for asyncore.loop() to exit
    self.thread.join()

  # check for emails in a non-blocking way
  def get(self):
    '''Return all emails received so far'''
    return self.smtp.emails

if __name__ == '__main__':
  server = CustomSMTPServer(('0.0.0.0', 1025), None)
  asyncore.loop()
import smtplib
import email.utils
from email.mime.text import MIMEText

def send_email():
  sender='author@example.com'
  recipient='6142546977@tmomail.net'

  msg = MIMEText('This is the body of the message.')
  msg['To'] = email.utils.formataddr(('Recipient', recipient))
  msg['From'] = email.utils.formataddr(('Author', 'author@example.com'))
  msg['Subject'] = 'Simple test message'

  client = smtplib.SMTP('127.0.0.1', 1025)
  client.set_debuglevel(True) # show communication with the server
  try:
    client.sendmail('author@example.com', [recipient], msg.as_string())
  finally:
    client.quit()