2013-01-23 7 views
10

Devo creare una classe in grado di ricevere e archiviare messaggi SMTP, ad esempio E-mail. Per fare ciò, sto usando asyncore secondo un esempio pubblicato here. Tuttavia, asyncore.loop() sta bloccando, quindi non posso fare nient'altro nel codice.Come gestire asyncore all'interno di una classe in python, senza bloccare nulla?

Quindi ho pensato di utilizzare i thread. Ecco un esempio di codice che mostra quello che ho in mente:

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() 

Spero che tu abbia la foto. La classe MyServer dovrebbe essere in grado di avviare e interrompere l'ascolto della porta 25 in modo non bloccante, in grado di essere interrogato per i messaggi durante l'ascolto (o meno). Il metodo start avvia il listener , che, quando si verifica una ricezione di un'e-mail, si aggiunge a un elenco interno. Simile, il metodo stop dovrebbe essere in grado di arrestare questo server, come suggerito here.

Nonostante il fatto che questo codice non funziona come mi aspetto di (asyncore sembra funzionare sempre, anche io chiamo il metodo di cui sopra stop. La error Alzo catched all'interno stop, ma non all'interno della funzione target contenente asyncore.loop()), Non sono sicuro che il mio approccio al problema sia sensato. Sono graditi suggerimenti per la correzione del codice di cui sopra o per la proposta di un'implementazione più solida (senza utilizzando software di terze parti).

+0

Mi sento un po 'di confusione lì. Qual è il problema con il blocco di 'asyncore.loop()? Capisci perché chiami la funzione 'loop' e cosa fa? – mmgp

+0

@mmgp: Il problema con 'asyncore.loop()' è che sta bloccando.Voglio essere in grado di utilizzare la classe in qualsiasi momento all'interno di un altro codice. Dall'altro lato, non sono un esperto di 'asyncore.loop()', ma AFAIK gestisce internamente il 'select.select', che sta cercando ad es. per i messaggi SMTP in arrivo sulla porta 25, in questo caso. – Alex

+0

hai usato i toolkit della GUI? Fondamentalmente tutti sono basati su loop di eventi. Devi organizzare cose tali da produrre eventi che devono essere gestiti dal "ciclo degli eventi". La confusione che ho menzionato è perché sembri inconsapevole di come usare un ciclo di eventi, è questo il caso? – mmgp

risposta

12

La soluzione fornita potrebbe non essere la soluzione più sofisticata, ma funziona in modo ragionevole ed è stata testata.

Innanzitutto, la questione con asyncore.loop() è che blocca finché tutti asyncore canali sono chiusi, come utente Wessie sottolineato in un commento prima. Facendo riferimento allo smtp example menzionato in precedenza, risulta che smtpd.SMTPServer eredita da asyncore.dispatcher (come descritto nello smtpd documentation), che risponde alla domanda su quale canale deve essere chiuso.

Pertanto, la domanda iniziale può essere risolta con il seguente codice di esempio aggiornato:

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 
    .... 

Così, alla fine, ho una e un metodo startstop per avviare e interrompere l'ascolto sulla porta 25 all'interno di un non -blocco dell'ambiente.

+1

C'è un modo per fare lo stesso senza usare i thread? – Vikram

2

Si consiglia di utilizzare Twisted, invece. http://twistedmatrix.com/trac/browser/trunk/doc/mail/examples/emailserver.tac mostra come configurare un server SMTP con un hook personalizzabile alla consegna.

+2

scusa, non voglio usare qualcosa in più. Avrei dovuto essere più esplicito – Alex

+1

Non è probabile che l'uso di qualcosa di "extra" possa danneggiare il tuo progetto. È molto più probabile che lo renderà molto meglio. Stai già usando Python, anche questo è "extra". Va bene, le persone possono installare software. Puoi persino creare pacchetti super slick per installarlo per loro. Limitando te stesso a ciò che è nella libreria standard di Python, stai tagliando una quantità enorme di funzionalità incredibilmente utili, Twisted ne è solo un piccolo esempio. Alla fine, stai sprecando il tuo tempo (e il tempo delle persone che scelgono di rispondere alle tue domande) e ferendo il tuo progetto. –

4

Venendo da altra domanda asyncore.loop doesn't terminate when there are no more connections

penso che tu sia un po 'più di pensare la filettatura. Utilizzando il codice dalla altra domanda, è possibile avviare un nuovo thread che gestisce la asyncore.loop dal seguente frammento di codice:

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() 

Questo verrà eseguito in un nuovo thread e andrà avanti fino a che tutti asyncore canali sono chiusi.

+1

Ok, pensavo che il mio approccio fosse un po 'troppo grande. Ma per seguire, per terminare questo thread ho bisogno di chiudere tutti i canali 'asyncore'. Come farlo? Come posso 'smettere' di asyncore? Come posso interrompere questo thread? (Come parte della mia domanda reale) – Alex

+0

@Alex La chiamata a 'asyncore.loop' si sbloccherà quando tutti i canali' asyncore' saranno chiusi. Canali in questo contesto che si riferiscono a qualsiasi sottoclasse di 'asyncore.dispatcher' o' asynchat.async_chat'. Nel tuo caso devi semplicemente chiamare 'server.close()' nel momento in cui vuoi uscire. – Wessie

+0

L'ho provato nel codice di esempio precedente e ho inserito quanto segue nel metodo 'stop':' self.close(); self._thread.join() '. Non funziona. Si blocca su 'thread.join' che implica che vi sia un altro canale (?) Aperto? Quale? Come trovarli? – Alex

Problemi correlati