.NET开发邮件发送功能的全面教程(含邮件组件源码)
今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能。在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下:
1) 邮件基础理论知识
2) 邮件发送相关.NET类库
3) 介绍我开发的一个发送邮件的小组件(MailHelper)
4) MailHelper组件的一个示例以及几种方式发邮件的优劣测试
示例及组件源码:(有几位园友反馈IE浏览器解析不出下载地址)
.NET开发邮件发送功能的全面教程(含邮件组件源码).rar
邮件基础理论知识
什么业务需要邮件功能?
- 服务提供方:需提供邮件收发客户端或Web服务。(eg:Outlook、QQ邮箱)。当然这些服务都是知名商提供。若是一般的小网站提供的邮件收发服务,不知道节操如何,谁敢用呢?就算你用了,别的知名商SMTP服务器也不认可从这小网站发出的邮件,出现SMTP服务器拒收来源邮件(视为恶意邮件或垃圾邮件)。
- 安全性、机密性:比如某安全部门需要提供自己发邮件的SMTP服务器和收邮件POP3服务器以及相应的操作软件
- 电子商务、论坛等会员机制社区:主家需要向会员发送通知信息,比如:密码重置、降价通知、留言通知、回复通知、订阅通知、会员间交流等等。主家保证邮箱有效性的办法常常是通过会员注册、更换邮箱时发送“激活邮件”。
- 邮件营销:在大数据时代的现在,企业可以根据所掌握的数据预测客户的需求,来提供主动推送营销消息的功能(数据化运营);当然也有没有预测能力的小商家通过邮件群发器进行撒网式邮件营销。(邮件营销是:在恰当的时间传递合适的信息给需要的客户,目的是产生销售) ---- 比如“RichMail 邮件营销平台”,外贸邮件营销技巧
- 等等
什么是电子邮件协议?
当前常用的电子邮件协议有SMTP、POP3、IMAP4,它们都隶属于TCP/IP协议簇。
- SMTP
Simple Mail Transfer Protocol(即简单邮件传输协议),它是一组用于从源地址到目的地址传送邮件的规则,简单的说就是:From-->To的传送规则。由SMTP来控制信件中转的方式。SMTP属于TCP/IP家族中的一员,它帮助每一台计算机在发送或中转信件时找到下一个目的地。通过SMTP协议所指定的服务器,就可以把E-Mail寄到收信人的服务器上。SMTP服务器则是遵循SMTP协议的邮件发送服务器,用来中转你发出的电子邮件。
SMTP目前已是事实上的E-Mail传输的标准。
- POP3
Post Office Protocol 3(即邮局协议的第3个版本),负责从邮件服务器中检索电子邮件。它要求邮件服务器完成下面几种任务之一:从邮件服务器中检索邮件并从服务器中删除这个邮件;从邮件服务器中检索邮件但不删除它;不检索邮件,只是询问是否有新邮件到达。
POP3是因特网电子邮件的第一个离线协议标准。
- 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 时,表示数据段中包含控制信息。
- 三次握手,建立连接
在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状态,完成三次握手。
- 四次挥手,关闭连接
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。
1) 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
2) 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
3) 服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
4) 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
- 为什么建立连接协议是三次握手,而关闭连接却是四次挥手呢?
建立连接时,服务端LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN放在一个报文里来发送。
关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以关闭连接的ACK报文和FIN报文多数情况下都是分开发送的。
常见的邮箱类型有哪些?
常见的邮箱类型有:免费邮箱、vip邮箱、域名邮箱、企业邮箱等等。
“免费邮箱”是邮件商家为任何人免费提供的电子邮件传输服务,作为交换,该网站上你请求电子邮件服务和一些个人信息的地方会显示广告。它更适合个人生活和娱乐的需要,却并非那么注重邮箱的安全和功能。
部分免费邮件SMTP服务器参考设置:
Email类型 |
SMTP[Host]主服务器 |
Port[端口号] |
是否可启用SSL |
Gmail(Google 的网络邮件服务) |
smtp.gmail.com |
587 |
True |
HotMail/Live |
smtp.live.com |
25 |
True |
QQ/FoxMail(Foxmail被腾讯收购) |
smtp.qq.com |
25 |
False |
smtp.126.com |
25 |
False |
|
smtp.163.com |
25 |
False |
|
Sina(新浪邮箱) |
smtp.sina.com |
25 |
False |
Tom |
smtp.tom.com |
25 |
False |
SoHu(搜狐邮箱) |
smtp.sohu.com |
25 |
False |
smtp.mail.yahoo.com |
25 |
False |
- vip邮箱
“vip邮箱”即邮件商家提供的收费版邮件服务,在速度、安全、稳定性、容量、附件大小限制、群发数等方面相对好些。其SMTP服务器设置就是多了个vip字符。eg:smtp.vip.qq.com。邮箱地址:369220123@vip.qq.com。
“域名邮箱”是个性化邮件服务,能让您用自己的域名做为后缀即“@自己的域名”,前提是你需要一个域名(通常域名要收费)。功能比免费邮箱要多:可分配单个邮箱、规划容量、更加的安全、更好的稳定性、个性化名称、邮件发送量更大、附件大小限制等等。
“企业邮箱”是域名邮箱,但通常是指通过付费方式获得更好服务的邮箱。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类。
- MailMessage 类表示邮件的内容。
MailMessage常用属性 |
||||||
From |
MailAddress |
获取或设置此电子邮件的发信人地址。 两者区别:當 Sender 與 From 都有設定時,Mail Server 會取用Sender 的設定發信,但郵件上的名稱會使用 From 的設定,而若不需要 Sender 和 From 同時設定時,則 Sender 可以免設,但From 一定要設。详细请看:《MailMessage 的 Sender 和 From?傻傻分不清楚》 |
||||
Sender |
||||||
To |
MailAddressCollection |
获取包含此电子邮件的收件人的地址集合。 |
||||
CC |
MailAddressCollection |
获取包含此电子邮件的抄送 (CC) 收件人的地址集合。 |
||||
Bcc |
MailAddressCollection |
获取包含此电子邮件的密件抄送(BCC) 收件人的地址集合。 |
||||
Attachments |
AttachmentCollection |
获取用于存储附加到此电子邮件的数据的附件集合。 |
||||
Headers |
NameValueCollection |
获取与此电子邮件一起传输的电子邮件标头。(例如用于:发送DKIM签名的邮件) |
||||
Subject |
string |
获取或设置此电子邮件的主题。 |
||||
Body |
string |
获取或设置邮件正文。 |
||||
AlternateViews |
AlternateViewCollection |
指定一个电子邮件不同格式显示的副本。(eg:发送HTML格式的邮件,可能希望同时提供邮件的纯文本格式,以防止一些收件人使用的电子邮件阅读程序无法显示html内容) |
||||
IsBodyHtml |
bool |
默认false。获取或设置指示邮件正文是否为 Html 格式的值。 |
||||
Priority |
MailPriority |
默认Normal。获取或设置此电子邮件的优先级。(Normal | Low| High) |
||||
HeadersEncoding |
Encoding |
获取或设置此电子邮件的用户定义的自定义标题使用的编码。 |
||||
SubjectEncoding |
Encoding |
获取或设置此电子邮件的主题内容使用的编码。 |
||||
BodyEncoding |
Encoding |
获取或设置用于邮件正文的编码。 |
||||
ReplyToList |
MailAddressCollection |
设置接收方回复邮件时默认的接收地址,eg:你用一个邮箱发信,但却用另一个来收信。 (ReplyTo,表示单个回复地址,已过期,使用ReplyToList代替) |
||||
下面属性想不到用在什么场景……请高人指出使用案例,谢谢! |
||||||
DeliveryNotificationOptions |
DeliveryNotificationOptions |
默认None。获取或设置此电子邮件的发送通知。
不懂干嘛的,设置为OnSuccess,不会回复我发送成功。设置为Never,发送失败也会回复我。。。 |
||||
- SmtpClient类用于将电子邮件发送到 SMTP 服务器以便传递。
SmtpClient常用属性 |
||||
Host |
string |
获取或设置用于 SMTP 事务的主机的名称或 IP 地址。 |
||
Port |
int |
获取或设置用于 SMTP 事务的端口。 |
||
UseDefaultCredentials |
bool |
默认false。 若要使用默认网络凭据,可以将UseDefaultCredentials设置为 true,此时System.Net.CredentialCache.DefaultCredentials(应用程序系统凭证)会随请求一起发送。 如果UseDefaultCredentials属性设置为 false,则连接到服务器时会将 Credentials 属性中设置的值用作凭据。如果UseDefaultCredentials属性设置为 false 并且尚未设置 Credentials 属性,则将邮件以匿名方式发送到服务器。若SMTP 服务器要求在验证客户端的身份则会抛出异常。 |
||
Credentials |
ICredentialsByHost |
获取或设置用于验证发件人身份的凭据。 |
||
ClientCertificates |
X509CertificateCollection |
指定应该使用哪些证书来建立安全套接字层 (SSL) 连接。 |
||
EnableSsl |
bool |
默认false。指定SmtpClient是否使用安全套接字层 (SSL)加密连接。 |
||
Timeout |
int |
默认100000.获取或设置一个值,该值指定同步重载:SmtpClient.Send()调用的超时时间。 |
||
自建本地SMTP服务器获取邮件时需要使用的属性: |
||||
DeliveryMethod |
SmtpDeliveryMethod |
默认NetworkCredential。
|
||
PickupDirectoryLocation |
string |
获取或设置文件夹,应用程序在该文件夹中保存将由本地 SMTP 服务器处理的邮件。 |
||
下面属性想不到用在什么场景……请高人指出使用案例,谢谢! |
||||
TargetName |
string |
"SMTPSVC/" + this.host。获取或设置在使用扩展保护时用于身份验证的服务提供程序名称 (SPN)。 |
||
ServicePoint |
ServicePoint |
获取用于传输电子邮件的网络连接。(应该会保存TCP连接,避免再次进行TCP的三次握手???) |
- 一个简单的邮件发送示例
1
2
3
4
5
6
7
8
9
|
MailMessage mail = new MailMessage(); mail.From = new MailAddress(From, FromDisplayName); mail.To.Add( new MailAddress(To, ToDisplayName)); mail.Subject = "this is a test email." ; mail.Body = "this is my test email body.<br><b>this part is in bold</b>" ; mail.IsBodyHtml = true ; SmtpClient smtp = new SmtpClient(host, port); smtp.Credentials = new NetworkCredential(userName, password); smtp.Send(mail); |
- 邮件扩展:如何发送内嵌资源(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路径字符串的替代即可。
代码如下:(详细见示例代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
string picPath = Environment.CurrentDirectory + "\\附件\\PIC_Mail中文.png" ; Attachment attach_pic = new Attachment(picPath); // 获取或设置此附件的 MIME 内容 ID。 attach_pic.ContentId = "MyPic" ; // 实例邮件内容 System.Net.Mime.ContentDisposition disposition = attach_pic.ContentDisposition; // 若为内联,则不会以附件的形式显示,而是直接显示为邮件内容 disposition.Inline = true ; FileInfo file = new FileInfo(picPath); // 设置文件附件的创建日期。 disposition.CreationDate = file.CreationTime; // 设置文件附件的修改日期。 disposition.ModificationDate = file.LastWriteTime; // 设置文件附件的读取日期。 disposition.ReadDate = file.LastAccessTime; // 设定文件名称 (内嵌资源设置文件名后下载下来才有默认后缀) disposition.FileName = file.Name.ToString(); mail.AddAttachment(attach_pic); |
另外,可使用AlternateView类和LinkedResource类来实现内嵌资源……
1) 创建一个MailMessage对象,同时指定发送人和接收人地址。
2) 创建AlternateView来接收文本内容,创建LinkedResource来接收要嵌入的图片或其他资源。
3) 添加LinkedResource到AlternateView
4) 添加AlternateView到MailMessage
5) 设置SmtpClient,发送email
- 发送DKIM签名的邮件(感谢@dudu提点)
DKIM(DomainKeys Identified Mail,域秘钥识别邮件)只是为了实现一定程度上的认证,而并不是为了提供一种强大的加密认证机制。因此DKIM的主要应用前景是在即使被攻击或认证失败也不会有很大损失的“反垃圾邮件等低危险性领域”。在反垃圾邮件方面,DKIM能够有效地限制垃圾邮件发送者盗用其他机构或域名的名义,为邮件过滤提供鉴别手段,并且能够在现有邮件体系下快速进行低成本的部署。(垃圾邮件发件人可以伪造邮件上的“发件人”地址,使垃圾邮件看起来好像来自您所在域中的某个用户。)
DKIM是基于域名的,而不是整个邮件地址。签名是由域名的管理者控制而不是单独的邮件用户(这也是与用户个体提供证书签名方式的明显区别)。
具体如何签名?如何创建DKIM密钥?如何使用开源库DKIM.Net.dll发送DKIM签名的邮件?我们跳转到dudu站长的博文:《C#发送DKIM签名的邮件》
我开发的一个发送邮件的小组件(代码在博文开始处已给出下载地址)
为了简化邮件发送代码编写和SmtpClient实例的管理,我封装了一个发邮件的帮助类。
这个帮助类,包含如图几个文件:
两个主要类: SmtpHelper 和MailHelper
- SmtpHelper
此类是为了简化构造SmtpClient实例所需的代码量。通过SmtpHelper构造函数设置好SMTP服务器、端口号、身份凭据,再通过链式操作快速设置SmtpClient其他不常使用的属性。
Eg:
1
2
3
|
SmtpClient client = new SmtpHelper( host, port, false , userName, password) .SetTimeout(60*1000) .SmtpClient; |
使用SmtpHelper类注意事项:
1) 非线程安全类.
2) 构造的SmtpClient 实例由外部进行Dispose()。SmtpHelper类只简单提供构造,不做释放操作。
3) SmtpClient 没有提供 Finalize() 终结器,所以GC不会进行回收,只能由外部使用完后进行显示释放,否则会发生内存泄露问题.
- MailHelper
此类完成邮件的发送工作。需要结合MailInfoHelper静态类验证邮件信息的有效性。
1) 支持快捷添加附件、内嵌资源、地址信息、备用视图格式;
Eg:添加内嵌资源
1
2
3
|
// 邮件内容:"<a href=\"cid:MyPic\" target=\"_blank\">点击在新窗口打开图片</a>"; string picPath = Environment.CurrentDirectory + "\\附件\\PIC_Mail中文.png" ; mail.AddInlineAttachment(picPath, "MyPic" ); |
2) 支持在发送邮件前对邮件信息有效性进行检查;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Dictionary<MailInfoType, string > dic = mail.CheckSendMail(); if (dic.Count > 0 && MailInfoHelper.ExistsError(dic)) { // 反馈“错误+提示”信息 msg = MailInfoHelper.GetMailInfoStr(dic)) } else { if (dic.Count > 0) { // 反馈“提示”信息,但还是可以发送邮件 msg = MailInfoHelper.GetMailInfoStr(dic); } // 发送邮件 } |
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定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/// <summary> /// 异步发送邮件时保存的信息,用于释放和传递数据 /// </summary> public class MailUserState { #region 由MailHelper内部的SendCompleted注册的事件使用 // 用于释放 MailMessage 和 SmtpClient public MailMessage CurMailMessage { get ; set ; } public bool AutoReleaseSmtp { get ; set ; } public SmtpClient CurSmtpClient { get ; set ; } // 只发送单封邮件的时候使用此进行判断释放 public bool IsSmpleMail { get ; set ; } #endregion /// <summary> /// 用户传递的状态对象 /// </summary> public object UserState { get ; set ; } /// <summary> /// 当异步发送报错时可通过此标识是否已经处理该异常 /// </summary> public bool IsErrorHandle { get ; set ; } } |
批量异步发送示例(注意回调函数的用户信息):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 设置SmtpClient的回调函数 client.SendCompleted +=(send,args) => { AsyncCompletedEventArgs arg = args; MailUserState userState = arg.UserState as MailUserState; } // 在MailHelper的构造函数中决定是同步发送还是异步发送邮件 MailHelper mail = new MailHelper(client, true ,isAsync); for ( long i = 1; i <= mailCount; i++) { if (mail.GetAwaitMailCountAsync()>1000) { // 当待发送队列大于1000时,线程休眠1秒 Thread.Sleep(1000); } // 设置 MailHelper 发送信息 // …… // 设置每封电子邮件发送完执行回调函数的UserState mail.AsyncUserState = “你传递的对象信息”; // 执行批量发送邮件 mail.SendBatchMail(); } mail.SetBatchMailCount(count); |
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进行并行发送所以测试不出效率提升)
来个整个示例截图:
有园友反馈报如下错误:
我当时弄测试的时候偶尔出现就没注意这个问题了,现在也不知道具体原因,猜测是邮箱服务器的一些限制吧!换个其他邮箱就可以了;或则注释掉下面内嵌图片:
另外解决方案,调整代码如下:
1
2
3
4
5
6
7
|
foreach ( string filePath in FilePaths) { //会出现 在邮件标头中找到无效的字符:“周”的错误,改为如下写法 //mail.AddAttachment(filePath); Attachment fileAttachment = new Attachment(filePath, MediaTypeNames.Application.Octet); mail.AddAttachment(fileAttachment); } |