如何在python中处理类内的asyncore,而不阻塞任何内容?
我需要创建一个类来接收和存储SMTP消息,即电子邮件。为了做到这一点,根据发布的一个示例,我使用了如何在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
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()
的目标
函数中却无法捕获),我不确定我处理这个问题的方法是否明智。对于修复上述代码或提出更可靠的实现(无需使用第三方软件)的任何建议,我们将不胜感激相反,你应该考虑使用扭曲。演示如何使用可自定义的传递挂钩设置SMTP服务器。来自另一个问题
我认为你对线程的考虑有点过头了。使用另一个问题中的代码,您可以通过以下代码片段启动运行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()