在电子邮件技术中,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具体代码如下:

 namespace TestMailKit
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
} private void button1_Click(object sender, EventArgs e)
{
TodoMail();
} public static void TodoMail()
{
try
{
using (var client = new ImapClient(new ProtocolLogger(Console.OpenStandardError())))
{
client.Connect("imap.qq.com", , true);
if (client.AuthenticationMechanisms.Contains("XOAUTH2"))
client.AuthenticationMechanisms.Remove("XOAUTH2");
client.Authenticate("110xxxxx31@qq.com", "******chfcf"); client.Inbox.Open(FolderAccess.ReadOnly); // Get the summary information of all of the messages (suitable for displaying in a message list).
var messages = client.Inbox.Fetch(, -, MessageSummaryItems.Full | MessageSummaryItems.UniqueId).ToList(); // Keep track of messages being expunged so that when the CountChanged event fires, we can tell if it's
// because new messages have arrived vs messages being removed (or some combination of the two).
client.Inbox.MessageExpunged += (sender, e) =>
{
var folder = (ImapFolder)sender; if (e.Index < messages.Count)
{
var message = messages[e.Index]; Console.WriteLine("{0}: expunged message {1}: Subject: {2}", folder, e.Index, message.Envelope.Subject); // Note: If you are keeping a local cache of message information
// (e.g. MessageSummary data) for the folder, then you'll need
// to remove the message at e.Index.
messages.RemoveAt(e.Index);
}
else
{
Console.WriteLine("{0}: expunged message {1}: Unknown message.", folder, e.Index);
}
}; // Keep track of changes to the number of messages in the folder (this is how we'll tell if new messages have arrived).
client.Inbox.CountChanged += (sender, e) =>
{
// Note: the CountChanged event will fire when new messages arrive in the folder and/or when messages are expunged.
var folder = (ImapFolder)sender; Console.WriteLine("The number of messages in {0} has changed.", folder); // Note: because we are keeping track of the MessageExpunged event and updating our
// 'messages' list, we know that if we get a CountChanged event and folder.Count is
// larger than messages.Count, then it means that new messages have arrived.
if (folder.Count > messages.Count)
{
Console.WriteLine("{0} new messages have arrived.", folder.Count - messages.Count); // Note: your first instict may be to fetch these new messages now, but you cannot do
// that in an event handler (the ImapFolder is not re-entrant).
//
// If this code had access to the 'done' CancellationTokenSource (see below), it could
// cancel that to cause the IDLE loop to end.
}
}; // Keep track of flag changes.
client.Inbox.MessageFlagsChanged += (sender, e) =>
{
var folder = (ImapFolder)sender; Console.WriteLine("{0}: flags for message {1} have changed to: {2}.", folder, e.Index, e.Flags);
}; Console.WriteLine("Hit any key to end the IDLE loop.");
using (var done = new CancellationTokenSource())
{
// Note: when the 'done' CancellationTokenSource is cancelled, it ends to IDLE loop.
var thread = new Thread(IdleLoop); thread.Start(new IdleState(client, done.Token)); Console.ReadKey();
done.Cancel();
thread.Join();
} if (client.Inbox.Count > messages.Count)
{
Console.WriteLine("The new messages that arrived during IDLE are:");
foreach (var message in client.Inbox.Fetch(messages.Count, -, MessageSummaryItems.Full | MessageSummaryItems.UniqueId))
Console.WriteLine("Subject: {0}", message.Envelope.Subject);
} client.Disconnect(true);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
} static void IdleLoop(object state)
{
var idle = (IdleState)state; lock (idle.Client.SyncRoot)
{
// Note: since the IMAP server will drop the connection after 30 minutes, we must loop sending IDLE commands that
// last ~29 minutes or until the user has requested that they do not want to IDLE anymore.
//
// For GMail, we use a 9 minute interval because they do not seem to keep the connection alive for more than ~10 minutes.
while (!idle.IsCancellationRequested)
{
// Note: Starting with .NET 4.5, you can make this simpler by using the CancellationTokenSource .ctor that
// takes a TimeSpan argument, thus eliminating the need to create a timer.
using (var timeout = new CancellationTokenSource())
{
using (var timer = new System.Timers.Timer( * * ))
{
// End the IDLE command when the timer expires.
timer.Elapsed += (sender, e) => timeout.Cancel();
timer.AutoReset = false;
timer.Enabled = true; try
{
// We set the timeout source so that if the idle.DoneToken is cancelled, it can cancel the timeout
idle.SetTimeoutSource(timeout); if (idle.Client.Capabilities.HasFlag(ImapCapabilities.Idle))
{
// The Idle() method will not return until the timeout has elapsed or idle.CancellationToken is cancelled
idle.Client.Idle(timeout.Token, idle.CancellationToken);
}
else
{
// The IMAP server does not support IDLE, so send a NOOP command instead
idle.Client.NoOp(idle.CancellationToken); // Wait for the timeout to elapse or the cancellation token to be cancelled
WaitHandle.WaitAny(new[] { timeout.Token.WaitHandle, idle.CancellationToken.WaitHandle });
}
}
catch (OperationCanceledException)
{
// This means that idle.CancellationToken was cancelled, not the DoneToken nor the timeout.
break;
}
catch (ImapProtocolException)
{
// The IMAP server sent garbage in a response and the ImapClient was unable to deal with it.
// This should never happen in practice, but it's probably still a good idea to handle it.
//
// Note: an ImapProtocolException almost always results in the ImapClient getting disconnected.
break;
}
catch (ImapCommandException)
{
// The IMAP server responded with "NO" or "BAD" to either the IDLE command or the NOOP command.
// This should never happen... but again, we're catching it for the sake of completeness.
break;
}
finally
{
// We're about to Dispose() the timeout source, so set it to null.
idle.SetTimeoutSource(null);
}
}
}
}
}
} } class IdleState
{
readonly object mutex = new object();
CancellationTokenSource timeout; /// <summary>
/// Get the cancellation token.
/// </summary>
/// <remarks>
/// <para>The cancellation token is the brute-force approach to cancelling the IDLE and/or NOOP command.</para>
/// <para>Using the cancellation token will typically drop the connection to the server and so should
/// not be used unless the client is in the process of shutting down or otherwise needs to
/// immediately abort communication with the server.</para>
/// </remarks>
/// <value>The cancellation token.</value>
public CancellationToken CancellationToken { get; private set; } /// <summary>
/// Get the done token.
/// </summary>
/// <remarks>
/// <para>The done token tells the <see cref="Program.IdleLoop"/> that the user has requested to end the loop.</para>
/// <para>When the done token is cancelled, the <see cref="Program.IdleLoop"/> will gracefully come to an end by
/// cancelling the timeout and then breaking out of the loop.</para>
/// </remarks>
/// <value>The done token.</value>
public CancellationToken DoneToken { get; private set; } /// <summary>
/// Get the IMAP client.
/// </summary>
/// <value>The IMAP client.</value>
public ImapClient Client { get; private set; } /// <summary>
/// Check whether or not either of the CancellationToken's have been cancelled.
/// </summary>
/// <value><c>true</c> if cancellation was requested; otherwise, <c>false</c>.</value>
public bool IsCancellationRequested
{
get
{
return CancellationToken.IsCancellationRequested || DoneToken.IsCancellationRequested;
}
} /// <summary>
/// Initializes a new instance of the <see cref="IdleState"/> class.
/// </summary>
/// <param name="client">The IMAP client.</param>
/// <param name="doneToken">The user-controlled 'done' token.</param>
/// <param name="cancellationToken">The brute-force cancellation token.</param>
public IdleState(ImapClient client, CancellationToken doneToken, CancellationToken cancellationToken = default(CancellationToken))
{
CancellationToken = cancellationToken;
DoneToken = doneToken;
Client = client; // When the user hits a key, end the current timeout as well
doneToken.Register(CancelTimeout);
} /// <summary>
/// Cancel the timeout token source, forcing ImapClient.Idle() to gracefully exit.
/// </summary>
void CancelTimeout()
{
lock (mutex)
{
if (timeout != null)
timeout.Cancel();
}
} /// <summary>
/// Set the timeout source.
/// </summary>
/// <param name="source">The timeout source.</param>
public void SetTimeoutSource(CancellationTokenSource source)
{
lock (mutex)
{
timeout = source; if (timeout != null && IsCancellationRequested)
timeout.Cancel();
}
}
} }

mailkit IDLE模式

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

IMAP IDLE模式(推送邮件)的更多相关文章

  1. Quartz-第二篇 使用quartz框架定时推送邮件

    1.定时推送邮件,也就是使用定时调度框架触发我们的发邮件动作,发邮件动作,请参考我的这篇随笔.

  2. Laravel 下结合阿里云邮件推送服务

    最近在学习laravel做项目开发,遇到注册用户推送邮件的问题,之前用java做的时候是自己代码写的,也就是用ECS推送邮件,但是现在转php的laravel了就打算用php的邮件发送功能来推送邮件, ...

  3. PCB 后台自动系统集成与邮件推送实现

    在PCB行业中,工程系统是主要数据生产者,而这些数据不仅仅给自己系统使用呀,我们需要将数据传递到各系统,才达到各系统共同协作的目的. 这里以问答方式对实现方式进行讲解.呵呵呵! 后台自动集成问题解答: ...

  4. Android实现推送方式解决方案 - 长连接+心跳机制(MQTT协议)

    本文介绍在Android中实现推送方式的基础知识及相关解决方案.推送功能在手机开发中应用的场景是越来起来了,不说别的,就我们手机上的新闻客户端就时不j时的推送过来新的消息,很方便的阅读最新的新闻信息. ...

  5. Android实现推送方式解决方案(转)

    本文介绍在Android中实现推送方式的基础知识及相关解决方案.推送功能在手机开发中应用的场景是越来起来了,不说别的,就我们手机上的新闻客户端就时不j时的推送过来新的消息,很方便的阅读最新的新闻信息. ...

  6. EDM推送

    一.需求描述:        日前,做了一个发送客户账单的功能,邮件模板采用自定义,生成vm文件,保存至redis,    采用jodd-mail发送邮件,查询用户账单数据,账单明细,缓存加载模板并渲 ...

  7. 【转】Android实现推送方式解决方案

    本文介绍在Android中实现推送方式的基础知识及相关解决方案.推送功能在手机开发中应用的场景是越来起来了,不说别的,就我们手机上的新闻客户端就时不j时的推送过来新的消息,很方便的阅读最新的新闻信息. ...

  8. $Django 支付宝支付,微信服务号推送消息 (测试需要把应用程序部署到服务器上)

    一 支付宝支付 大概 支付宝支付 正式环境:需要用营业执照去申请商户号,appid 测试环境:沙箱环境:https://openhome.alipay.com/platform/appDaily.ht ...

  9. ASP.NET Core2基于RabbitMQ对Web前端实现推送功能

    在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...

随机推荐

  1. resharper激活

    1.解压后点击64位系统的IntelliJIDEALicenseServer_windows_amd64.exe      32位点击IntelliJIDEALicenseServer_windows ...

  2. 哈夫曼树(C++优先队列的使用)

       给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近.    构造 假设有n个权 ...

  3. 【干货】2个小时教你hexo博客添加评论、打赏、RSS等功能 (转)

    备注:该教程基于Hexo 2.x版本,目前Hexo是3.x版本,照本教程实现有可能会出现404错误,笔者目前还未找时间去解决,待笔者找时间解决该问题后,再写一篇该问题的解决教程,给各位读者带来困扰,还 ...

  4. nginx索引目录配置

    为了简单共享文件,有些人使用svn,有些人使用ftp,但是更多得人使用索引(index)功能.apache得索引功能强大,并且也是最常见得,nginx得auto_index实现得目录索引偏少,而且功能 ...

  5. [CF1000E]We Need More Bosses

    题目大意:给一张无向图,要求找一对$s$和$t$,使得其路径上的割边是最多的,输出其数量. 题解:把边双缩点以后求树的直径. 卡点:无 C++ Code: #include <cstdio> ...

  6. BZOJ2599 [IOI2011]Race 【点分治】

    题目 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 输入格式 第一行 两个整数 n, k 第二..n行 每行三个整 ...

  7. BZOJ2125 最短路 【仙人掌最短路】

    题目 给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径. 输入格式 输入的第一行包含三个整数,分别表示N和M和Q 下接M行,每行三个整数v,u,w表示一 ...

  8. 一种简单高效的音频降噪算法示例(附完整C代码)

    近期比较忙, 抽空出来5.1开源献礼. 但凡学习音频降噪算法的朋友,肯定看过一个算法. <<语音增强-理论与实践>> 中提及到基于对数的最小均方误差的降噪算法,也就是LogMM ...

  9. hdu 1847 Good Luck in CET-4 Everybody! SG函数SG引理

    大学英语四级考试就要来临了,你是不是在紧张的复习?也许紧张得连短学期的ACM都没工夫练习了,反正我知道的Kiki和Cici都是如此.当然,作为在考场浸润了十几载的当代大学生,Kiki和Cici更懂得考 ...

  10. OPENCV mat类

    OpenCV参考手册之Mat类详解 目标 我们有多种方法可以获得从现实世界的数字图像:数码相机.扫描仪.计算机体层摄影或磁共振成像就是其中的几种.在每种情况下我们(人类)看到了什么是图像.但是,转换图 ...