java JavaMail:保持 IMAPFolder.idle() 活着

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4155412/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 05:05:14  来源:igfitidea点击:

JavaMail: Keeping IMAPFolder.idle() alive

javaimapjavamail

提问by Anders

I am making a program that needs to monitor a Gmail account for new messages, and in order to get them ASAP I am using JavaMail's idle feature. Here is a code snippet from the thread I am using to call folder.idle():

我正在制作一个需要监控新邮件的 Gmail 帐户的程序,为了尽快获得它们,我正在使用 JavaMail 的空闲功能。这是我用来调用 folder.idle() 的线程中的代码片段:

//Run method that waits for idle input. If an exception occurs, end the thread's life.
public void run() {

    IMAPFolder folder = null;

            try {
                folder = getFolder();
                while(true)
                {
                  //If connection has been lost, attempt to restore it
                  if (!folder.isOpen())
                      folder = getFolder();
                  //Wait until something happens in inbox
                  folder.idle(true);
                  //Notify controller of event
                  cont.inboxEventOccured();
                }
            }
            catch (Exception ex) {
                ex.printStackTrace();
            }
             System.out.println("MailIdleWaiter thread ending.");
}

The getFolder() method basically opens the connection to the IMAP server and opens the inbox.

getFolder() 方法基本上打开与 IMAP 服务器的连接并打开收件箱。

This works for a while, but after 10 minutes or so it stops getting updates (no exception is thrown).

这工作了一段时间,但在 10 分钟左右后它停止获取更新(没有抛出异常)。

I am looking for suggestions to keep the connection alive. Do I need a second thread whose only role is to sleep and renew the idle() thread every 10 minutes or is there an easier/better way?

我正在寻找保持连接活跃的建议。我是否需要第二个线程,其唯一作用是每 10 分钟睡眠和更新一次 idle() 线程,还是有更简单/更好的方法?

Thanks in advance.

提前致谢。

回答by Johnco

A common mistake is to assume an IDLE command will keep posting updates indefinitely. However, the RFC 2177, that defines the IDLE extension states:

一个常见的错误是假设 IDLE 命令会无限期地发布更新。但是,定义 IDLE 扩展的RFC 2177指出:

The server MAY consider a client inactive if it has an IDLE command running, and if such a server has an inactivity timeout it MAY log the client off implicitly at the end of its timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and re-issue it at least every 29 minutes to avoid being logged off. This still allows a client to receive immediate mailbox updates even though it need only "poll" at half hour intervals.

服务器可以认为客户端处于非活动状态,如果它有一个空闲命令正在运行,并且如果这样的服务器具有非活动超时,它可以在其超时周期结束时隐式地注销客户端。因此,建议使用 IDLE 的客户端终止 IDLE 并至少每 29 分钟重新发出一次,以避免被注销。这仍然允许客户端接收即时邮箱更新,即使它只需要每隔半小时“轮询”一次。

GMail in particular, has a much lower timeout, as you say, around 10 minutes.

特别是 GMail,超时时间要短得多,如您所说,大约为 10 分钟。

We simply need to reissue the IDLE command every 9 minutes or so for it to work. The javax.mailAPIs have no way to set a timeout for the IDLE command, so you will need a second thread to move around this.

我们只需要每 9 分钟左右重新发出一次 IDLE 命令即可使其工作。该javax.mailAPI已经没有办法设置超时为IDLE命令,所以你需要第二个线程移动解决这个问题。

A first approach would be to have the second thread interrupt the first one, handling the exception and ignoring it. This however, would allow for no clean way to shutdown the thread, so I won't recomend it. A much cleaner way is to have the second thread issue a NOOP command to the server. This does nothing at all, but is enough to have IDLE abort and be reissued.

第一种方法是让第二个线程中断第一个线程,处理异常并忽略它。然而,这将不允许以干净的方式关闭线程,所以我不会推荐它。更简洁的方法是让第二个线程向服务器发出 NOOP 命令。这根本没有任何作用,但足以让 IDLE 中止并重新发布。

I here provide some code to do this:

我在这里提供了一些代码来做到这一点:

public void startListening(IMAPFolder imapFolder) {
    // We need to create a new thread to keep alive the connection
    Thread t = new Thread(
        new KeepAliveRunnable(imapFolder), "IdleConnectionKeepAlive"
    );

    t.start();

    while (!Thread.interrupted()) {
        LOGGER.debug("Starting IDLE");
        try {
            imapFolder.idle();
        } catch (MessagingException e) {
            LOGGER.warn("Messaging exception during IDLE", e);
            throw new RuntimeException(e);
        }
    }

    // Shutdown keep alive thread
    if (t.isAlive()) {
        t.interrupt();
    }
}

/**
 * Runnable used to keep alive the connection to the IMAP server
 * 
 * @author Juan Martín Sotuyo Dodero <[email protected]>
 */
private static class KeepAliveRunnable implements Runnable {

    private static final long KEEP_ALIVE_FREQ = 300000; // 5 minutes

    private IMAPFolder folder;

    public KeepAliveRunnable(IMAPFolder folder) {
        this.folder = folder;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                Thread.sleep(KEEP_ALIVE_FREQ);

                // Perform a NOOP just to keep alive the connection
                LOGGER.debug("Performing a NOOP to keep alvie the connection");
                folder.doCommand(new IMAPFolder.ProtocolCommand() {
                    public Object doCommand(IMAPProtocol p)
                            throws ProtocolException {
                        p.simpleCommand("NOOP", null);
                        return null;
                    }
                });
            } catch (InterruptedException e) {
                // Ignore, just aborting the thread...
            } catch (MessagingException e) {
                // Shouldn't really happen...
                LOGGER.warn("Unexpected exception while keeping alive the IDLE connection", e);
            }
        }
    }
}

回答by stefan.at.wpf

Actually the Java Mail samplesinclude an IMAP IDLE example, which is as follows. Besides that, the IdleManager classmight be interesting.

实际上,Java Mail 示例包含一个 IMAP IDLE 示例,如下所示。除此之外,IdleManager 类可能很有趣。

/*
 * Copyright (c) 1996-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.*;
import java.io.*;
import javax.mail.*;
import javax.mail.event.*;
import javax.activation.*;

import com.sun.mail.imap.*;

/* Monitors given mailbox for new mail */

public class monitor {

    public static void main(String argv[]) {
    if (argv.length != 5) {
        System.out.println(
        "Usage: monitor <host> <user> <password> <mbox> <freq>");
        System.exit(1);
    }
    System.out.println("\nTesting monitor\n");

    try {
        Properties props = System.getProperties();

        // Get a Session object
        Session session = Session.getInstance(props, null);
        // session.setDebug(true);

        // Get a Store object
        Store store = session.getStore("imap");

        // Connect
        store.connect(argv[0], argv[1], argv[2]);

        // Open a Folder
        Folder folder = store.getFolder(argv[3]);
        if (folder == null || !folder.exists()) {
        System.out.println("Invalid folder");
        System.exit(1);
        }

        folder.open(Folder.READ_WRITE);

        // Add messageCountListener to listen for new messages
        folder.addMessageCountListener(new MessageCountAdapter() {
        public void messagesAdded(MessageCountEvent ev) {
            Message[] msgs = ev.getMessages();
            System.out.println("Got " + msgs.length + " new messages");

            // Just dump out the new messages
            for (int i = 0; i < msgs.length; i++) {
            try {
                System.out.println("-----");
                System.out.println("Message " +
                msgs[i].getMessageNumber() + ":");
                msgs[i].writeTo(System.out);
            } catch (IOException ioex) { 
                ioex.printStackTrace(); 
            } catch (MessagingException mex) {
                mex.printStackTrace();
            }
            }
        }
        });

        // Check mail once in "freq" MILLIseconds
        int freq = Integer.parseInt(argv[4]);
        boolean supportsIdle = false;
        try {
        if (folder instanceof IMAPFolder) {
            IMAPFolder f = (IMAPFolder)folder;
            f.idle();
            supportsIdle = true;
        }
        } catch (FolderClosedException fex) {
        throw fex;
        } catch (MessagingException mex) {
        supportsIdle = false;
        }
        for (;;) {
        if (supportsIdle && folder instanceof IMAPFolder) {
            IMAPFolder f = (IMAPFolder)folder;
            f.idle();
            System.out.println("IDLE done");
        } else {
            Thread.sleep(freq); // sleep for freq milliseconds

            // This is to force the IMAP server to send us
            // EXISTS notifications. 
            folder.getMessageCount();
        }
        }

    } catch (Exception ex) {
        ex.printStackTrace();
    }
    }
}

回答by Mo Firouz

The suggestion by @user888307 is a dirty hack and eventually fail miserably. There is really only one proper way of doing this.

@user888307 的建议是一个肮脏的黑客,最终惨遭失败。确实只有一种正确的方法可以做到这一点。

Call the idle(false) method on the folder that's currently selected. Ideally Inbox because that will receive all messages.

对当前选择的文件夹调用 idle(false) 方法。理想情况下收件箱,因为它会收到所有邮件。

Calling idle(false) will basically hang the runtime of the thread, so better to put idle(false) on a new thread. Then once you receive a new email/notification using messageCountChange, you have to rerun this thread.

调用 idle(false) 基本上会挂起线程的运行时,因此最好将 idle(false) 放在新线程上。然后,一旦您使用 messageCountChange 收到新电子邮件/通知,您必须重新运行此线程。

This is the only true way of achieving this. I have written a wrapper for your explicit problem as I am writing a program called JavaPushMail. You can find more info on my website (http://www.mofirouz.com/wordpress) or you can grab the application (which is currently in development) on GitHub https://github.com/mofirouz/JavaPushMail

这是实现这一目标的唯一正确方法。在编写名为 JavaPushMail 的程序时,我已经为您的显式问题编写了一个包装器。您可以在我的网站 ( http://www.mofirouz.com/wordpress)上找到更多信息,也可以在 GitHub https://github.com/mofirouz/JavaPushMail上获取该应用程序(目前正在开发中)

回答by user888307

checking the message count every 5 minutes works for me:

每 5 分钟检查一次消息计数对我有用:

new Thread()
{
    @Override
    public void run()
    {
        startTimer();
    }
    private void startTimer()
    {
        int seconds = 0;
        while (true)
        {
            try
            {
                Thread.sleep(300000);
                int c = folder.getMessageCount();    
            }
            catch (InterruptedException ex)
            {
            }
            catch (MessagingException me)
            {
            }
        }
    }
}.start();