imaplib2:imap.gmail.com处理程序BYE响应:系统错误

Seg*_*gev 6 python imap imaplib gmail-imap python-2.7

我正在翻新一个python脚本,检查IMAP是否有新电子邮件,如果有新邮件则发送推送通知.问题是每隔几个小时我就会崩溃.起初我无法真正理解发生了什么,但后来我发现M.debug = 4这给了我一个很好的输出,但我仍然无法理解导致问题的原因.我已经将我的脚本和调试输出从正常行为发布到崩溃,希望在python中有更好理解的人可以告诉我发生了什么以及如何解决它.

编辑:

我按照以下答案中的建议编辑了我的代码:

while True:
    try:
      [call function that does all your logic]
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")
Run Code Online (Sandbox Code Playgroud)

或这个:

    while True:
        try:
          [call function that does all your logic]
except (imaplib2.IMAP4.abort, imaplib2.IMAP4.error) as e: 
      print("Disconnected.  Trying again.")
Run Code Online (Sandbox Code Playgroud)

但是在一段不确定的时间之后我仍然会遇到崩溃,而且异常永远不会被抓住.print("Disconnected. Trying again.")永远不会执行.

代码:

    #!/usr/local/bin/python2.7
    print "Content-type: text/html\r\n\r\n";

    import socket, ssl, json, struct, re
    import imaplib2, time
    from threading import *

    # enter gmail login details here
    USER="username@gmail.com"
    PASSWORD="password"
    # enter device token here
    deviceToken = 'my device token x x x x x'
    deviceToken = deviceToken.replace(' ','').decode('hex')
    currentBadgeNum = -1

    def getUnseen():
        (resp, data) = M.status("INBOX", '(UNSEEN)')
        print data
        return int(re.findall("UNSEEN (\d)*\)", data[0])[0])    

    def sendPushNotification(badgeNum):
        global currentBadgeNum, deviceToken
        if badgeNum != currentBadgeNum:
            currentBadgeNum = badgeNum
            thePayLoad = {
                 'aps': {
                      'alert':'Hello world!',
                      'sound':'',
                      'badge': badgeNum,
                      },
                 'test_data': { 'foo': 'bar' },
                 }
            theCertfile = 'certif.pem'
            theHost = ('gateway.push.apple.com', 2195)

            data = json.dumps(thePayLoad)
            theFormat = '!BH32sH%ds' % len(data)
            theNotification = struct.pack(theFormat, 0, 32, deviceToken, len(data), data)

            ssl_sock = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM), certfile=theCertfile)
            ssl_sock.connect(theHost)
            ssl_sock.write(theNotification)
            ssl_sock.close()
            print "Sent Push alert."

    # This is the threading object that does all the waiting on 
    # the event
    class Idler(object):
        def __init__(self, conn):
            self.thread = Thread(target=self.idle)
            self.M = conn
            self.event = Event()

        def start(self):
            self.thread.start()

        def stop(self):
            # This is a neat trick to make thread end. Took me a 
            # while to figure that one out!
            self.event.set()

        def join(self):
            self.thread.join()

        def idle(self):
            # Starting an unending loop here
            while True:
                # This is part of the trick to make the loop stop 
                # when the stop() command is given
                if self.event.isSet():
                    return
                self.needsync = False
                # A callback method that gets called when a new 
                # email arrives. Very basic, but that's good.
                def callback(args):
                    if not self.event.isSet():
                        self.needsync = True
                        self.event.set()
                # Do the actual idle call. This returns immediately, 
                # since it's asynchronous.
                self.M.idle(callback=callback)
                # This waits until the event is set. The event is 
                # set by the callback, when the server 'answers' 
                # the idle call and the callback function gets 
                # called.
                self.event.wait()
                # Because the function sets the needsync variable,
                # this helps escape the loop without doing 
                # anything if the stop() is called. Kinda neat 
                # solution.
                if self.needsync:
                    self.event.clear()
                    self.dosync()

        # The method that gets called when a new email arrives. 
        # Replace it with something better.
        def dosync(self):
            print "Got an event!"
            numUnseen = getUnseen()
            sendPushNotification(numUnseen)

    # Had to do this stuff in a try-finally, since some testing 
    # went a little wrong.....
while True:
    try:
        # Set the following two lines to your creds and server
        M = imaplib2.IMAP4_SSL("imap.gmail.com")
        M.login(USER, PASSWORD)
        M.debug = 4
        # We need to get out of the AUTH state, so we just select 
        # the INBOX.
        M.select("INBOX")
        numUnseen = getUnseen()
        sendPushNotification(numUnseen)

        typ, data = M.fetch(1, '(RFC822)')
        raw_email = data[0][1]

        import email
        email_message = email.message_from_string(raw_email)
        print email_message['Subject']

        #print M.status("INBOX", '(UNSEEN)')
        # Start the Idler thread
        idler = Idler(M)
        idler.start()


        # Sleep forever, one minute at a time
        while True:
            time.sleep(60)
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")   
    finally:
        # Clean up.
        #idler.stop() #Commented out to see the real error
        #idler.join() #Commented out to see the real error
        #M.close()    #Commented out to see the real error
        # This is important!
        M.logout()
Run Code Online (Sandbox Code Playgroud)

...... ......

  43:54.43 imap.gmail.com handler _request_pop(continuation, (True, 'idling')) = CHPJ127
  43:54.43 imap.gmail.com handler None:CHPJ127.ready.set
  43:54.43 Thread-4 continuation => True, idling
  43:54.43 Thread-4 server IDLE started, timeout in 1740.00 secs
  43:54.43 Thread-4 state_change_pending.release
  57:13.60 imap.gmail.com reader < * BYE System error\r\n
  57:13.63 imap.gmail.com handler server IDLE finished
  57:13.63 imap.gmail.com handler BYE response: System error
  57:13.63 imap.gmail.com writer > DONE\r\n
  **57:13.63 imap.gmail.com handler terminating: 'connection terminated'**
  57:13.63 imap.gmail.com writer finished
  57:13.63 imap.gmail.com handler last 20 log messages:
  51:49.77 Thread-4 [sync] IDLE ()
  20:50.18 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  20:50.51 Thread-4 [sync] IDLE ()
  49:50.79 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  49:51.02 Thread-4 [sync] IDLE ()
  18:51.33 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  18:51.49 Thread-4 [sync] IDLE ()
  47:51.80 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  47:51.96 Thread-4 [sync] IDLE ()
  16:52.26 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  16:52.63 Thread-4 [sync] IDLE ()
  45:53.08 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  45:53.24 Thread-4 [sync] IDLE ()
  14:53.54 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  14:53.69 Thread-4 [sync] IDLE ()
  43:53.96 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  43:54.17 Thread-4 [sync] IDLE ()
  57:13.63 imap.gmail.com handler BYE response: System error
  57:13.63 imap.gmail.com handler terminating: 'connection terminated'
  57:13.63 imap.gmail.com writer finished
Got an event!
  57:13.63 imap.gmail.com handler state_change_free.set
  57:13.63 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  57:13.63 imap.gmail.com handler finished
  57:13.63 Thread-4 state_change_pending.acquire
  57:13.63 Thread-4 state_change_pending.release
Exception in thread Thread-4:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 551, in __bootstrap_inner
    self.run()
  File "/usr/local/lib/python2.7/threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "shaserver.py", line 111, in idle
    self.dosync()
  File "shaserver.py", line 117, in dosync
    numUnseen = getUnseen()
  File "shaserver.py", line 35, in getUnseen
    (resp, data) = M.status("INBOX", '(UNSEEN)')
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1121, in status
    return self._simple_command(name, mailbox, names, **kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1607, in _simple_command
    return self._command_complete(self._command(name, *args), kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1295, in _command
    self._check_bye()
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1229, in _check_bye
    raise self.abort(bye[-1])
abort: System error

  57:13.70 imap.gmail.com reader finished
Run Code Online (Sandbox Code Playgroud)

...... ......

有时我得到这个:

03:09.29 Thread-4 [sync] IDLE ()
  05:53.25 imap.gmail.com reader socket error: <type 'exceptions.IOError'> - Error Hang up
  05:53.25 imap.gmail.com reader finished
  05:53.26 imap.gmail.com handler terminating: "socket error: <type 'exceptions.IOError'> - Error Hang up"
  05:53.26 imap.gmail.com handler last 20 log messages:
  07:07.66 Thread-4 [sync] IDLE ()
  36:07.78 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  36:07.83 Thread-4 [async] SEARCH ('ALL',)
  36:07.88 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  36:08.09 Thread-4 [sync] IDLE ()
  05:08.19 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  05:08.25 Thread-4 [async] SEARCH ('ALL',)
  05:08.42 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  05:08.48 Thread-4 [sync] IDLE ()
  34:08.58 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  34:08.68 Thread-4 [async] SEARCH ('ALL',)
  34:08.79 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  34:08.94 Thread-4 [sync] IDLE ()
  03:09.05 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  03:09.16 Thread-4 [async] SEARCH ('ALL',)
  03:09.21 Thread-4 [async] FETCH ('1', '(RFC822.HEADER)')
  03:09.29 Thread-4 [sync] IDLE ()
  05:53.25 imap.gmail.com reader socket error: <type 'exceptions.IOError'> - Error Hang up
  05:53.25 imap.gmail.com reader finished
  05:53.26 imap.gmail.com handler terminating: "socket error: <type 'exceptions.IOError'> - Error Hang up"
  05:53.26 imap.gmail.com writer finished
Got an event!
  05:53.26 Thread-4 [async] STATUS ('INBOX', '(UNSEEN)')
  05:53.26 Thread-4 state_change_pending.acquire
  05:53.26 Thread-4 server IDLE finished
  05:53.26 Thread-4 state_change_pending.release
  05:53.26 Thread-4 _get_untagged_response(READ-ONLY) => ['']
  05:53.26 imap.gmail.com handler state_change_free.set
  05:53.26 imap.gmail.com handler finished
Exception in thread Thread-4:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 551, in __bootstrap_inner
    self.run()
  File "/usr/local/lib/python2.7/threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "shaserver.py", line 229, in idle
    self.dosync()
  File "shaserver.py", line 235, in dosync
    numUnseen = getUnseen()
  File "shaserver.py", line 150, in getUnseen
    (resp, data) = M.status("INBOX", '(UNSEEN)')
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1121, in status
    return self._simple_command(name, mailbox, names, **kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1607, in _simple_command
    return self._command_complete(self._command(name, *args), kw)
  File "/home/boombe/lib/python2.7/imaplib2/imaplib2.py", line 1305, in _command
    raise self.abort('connection closed')
abort: connection closed
Run Code Online (Sandbox Code Playgroud)

Max*_*Max -1

从您的跟踪来看,远程端似乎向您发送了一个未经请求的 BYE 命令,因为它出于某种原因想要关闭连接。

您可能需要使脚本更加健壮,以处理连接失败和 BYE 命令。

例如,您应该将顶层更改为循环:

  while True:
    try:
      [call function that does all your logic]
    except imaplib2.IMAP4.abort:
      print("Disconnected.  Trying again.")
Run Code Online (Sandbox Code Playgroud)

另外,您需要更新您的回调()以查看它的参数。如果IDLE返回错误,因为你是异步使用的,它会向callback()报告错误。您需要在那里处理异常。(请注意,它不会在回调中引发错误,只会在参数中返回错误。

来自 imaplib2s 文档:

If 'callback' is provided then the command is asynchronous, so after
the command is queued for transmission, the call returns immediately
with the tuple (None, None).
The result will be posted by invoking "callback" with one arg, a tuple:
callback((result, cb_arg, None))
or, if there was a problem:
callback((None, cb_arg, (exception class, reason)))
Run Code Online (Sandbox Code Playgroud)

这意味着您的回调需要查看它的参数:

            def callback(args):
                result, arg, exc = args
                if result is None:
                    print("There was an error during IDLE:", str(exc))
                    self.error = exc
                    self.event.set()
                else:
                    self.needsync = True
                    self.event.set()
Run Code Online (Sandbox Code Playgroud)

现在您可以检查您的 Idler 线程是否有错误。然后,在您每次坐和睡觉 60 秒的主线程中,您可以设置某种标志来指示您是否已断开连接。