JavaMail: поддержание IMAPFolder.idle () в живых - PullRequest
13 голосов
/ 11 ноября 2010

Я создаю программу, которая должна отслеживать учетную запись 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.");
}

Метод getFolder () в основном открывает соединение с сервером IMAP и открывает папку входящих сообщений.

Это работает некоторое время, но примерно через 10 минут перестает получать обновления (исключение не выдается).

Я ищу предложения по поддержанию связи. Нужен ли мне второй поток, единственная роль которого состоит в том, чтобы спать и обновлять поток idle () каждые 10 минут или есть более простой / лучший способ?

Заранее спасибо.

Ответы [ 4 ]

22 голосов
/ 15 апреля 2013

Распространенной ошибкой является допущение, что команда IDLE будет публиковать обновления бесконечно долго.Однако RFC 2177 , который определяет состояния расширения IDLE:

Сервер МОЖЕТ считать клиента неактивным, если на нем запущена команда IDLE, и если на таком сервереТайм-аут неактивности МОЖЕТ неявно отключить клиента в конце периода его ожидания.Из-за этого клиентам, использующим IDLE, рекомендуется прекратить IDLE и переиздавать его, по крайней мере, каждые 29 минут, чтобы избежать выхода из системы.Это по-прежнему позволяет клиенту получать немедленные обновления почтового ящика, даже если ему требуется только «опрос» с интервалом в полчаса.

В частности, GMail имеет гораздо меньшее время ожидания, как вы сказали, около 10 минут.

Нам просто нужно повторять команду IDLE каждые 9 минут или около того, чтобы она работала.API javax.mail не имеют возможности установить тайм-аут для команды 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);
            }
        }
    }
}
3 голосов
/ 10 июня 2014

На самом деле Примеры 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();
    }
    }
}
3 голосов
/ 20 октября 2011

Предложение @ user888307 является грязным хаком и в конечном итоге терпит неудачу. На самом деле есть только один правильный способ сделать это.

Вызвать метод ожидания (false) для папки, которая выбрана в данный момент. В идеале Входящие, потому что он будет получать все сообщения.

Вызов idle (false) в основном приведет к зависанию среды выполнения потока, поэтому лучше поставить idle (false) в новый поток. Затем, как только вы получите новое электронное письмо / уведомление с помощью messageCountChange, вам придется перезапустить эту тему.

Это единственный верный способ достичь этого. Я написал оболочку для вашей явной проблемы, когда пишу программу под названием JavaPushMail. Вы можете найти больше информации на моем веб-сайте (http://www.mofirouz.com/wordpress)) или загрузить приложение (которое в настоящее время находится в разработке) на GitHub https://github.com/mofirouz/JavaPushMail

0 голосов
/ 10 августа 2011

проверка количества сообщений каждые 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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...