【问题标题】:JavaMail: Keeping IMAPFolder.idle() aliveJavaMail:保持 IMAPFolder.idle() 活着
【发布时间】:2011-05-08 12:16:49
【问题描述】:

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

//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.");
}

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

这可以运行一段时间,但在 10 分钟左右后它会停止获取更新(不会引发异常)。

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

提前致谢。

【问题讨论】:

  • 我也打算这样做。你终于能够解决问题了吗?目前,我每 15 秒通过“folder.open/folder.close”轮询文件夹,但 IDLE 当然会更好。我打算在应用服务器环境中使用它。
  • 很抱歉没有早点发现您的评论。我最终放弃了这个项目,所以我从来没有接近过解决方案。但是现在这个线程有了答案,也许这会起作用……虽然它是基于轮询而不是空闲的。
  • 您必须轮询和空闲才能正确执行此操作。根据规范,IDLE 必须每半小时终止和更新一次,如果 NATbox 出现故障,则更频繁。正确的间隔是......好吧,也许没有一个正确的值。
  • 有什么好的解决方案吗?我自己也有类似的问题。我想闲着等待,直到将新邮件插入收件箱文件夹,同时保持连接处于活动状态。我不想使用轮询。

标签: java imap jakarta-mail


【解决方案1】:

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

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

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

我们只需每 9 分钟左右重新发出 IDLE 命令即可使其工作。 javax.mail API 无法为 IDLE 命令设置超时,因此您需要第二个线程来解决此问题。

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

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

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 <jmsotuyo@monits.com>
 */
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);
            }
        }
    }
}

【讨论】:

  • 在使用 ExecutorService(尤其是在 JEE 中)的场景中,您可能希望使用 Object.wait() 而不是 Thread.sleep(),这样您也可以在不受控制的情况下中断它线程的。
【解决方案2】:

其实Java Mail samples包含一个IMAP IDLE的例子,如下。 除此之外,IdleManager class 可能很有趣。

/*
 * 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();
    }
    }
}

【讨论】:

    【解决方案3】:

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

    在当前选中的文件夹上调用 idle(false) 方法。理想情况下是收件箱,因为它将接收所有消息。

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

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

    【讨论】:

    • 两个问题:1) messageCountChange 是在同一个空闲线程上调用还是在新线程上调用? 2)如何更新空闲以防止超时?谢谢。
    • The javadoc @Mo 您并没有真正提供问题的答案。你给一个 github repo 和 wordpress 博客的链接。调用 idle() 和 idle(false) 都会挂起运行时,需要在自己的线程上运行。您还说“上述建议”,但由于对 S.O. 进行了投票。不能保证上述答案将保持在上面。
    【解决方案4】:

    您可以将您的文件夹注册到connectionListener

    var folder = store.getFolder("<FOLDER_NAME>");
    folder.addConnectionListener(new javax.mail.event ConnectionAdapter() {
            public void closed(ConnectionEvent e) {
                try {
                    log.info("Folder connection closed. Reconnect to server");
                    
                    // reopen connection 
                    connectToServer();
                } catch (Exception exception) {
                    log.error("Could not connect to server={0}", exception);
                }
            }
        });
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 这是一个轮询循环而不是空闲。它对服务器和带宽使用效率都很低,而且无法获得即时通知。
      • 另外,请不要使用空的 catch 块。即使您确定它不会发生(不是这种情况),最好记录它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-19
      • 1970-01-01
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 2011-06-19
      相关资源
      最近更新 更多