`今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能。在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下:
1) 邮件基础理论知识 2) 邮件发送相关.NET类库 3) 介绍我开发的一个发送邮件的小组件(MailHelper) 4) MailHelper组件的一个示例以及几种方式发邮件的优劣测试
示例及组件源码: .NET开发邮件发送功能的全面教程(含邮件组件源码).rar
邮件基础理论知识
什么业务需要邮件功能? 1. 服务提供方:需提供邮件收发客户端或Web服务。(eg:Outlook、QQ邮箱)。当然这些服务都是知名商提供。若是一般的小网站提供的邮件收发服务,不知道节操如何,谁敢用呢?就算你用了,别的知名商SMTP服务器也不认可从这小网站发出的邮件,出现SMTP服务器拒收来源邮件(视为恶意邮件或垃圾邮件)。 2. 安全性、机密性:比如某安全部门需要提供自己发邮件的SMTP服务器和收邮件POP3服务器以及相应的操作软件 3. 电子商务、论坛等会员机制社区:主家需要向会员发送通知信息,比如:密码重置、降价通知、留言通知、回复通知、订阅通知、会员间交流等等。主家保证邮箱有效性的办法常常是通过会员注册、更换邮箱时发送“激活邮件”。 4. 邮件营销:在大数据时代的现在,企业可以根据所掌握的数据预测客户的需求,来提供主动推送营销消息的功能;当然也有没有预测能力的小商家通过邮件群发器进行撒网式邮件营销。 5. 等等
什么是电子邮件协议? 当前常用的电子邮件协议有SMTP、POP3、IMAP4,它们都隶属于TCP/IP协议簇。 1. SMTP Simple Mail Transfer Protocol(即简单邮件传输协议),它是一组用于从源地址到目的地址传送邮件的规则,简单的说就是:From-->To的传送规则。由SMTP来控制信件中转的方式。SMTP属于TCP/IP家族中的一员,它帮助每一台计算机在发送或中转信件时找到下一个目的地。通过SMTP协议所指定的服务器,就可以把E-Mail寄到收信人的服务器上。SMTP服务器则是遵循SMTP协议的邮件发送服务器,用来中转你发出的电子邮件。 SMTP目前已是事实上的E-Mail传输的标准。 2. POP3 Post Office Protocol 3(即邮局协议的第3个版本),负责从邮件服务器中检索电子邮件。它要求邮件服务器完成下面几种任务之一:从邮件服务器中检索邮件并从服务器中删除这个邮件;从邮件服务器中检索邮件但不删除它;不检索邮件,只是询问是否有新邮件到达。 POP3是因特网电子邮件的第一个离线协议标准。 3. IMAP4 Internet Message Access Protocol 4(即交互式数据消息访问协议第四个版本),提供脱机和联机访问功能。是一种优于POP的新协议,是美国斯坦福大学在1986年开始研发的多重邮箱电子邮件系统。和POP一样,IMAP也能下载邮件、从服务器中删除邮件或询问是否有新邮件,但IMAP克服了POP的一些缺点。例如,请求邮件服务器只下载所选中的邮件而不是全部邮件。客户机可先阅读邮件信息的标题和发送者的名字再决定是否下载这个邮件。通过用户的客户机电子邮件程序,IMAP可让用户在服务器上创建并管理邮件文件夹或邮箱、删除邮件、查询某封信的一部分或全部内容,完成所有这些工作时都不需要把邮件从服务器下载到用户的个人计算机上。 默认情况下,当 IMAP4 电子邮件应用程序将电子邮件下载到客户端计算机,下载邮件的副本会保留在电子邮件服务器上。正是由于用户的电子邮件副本保留在电子邮件服务器上,用户可以从多台计算机上访问相同的电子邮件。也可以实现电子邮件服务器上的多个文件夹与客户端计算机上的多个文件夹同步。
SMTP/POP3工作方式如图:
TCP的3次握手和4次挥手? 详细可见《TCP3次握手/4次握手》 在 TCP 数据段报头中,有六个包含控制信息的 1 bit字段,用于管理 TCP 进程。这些字段分别是: URG —紧急指针 ACK —确认字段 PSH —推送功能 RST —重置连接 SYN —同步序列号 FIN —发送方已传输完所有数据 这些字段用作标志,由于它们都只有 1 bit大小,所以它们都只有两个值:1 或者 0。当值设为 1 时,表示数据段中包含控制信息。 1. 三次握手,建立连接 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。 1) 建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。 2) 服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。 3) 客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
2. 四次挥手,关闭连接 由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。 1) 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。 2) 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。 3) 服务器B关闭与客户端A的连接,发送一个FIN给客户端A。 4) 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
3. 为什么建立连接协议是三次握手,而关闭连接却是四次挥手呢? 建立连接时,服务端LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN放在一个报文里来发送。 关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以关闭连接的ACK报文和FIN报文多数情况下都是分开发送的。
常见的邮箱类型有哪些? 常见的邮箱类型有:免费邮箱、vip邮箱、域名邮箱、企业邮箱等等。 1. 免费邮箱 “免费邮箱”是邮件商家为任何人免费提供的电子邮件传输服务,作为交换,该网站上你请求电子邮件服务和一些个人信息的地方会显示广告。它更适合个人生活和娱乐的需要,却并非那么注重邮箱的安全和功能。 部分免费邮件SMTP服务器参考设置:
2. vip邮箱 “vip邮箱”即邮件商家提供的收费版邮件服务,在速度、安全、稳定性、容量、附件大小限制、群发数等方面相对好些。其SMTP服务器设置就是多了个vip字符。eg:smtp.vip.qq.com。邮箱地址:369220123@vip.qq.com。 3. 域名邮箱 “域名邮箱”是个性化邮件服务,能让您用自己的域名做为后缀即“@自己的域名”,前提是你需要一个域名(通常域名要收费)。功能比免费邮箱要多:可分配单个邮箱、规划容量、更加的安全、更好的稳定性、个性化名称、邮件发送量更大、附件大小限制等等。 4. 企业邮箱 “企业邮箱”是域名邮箱,但通常是指通过付费方式获得更好服务的邮箱。eg:您公司域名为www.abc.com,则SMTP服务器为:mail.abc.com,邮箱地址:office@abc.com; 使用企业邮箱的优势: 1) 提升公司企业形象、邮箱稳定性、邮箱反垃圾反病毒性能、邮件收发速度; 2) 通过购买服务,能适应企业不断升级需求; 3) 为员工分配(域名)企业邮箱,便于将流动员工所有业务联系保留和延续下来; 4) 监控邮件(实际为邮件暗抄送功能),以防公司的机密和重要信息流失; 5) 获得高性能邮件海外转发功能,解决国际高效邮件收发、邮件营销有效投递等问题; 6) 出站电子邮件过滤,比如:敏感字过滤、基于政策邮件加密等等; 7) 等等。
邮件发送相关.NET类库 在 .net1.1 ,用System.Web.Mail发送邮件。在.net2.0及之后版本,用System.Net.Mail发送邮件。主要用到了在.net2.0中新增的两个类,分别是System.Net.Mail.MailMessage和System.Net.Mail.SmtpClient两个类,在SMTP身份验证方面用到了System.Net.NetworkCredential类。
1. MailMessage 类表示邮件的内容。
2. SmtpClient类用于将电子邮件发送到 SMTP 服务器以便传递。
3. 一个简单的邮件发送示例
4. 邮件扩展:如何发送内嵌资源(eg:图片、mp3等等) 详细请看:http://www.cnblogs.com/SkyD/archive/2009/05/11/1453868.html(斯克迪亚) 通过 ContentDisposition 类实现此功能,内嵌的资源只做为文件内容显示,不再在附件列表中出现。ContentDisposition 类表示 MIME 协议 Content-Disposition 标头。 对于文件附件,可以使用 ContentDisposition 的属性来设置文件大小、文件的创建日期、上次读取文件的日期以及上次修改文件的日期。对于所有附件,考虑到附件有可能会存储到接收计算机上,可以设置一个建议的文件名。显示电子邮件的软件可以使用 ContentDisposition 中的信息,按发件人预期的方式呈现电子邮件附件。 通过 ContentDisposition 实例的Inline属性实现邮件内嵌资源。如下: 1) 设置附件的ContentId属性为一个自定义名称。 2) 设置附件的ContentDisposition.Inline属性为true。 3) 在邮件的HTML格式正文中以“cid:自定义名称”的方式引用,比如ContentId设为“face”,那么正文中就以“cid:face”作为其URL路径字符串的替代即可。 代码如下:(详细见示例代码)
另外,可使用AlternateView类和LinkedResource类来实现内嵌资源…… 1) 创建一个MailMessage对象,同时指定发送人和接收人地址。 2) 创建AlternateView来接收文本内容,创建LinkedResource来接收要嵌入的图片或其他资源。 3) 添加LinkedResource到AlternateView 4) 添加AlternateView到MailMessage 5) 设置SmtpClient,发送email
我开发的一个发送邮件的小组件(代码在博文开始处已给出下载地址) 为了简化邮件发送代码编写和SmtpClient实例的管理,我封装了一个发邮件的帮助类。 这个帮助类,包含如图几个文件: 两个主要类: SmtpHelper 和MailHelper 1. SmtpHelper 此类是为了简化构造SmtpClient实例所需的代码量。通过SmtpHelper构造函数设置好SMTP服务器、端口号、身份凭据,再通过链式操作快速设置SmtpClient其他不常使用的属性。 Eg: 使用SmtpHelper类注意事项: 1) 非线程安全类. 2) 构造的SmtpClient 实例外部进行Dispose()。SmtpHelper类只简单提供构造,不做释放操作。 3) SmtpClient 没有提供 Finalize() 终结器,所以GC不会进行回收,只能由外部使用完后进行显示释放,否则会发生内存泄露问题. 2. MailHelper 此类完成邮件的发送工作。需要结合MailInfoHelper静态类验证邮件信息的有效性。 1) 支持快捷添加附件、内嵌资源、地址信息、备用视图格式; Eg:添加内嵌资源 2) 支持在发送邮件前对邮件信息有效性进行检查; 3) 支持批量同步、异步发送邮件 a) 批量同步发送邮件:实际上只是 SmtpClient.Send() 同步发送邮件的一个封装。 b) 批量异步发送邮件 i. 待发送队列:因为一个SmtpClient一次只能发送一个MailMessage,不管是同步还是异步发送,所以 SmtpClient.SendAsync() 方法后必须阻塞线程直到上一封邮件发送完成,否则会抛出“正在发送邮件”的异常。所以,MailHelper为了避免调用线程的阻塞,将待发送邮件的信息都加入到队列中,内部启用一个线程去执行串行化发送任务。 ii. 限流:“异步”批量发送过程中,为了防止待发送队列无限制的增大,导致内存溢出,我们可以通过MailHelper的GetAwaitMailCountAsync()方法监控该队列的大小,适当的执行Thread.Sleep(time). iii. 异步取消:可以通过MailHelper的SendAsyncCancel()方法,取消待发送队列中的邮件继续发送。 iv. 回调函数:异步发送完一封电子邮件后执行的回调函数。通过SendCompleted事件进行注册。但要注意其AsyncCompletedEventArgs参数的UserState对象被改写为了我定义的 MailUserState 对象 MailUserState定义如下: 批量异步发送示例(注意回调函数的用户信息): 4) 批量发送邮件中,每调用一次发送方法,要使用MailHelper的Reset()对邮件内容进行重置。 注意: a) 不重置SmtpClient。SmtpClient根据 m_autoDisposeSmtp 参数自动释放或由外部主动释放 b) 不重置:异步待发送队列及队列计数、AutoResetEvent实例、执行异步发送线程变量、是否启用异步发送标识变量 5) 支持自动释放SmtpClient实例 在平常邮件开发中,当在异步批量发送邮件时,我们没办法掌握何时释放我们重用的SmtpClient实例。 但,我们使用MailHelper类,可以不用关心SmtpClient的释放问题。我们通过构造函数中指定自动释放SmtpClient的参数为true,并且统计好批量邮件发送量之后调用 SetBatchMailCount(long preCount) 方法,MailHelper就会在(批量)同步、(批量)异步邮件全部发送完之后自动释放SmtpClient实例。 a) 为什么要“重用”同一个SmtpClient实例 因为,每次发送一封电子邮件,都必须经过TCP的三次握手与服务器建立连接,这个连接信息就保存在SmtpClient实例中,所以当进行大批量的电子邮件发送时(前提是发件地址是相同的,当然大部分场景下发件地址都是相同的),有必要重用SmtpClient实例,避免TCP不断地发生“三次握手和四次挥手”。 b) 为什么要“显示释放”SmtpClient实例 SmtpClient类没有 Finalize (终结器)方法,因此应用程序"必须"调用 Dispose 来显式释放资源。 Dispose 方法在所有建立到 Host 属性中指定的 SMTP 服务器的连接中循环,并发送 QUIT 消息,其后平稳断开 TCP 连接。 MailHelper组件的一个示例以及几种方式发邮件的优劣测试 示例(博文开始处已给出链接下载)包含四次实验方案和两组复选框,如图: 示例代码下载后注意,请先修改如Config.cs文件的几处红色标识信息(如下图),你才能正常发送邮件。 用QQ邮箱发件的注意啦,要在“设置”-“账户”中将“POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”服务都开启才能正常发送邮件。如图: 实验一:单条邮件同步和异步发送(可通过添加大附件来观察同步异步效果) 观察:通过大附件观察下同步发送邮件和异步发送邮件的效果,查看下单封邮件发送MailHelper类是如何使用的。 实验二:批量邮件同步和异步发送(单个线程,单个SmtpClient实例,SendAsync()) 观察:观察下MailHelper类中批量异步发送使用队列方式实现的高响应性,以及批量操作如何自动释放SmtpClient实例。 在数量较大的批量邮件发送场景中,我们可以使用多个SmtpClient实例来并行发送,以提高整体发件效率。即实验三 + 实验四(不清楚并行类库的,请看 《异步编程:.NET4.X 数据并行》 ) 实验三:批量邮件同步和异步发送(平行类库Parallel(自动分区),每个分区一个MailHelper、SmtpClient实例) 观察:现在通过Parallel.For的自动分区 + 每个分区一个MailHelper 和SmtpClient实例来提高整体效率。但是,有个问题就是自动分区又.NET内部根据资源负载均衡自动分区,分区的效果非常不好,总会开启过多的分区导致MailHelper和SmtpClient实例偏多,并且效率不高。 实验四:批量邮件同步和异步发送(平行类库Parallel(手动分区),每个分区一个MailHelper、SmtpClient实例) 观察:在通过Parallel.Foreach的手动分区 + 每个分区一个MailHelper 和SmtpClient实例来提高整体效率。我们自己根据业务场景和Environment.ProcessorCount内核数来决定分区数,这样可以根据需要创建MailHelper和SmtpClient实例,并且效率非常高。 另外:重用SmtpClient复选款的测试结果:如果只是简单的纯文本邮件发送(即,没有耗时的附件内容),重用SmtpClient可提升50%的效率。(注意:需要使用批量同步方式发送进行测试。因为异步方式会使用多个SmtpClient进行并行发送所以测试不出效率提升) 来个整个示例截图: 本邮件发送功能分享到此结束,如果你看后觉得对你有帮助的,还请多帮推荐……推荐……如果内容有误的,还请帮忙指出,谢谢! http://www.cnblogs.com/heyuquan/p/net-batch-mail-send-async.html |