• IMAP IDLE模式(推送邮件)


            在电子邮件技术中,IDLE是RFC 2177中描述的一项IMAP功能,它允许客户端向服务器表明它已准备好接受实时通知。

            Internet消息访问协议IMAP4协议,它要求客户端轮询服务器来更改所选中的文件夹(如拉取新邮件、删除邮件),如果能让服务器推送通知客户端,告知客户端有新邮件的话会更方便客户端,尤其是在手机端的时候,大量的轮询查询服务器会耗费电量和流量,用户是不太允许这样做的,而且也不是很及时的收到邮件。

           考虑到这种情况,其实IMAP4的扩展协议中是支持这个推送模式,即IMAP的IDLE模式。

            首先我们用CAPABILITY 命令查询一下是否支持IDLE模式,因为并不是所有邮箱多支持的。

            如qq邮箱:["CAPABILITY", "IMAP4", "IMAP4rev1", "IDLE", "XAPPLEPUSHSERVICE", "AUTH=LOGIN", "NAMESPACE", "CHILDREN", "ID", "UIDPLUS"]就支持这种模式,而163邮箱:["CAPABILITY", "IMAP4rev1", "XLIST", "SPECIAL-USE", "ID", "LITERAL+", "STARTTLS", "XAPPLEPUSHSERVICE", "UIDPLUS", "X-CM-EXT-1"]并不支持。

             我们就用qq邮箱来测试一下,测试前请开通QQ邮箱的imap协议功能保持连接正常,关于IDLE命令的使用,需要先登录验证后,选中文件夹之后才可以使用,具体测试如下图:

    查看命令可以知道,每次收到新邮件这会更改EXISTS的数量,这样就收到一个邮件通知,然后通过这个通知在去拉取邮件就可以。这是基本原理,具体到具体应用,由于我一直使用mailkit来获取邮件,而mailkit本身也是支持这种模式的。

    mailkit具体代码如下:

      1 namespace TestMailKit
      2 {
      3     public partial class Form2 : Form
      4     {
      5         public Form2()
      6         {
      7             InitializeComponent();
      8         }
      9 
     10         private void button1_Click(object sender, EventArgs e)
     11         {
     12             TodoMail();
     13         }
     14 
     15         public static void TodoMail()
     16         {
     17             try
     18             {
     19                 using (var client = new ImapClient(new ProtocolLogger(Console.OpenStandardError())))
     20                 {
     21                     client.Connect("imap.qq.com", 993, true);
     22                     if (client.AuthenticationMechanisms.Contains("XOAUTH2"))
     23                         client.AuthenticationMechanisms.Remove("XOAUTH2");
     24                     client.Authenticate("110xxxxx31@qq.com", "******chfcf");
     25 
     26                     client.Inbox.Open(FolderAccess.ReadOnly);
     27 
     28                     // Get the summary information of all of the messages (suitable for displaying in a message list).
     29                     var messages = client.Inbox.Fetch(0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId).ToList();
     30 
     31                     // Keep track of messages being expunged so that when the CountChanged event fires, we can tell if it's
     32                     // because new messages have arrived vs messages being removed (or some combination of the two).
     33                     client.Inbox.MessageExpunged += (sender, e) =>
     34                     {
     35                         var folder = (ImapFolder)sender;
     36 
     37                         if (e.Index < messages.Count)
     38                         {
     39                             var message = messages[e.Index];
     40 
     41                             Console.WriteLine("{0}: expunged message {1}: Subject: {2}", folder, e.Index, message.Envelope.Subject);
     42 
     43                             // Note: If you are keeping a local cache of message information
     44                             // (e.g. MessageSummary data) for the folder, then you'll need
     45                             // to remove the message at e.Index.
     46                             messages.RemoveAt(e.Index);
     47                         }
     48                         else
     49                         {
     50                             Console.WriteLine("{0}: expunged message {1}: Unknown message.", folder, e.Index);
     51                         }
     52                     };
     53 
     54                     // Keep track of changes to the number of messages in the folder (this is how we'll tell if new messages have arrived).
     55                     client.Inbox.CountChanged += (sender, e) =>
     56                     {
     57                         // Note: the CountChanged event will fire when new messages arrive in the folder and/or when messages are expunged.
     58                         var folder = (ImapFolder)sender;
     59 
     60                         Console.WriteLine("The number of messages in {0} has changed.", folder);
     61 
     62                         // Note: because we are keeping track of the MessageExpunged event and updating our
     63                         // 'messages' list, we know that if we get a CountChanged event and folder.Count is
     64                         // larger than messages.Count, then it means that new messages have arrived.
     65                         if (folder.Count > messages.Count)
     66                         {
     67                             Console.WriteLine("{0} new messages have arrived.", folder.Count - messages.Count);
     68 
     69                             // Note: your first instict may be to fetch these new messages now, but you cannot do
     70                             // that in an event handler (the ImapFolder is not re-entrant).
     71                             //
     72                             // If this code had access to the 'done' CancellationTokenSource (see below), it could
     73                             // cancel that to cause the IDLE loop to end.
     74                         }
     75                     };
     76 
     77                     // Keep track of flag changes.
     78                     client.Inbox.MessageFlagsChanged += (sender, e) =>
     79                     {
     80                         var folder = (ImapFolder)sender;
     81 
     82                         Console.WriteLine("{0}: flags for message {1} have changed to: {2}.", folder, e.Index, e.Flags);
     83                     };
     84 
     85                     Console.WriteLine("Hit any key to end the IDLE loop.");
     86                     using (var done = new CancellationTokenSource())
     87                     {
     88                         // Note: when the 'done' CancellationTokenSource is cancelled, it ends to IDLE loop.
     89                         var thread = new Thread(IdleLoop);
     90 
     91                         thread.Start(new IdleState(client, done.Token));
     92 
     93                         Console.ReadKey();
     94                         done.Cancel();
     95                         thread.Join();
     96                     }
     97 
     98                     if (client.Inbox.Count > messages.Count)
     99                     {
    100                         Console.WriteLine("The new messages that arrived during IDLE are:");
    101                         foreach (var message in client.Inbox.Fetch(messages.Count, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId))
    102                             Console.WriteLine("Subject: {0}", message.Envelope.Subject);
    103                     }
    104 
    105                     client.Disconnect(true);
    106                 }
    107             }
    108             catch (Exception ex)
    109             {
    110                 Console.WriteLine(ex.Message);
    111             }
    112         }
    113 
    114         static void IdleLoop(object state)
    115         {
    116             var idle = (IdleState)state;
    117 
    118             lock (idle.Client.SyncRoot)
    119             {
    120                 // Note: since the IMAP server will drop the connection after 30 minutes, we must loop sending IDLE commands that
    121                 // last ~29 minutes or until the user has requested that they do not want to IDLE anymore.
    122                 //
    123                 // For GMail, we use a 9 minute interval because they do not seem to keep the connection alive for more than ~10 minutes.
    124                 while (!idle.IsCancellationRequested)
    125                 {
    126                     // Note: Starting with .NET 4.5, you can make this simpler by using the CancellationTokenSource .ctor that
    127                     // takes a TimeSpan argument, thus eliminating the need to create a timer.
    128                     using (var timeout = new CancellationTokenSource())
    129                     {
    130                         using (var timer = new System.Timers.Timer(9 * 60 * 1000))
    131                         {
    132                             // End the IDLE command when the timer expires.
    133                             timer.Elapsed += (sender, e) => timeout.Cancel();
    134                             timer.AutoReset = false;
    135                             timer.Enabled = true;
    136 
    137                             try
    138                             {
    139                                 // We set the timeout source so that if the idle.DoneToken is cancelled, it can cancel the timeout
    140                                 idle.SetTimeoutSource(timeout);
    141 
    142                                 if (idle.Client.Capabilities.HasFlag(ImapCapabilities.Idle))
    143                                 {
    144                                     // The Idle() method will not return until the timeout has elapsed or idle.CancellationToken is cancelled
    145                                     idle.Client.Idle(timeout.Token, idle.CancellationToken);
    146                                 }
    147                                 else
    148                                 {
    149                                     // The IMAP server does not support IDLE, so send a NOOP command instead
    150                                     idle.Client.NoOp(idle.CancellationToken);
    151 
    152                                     // Wait for the timeout to elapse or the cancellation token to be cancelled
    153                                     WaitHandle.WaitAny(new[] { timeout.Token.WaitHandle, idle.CancellationToken.WaitHandle });
    154                                 }
    155                             }
    156                             catch (OperationCanceledException)
    157                             {
    158                                 // This means that idle.CancellationToken was cancelled, not the DoneToken nor the timeout.
    159                                 break;
    160                             }
    161                             catch (ImapProtocolException)
    162                             {
    163                                 // The IMAP server sent garbage in a response and the ImapClient was unable to deal with it.
    164                                 // This should never happen in practice, but it's probably still a good idea to handle it.
    165                                 //
    166                                 // Note: an ImapProtocolException almost always results in the ImapClient getting disconnected.
    167                                 break;
    168                             }
    169                             catch (ImapCommandException)
    170                             {
    171                                 // The IMAP server responded with "NO" or "BAD" to either the IDLE command or the NOOP command.
    172                                 // This should never happen... but again, we're catching it for the sake of completeness.
    173                                 break;
    174                             }
    175                             finally
    176                             {
    177                                 // We're about to Dispose() the timeout source, so set it to null.
    178                                 idle.SetTimeoutSource(null);
    179                             }
    180                         }
    181                     }
    182                 }
    183             }
    184         }
    185 
    186     }
    187 
    188     class IdleState
    189     {
    190         readonly object mutex = new object();
    191         CancellationTokenSource timeout;
    192 
    193         /// <summary>
    194         /// Get the cancellation token.
    195         /// </summary>
    196         /// <remarks>
    197         /// <para>The cancellation token is the brute-force approach to cancelling the IDLE and/or NOOP command.</para>
    198         /// <para>Using the cancellation token will typically drop the connection to the server and so should
    199         /// not be used unless the client is in the process of shutting down or otherwise needs to
    200         /// immediately abort communication with the server.</para>
    201         /// </remarks>
    202         /// <value>The cancellation token.</value>
    203         public CancellationToken CancellationToken { get; private set; }
    204 
    205         /// <summary>
    206         /// Get the done token.
    207         /// </summary>
    208         /// <remarks>
    209         /// <para>The done token tells the <see cref="Program.IdleLoop"/> that the user has requested to end the loop.</para>
    210         /// <para>When the done token is cancelled, the <see cref="Program.IdleLoop"/> will gracefully come to an end by
    211         /// cancelling the timeout and then breaking out of the loop.</para>
    212         /// </remarks>
    213         /// <value>The done token.</value>
    214         public CancellationToken DoneToken { get; private set; }
    215 
    216         /// <summary>
    217         /// Get the IMAP client.
    218         /// </summary>
    219         /// <value>The IMAP client.</value>
    220         public ImapClient Client { get; private set; }
    221 
    222         /// <summary>
    223         /// Check whether or not either of the CancellationToken's have been cancelled.
    224         /// </summary>
    225         /// <value><c>true</c> if cancellation was requested; otherwise, <c>false</c>.</value>
    226         public bool IsCancellationRequested
    227         {
    228             get
    229             {
    230                 return CancellationToken.IsCancellationRequested || DoneToken.IsCancellationRequested;
    231             }
    232         }
    233 
    234         /// <summary>
    235         /// Initializes a new instance of the <see cref="IdleState"/> class.
    236         /// </summary>
    237         /// <param name="client">The IMAP client.</param>
    238         /// <param name="doneToken">The user-controlled 'done' token.</param>
    239         /// <param name="cancellationToken">The brute-force cancellation token.</param>
    240         public IdleState(ImapClient client, CancellationToken doneToken, CancellationToken cancellationToken = default(CancellationToken))
    241         {
    242             CancellationToken = cancellationToken;
    243             DoneToken = doneToken;
    244             Client = client;
    245 
    246             // When the user hits a key, end the current timeout as well
    247             doneToken.Register(CancelTimeout);
    248         }
    249 
    250         /// <summary>
    251         /// Cancel the timeout token source, forcing ImapClient.Idle() to gracefully exit.
    252         /// </summary>
    253         void CancelTimeout()
    254         {
    255             lock (mutex)
    256             {
    257                 if (timeout != null)
    258                     timeout.Cancel();
    259             }
    260         }
    261 
    262         /// <summary>
    263         /// Set the timeout source.
    264         /// </summary>
    265         /// <param name="source">The timeout source.</param>
    266         public void SetTimeoutSource(CancellationTokenSource source)
    267         {
    268             lock (mutex)
    269             {
    270                 timeout = source;
    271 
    272                 if (timeout != null && IsCancellationRequested)
    273                     timeout.Cancel();
    274             }
    275         }
    276     }
    277 
    278 }
    mailkit IDLE模式

    通过上面的代码即可订阅邮件通知,方便的获取邮件。

  • 相关阅读:
    3.db2性能和优化
    SpringBoot之demo
    1设计模式---工厂模式。
    1.添加maven项目后,tomcat启动失败
    2.如何卸载mysql
    2.hdfs中常用的shell命令
    1.在eclipse上添加maven
    2.hive入门篇
    1.hive数据库调优之路
    2.myeclipse的使用技巧
  • 原文地址:https://www.cnblogs.com/zuimengaitianya/p/8588555.html
Copyright © 2020-2023  润新知