2010-11-11 19 views
13

Estoy haciendo un programa que necesita monitorear una cuenta de Gmail para nuevos mensajes, y para poder obtenerlos ASAP estoy usando la función inactiva de JavaMail. Aquí es un fragmento de código de la rosca que estoy utilizando para llamar folder.idle():JavaMail: Mantener IMAPFolder.idle() con vida

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

El método getFolder() básicamente se abre la conexión con el servidor IMAP y abre la bandeja de entrada.

Esto funciona por un tiempo, pero después de 10 minutos o así deja de recibir actualizaciones (no se lanza ninguna excepción).

Estoy buscando sugerencias para mantener viva la conexión. ¿Necesito un segundo hilo cuyo único rol es dormir y renovar el hilo inactivo() cada 10 minutos o hay una forma más fácil/mejor?

Gracias de antemano.

+0

Estoy planeando hacer lo mismo. ¿Finalmente pudiste resolver el problema? Actualmente, estoy sondeando la carpeta a través de 'folder.open/folder.close' cada 15 segundos, pero IDLE sería mejor, por supuesto. Estoy planeando usar esto en un entorno de servidor de aplicaciones. – Theo

+0

Disculpa por no haber visto tu comentario antes. Terminé abandonando el proyecto, por lo que nunca me acerqué a una solución. Pero ahora que este hilo tiene una respuesta, tal vez eso funcione ... aunque se basa en encuestas y no está inactivo. – Anders

+0

Tienes que sondear y estar inactivo para hacerlo correctamente. IDLE tiene que terminarse y renovarse cada media hora de acuerdo con la especificación y más a menudo si una NATbox rota está en camino. El intervalo correcto es ... bueno, quizás no haya un valor correcto. – arnt

Respuesta

-6

comprobar el mensaje de recuento de cada 5 minutos funciona para mí:

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(); 
+4

Este es un bucle de encuesta en lugar de inactivo. Es ineficiente, tanto para el servidor como para el uso del ancho de banda, y no recibe notificaciones instantáneas. – WhyNotHugo

+0

Además, NUNCA use un bloque catch vacío. Incluso si está SEGURO de que no sucederá (no en este caso), es mejor iniciar sesión. – sargue

3

La sugerencia de @ user888307 es un truco sucio y finalmente fracasan miserablemente. En realidad, solo hay una forma adecuada de hacer esto.

Llamar al método inactivo (falso) en la carpeta que está seleccionada actualmente. Idealmente, Inbox porque eso recibirá todos los mensajes.

Llamar a ralentí (falso) básicamente bloqueará el tiempo de ejecución del hilo, por lo que es mejor dejarlo inactivo (falso) en un nuevo hilo. Luego, una vez que reciba un nuevo correo electrónico/notificación usando messageCountChange, debe volver a ejecutar este hilo.

Esta es la única forma verdadera de lograr esto. He escrito un contenedor para su problema explícito cuando escribo un programa llamado JavaPushMail. Puede encontrar más información en mi sitio web (http://www.mofirouz.com/wordpress) o se puede agarrar la aplicación (que está actualmente en desarrollo) en GitHub https://github.com/mofirouz/JavaPushMail

+0

Dos preguntas: 1) ¿se llama al messageCountChange en el mismo hilo inactivo o en uno nuevo? 2) ¿cómo se renueva la inactividad para evitar tiempos de espera? Gracias. – Patricio

+0

[El javadoc] (https://javamail.java.net/nonav/docs/api/com/sun/mail/imap/IMAPFolder.html#idle()) @Mo realmente no proporciona una respuesta a la pregunta. Le das un enlace a un repositorio de Github y un blog de WordPress. Llamar a inactivo() e inactivo (falso) colgarán el tiempo de ejecución y se deben ejecutar en su propio hilo. Usted también dice "la sugerencia anterior", pero dado que hay una votación sobre S.O. no hay garantía de que la respuesta anterior permanezca arriba. – gdoubleod

+0

Uso el trabajo @Mo, es increíble. Buen trabajo :) – jechaviz

21

Un error común es asumir un comando IDLE mantendrá publicar actualizaciones indefinidamente. Sin embargo, el RFC 2177, que define los estados de extensión libre:

el servidor puede considerar un cliente inactivo si tiene un IDLE comando correr, y si dicho servidor tiene un tiempo de inactividad se puede registrar el cliente fuera implícitamente al final de su período de tiempo de espera. Como de eso, se recomienda a los clientes que utilizan IDLE terminar el IDLE y volver a emitirlo al menos cada 29 minutos para evitar que se cierre la sesión. Esto aún permite que un cliente reciba actualizaciones inmediatas de buzones, incluso aunque solo necesita "sondeo" a intervalos de media hora.

GMail en particular, tiene un tiempo de espera mucho menor, como dices, alrededor de 10 minutos.

Simplemente necesitamos volver a emitir el comando IDLE cada 9 minutos aproximadamente para que funcione. Las API javax.mail no tienen forma de establecer un tiempo de espera para el comando IDLE, por lo que necesitará un segundo hilo para moverse.

Un primer enfoque sería hacer que el segundo hilo interrumpa el primero, manejando la excepción e ignorándolo. Sin embargo, esto no permitiría una manera limpia de cerrar el hilo, por lo que no lo recomendaría.Una forma mucho más limpia es hacer que el segundo hilo emita un comando de NOOP al servidor. Esto no hace nada en absoluto, pero es suficiente para que IDLE se cancele y se vuelva a emitir.

aquí me ofrecen algo de código para hacer esto:

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); 
      } 
     } 
    } 
} 
+1

En un escenario en el que utiliza un ExecutorService (en particular en JEE), es posible que desee utilizar Object.wait() en lugar de Thread.sleep() para que pueda interrumpirlo también sin tener el control del Thread . –

3

En realidad, el Java Mail samples incluyen un ejemplo IMAP IDLE, que es el siguiente. Además de eso, el IdleManager class podría ser interesante.

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