您的位置:首页 > 编程语言 > C#

C#邮件发送问题(一)

2014-05-16 17:51 288 查看
邮件发送需考虑很多因素,包括发送邮件客户端(一般编码实现),发送和接收邮件服务器设置等。如果使用第三方邮件服务器作为发送服务器,就需要考虑该服务器的发送限制,(如发送邮件时间间隔,单位时间内发送邮件数量,是否使用安全连接SSL),同时无论使用第三方还是自己的邮件服务器都还需要考虑接收邮件服务器的限制。为理清思路,下面我们简单回顾电子邮件系统的基本网络结构和邮件发送接收流程。

一、电子邮件系统的基本网络结构

如下图:





邮件发送接收一般经过以下几个节点:

发送邮件客户端(MailUserAgent,MUA):Formail,Outlook,Webmail,C#Code,JavaCode,etc.

发送邮件服务器(MailTransferAgent,MTA):hMailServer,Exchange,TurboMail,etc.

接收邮件服务器(MailTransferAgent,MTA)

接收邮件客户端(MailUserAgent,MUA)

发送过程中客户端与服务器及服务器之间使用SMTP协议,在接收过程中客户端与服务端之间使用POP3或IMAP(POP3的替代协议,支持邮件摘要显示和脱机操作)。邮件发送可简单认为是一种文件传输,但与FTP实时文件传输不同,各邮件服务器会保存邮件文件本身,直至被下一个邮件服务器或客户端接收,类似异步与同步的差别。

由上可知,为顺利发送和接受邮件,客户端设置或编码需要严格适应邮件服务器的要求。对于发送邮件需明确:SMTP服务器地址和端口(默认端口25),是否使用安全连接(SSL),验证凭据(用户和密码),及更加细节的邮件格式,邮件编码方式等;对于接收邮件需明确:POP3或IMAP服务器地址和端口(POP3默认端口110,IMAP默认端口143),是否使用安全连接(SSL),验证凭据(用户和密码)

二、C#下发送邮件组件及测试

C#下发送邮件的组件使用较为普遍的有以下三个:System.Net.Mail,OpenSmtp,LumiSoft.Net。下面我们就分别对他们进行测试。

发送邮件至少需要发送邮件服务器信息和邮件信息,因此我们建立Host和Mail两个配置类。

publicclassConfigHost
{
publicstringServer{get;set;}
publicintPort{get;set;}
publicstringUsername{get;set;}
publicstringPassword{get;set;}
publicboolEnableSsl{get;set;}
}

publicclassConfigMail
{
publicstringFrom{get;set;}
publicstring[]To{get;set;}
publicstringSubject{get;set;}
publicstringBody{get;set;}
publicstring[]Attachments{get;set;}
publicstring[]Resources{get;set;}
}


同时定义一个统一的接口ISendMail,以方便测试和比较。

publicinterfaceISendMail
{
voidCreateHost(ConfigHosthost);
voidCreateMail(ConfigMailmail);
voidCreateMultiMail(ConfigMailmail);
voidSendMail();
}


1、使用System.Net.Mail

System.Net.Mail属于.NetFramework的一部分,.Net2.0以后可以使用这个组件。

usingSystem.Net.Mail;
publicclassUseNetMail:ISendMail
{
privateMailMessageMail{get;set;}
privateSmtpClientHost{get;set;}

publicvoidCreateHost(ConfigHosthost)
{
Host=newSmtpClient(host.Server,host.Port);
Host.Credentials=newSystem.Net.NetworkCredential(host.Username,host.Password);
Host.EnableSsl=host.EnableSsl;
}

publicvoidCreateMail(ConfigMailmail)
{
Mail=newMailMessage();
Mail.From=newMailAddress(mail.From);

foreach(vartinmail.To)
Mail.To.Add(t);

Mail.Subject=mail.Subject;
Mail.Body=mail.Body;
Mail.IsBodyHtml=true;
Mail.BodyEncoding=System.Text.Encoding.UTF8;
}

publicvoidCreateMultiMail(ConfigMailmail)
{
CreateMail(mail);

Mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString("Ifyouseethismessage,itmeansthatyourmailclientdoesnotsupporthtml.",Encoding.UTF8,"text/plain"));

varhtml=AlternateView.CreateAlternateViewFromString(mail.Body,Encoding.UTF8,"text/html");
foreach(stringresourceinmail.Resources)
{
varimage=newLinkedResource(resource,"image/jpeg");
image.ContentId=Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource)));
html.LinkedResources.Add(image);
}
Mail.AlternateViews.Add(html);

foreach(varattachmentinmail.Attachments)
{
Mail.Attachments.Add(newAttachment(attachment));
}
}

publicvoidSendMail()
{
if(Host!=null&&Mail!=null)
Host.Send(Mail);
else
thrownewException("Theseisnotahosttosendmailorthereisnotamailneedtobesent.");
}
}



2、使用OpenSmtp
开源的发送邮件组件,可以在这里获得源码。但是OpenSmtp目前不支持SSL。

usingOpenSmtp.Mail;
publicclassUseOpenSmtp:ISendMail
{
privateMailMessageMail{get;set;}
privateSmtpHost{get;set;}

publicvoidCreateHost(ConfigHosthost)
{
Host=newSmtp(host.Server,host.Username,host.Password,host.Port);
}

publicvoidCreateMail(ConfigMailmail)
{
Mail=newMailMessage();
Mail.From=newEmailAddress(mail.From);
foreach(vartinmail.To)
Mail.AddRecipient(t,AddressType.To);

Mail.HtmlBody=mail.Body;
Mail.Subject=mail.Subject;
Mail.Charset="UTF-8";
}

publicvoidCreateMultiMail(ConfigMailmail)
{
CreateMail(mail);
foreach(varattachmentinmail.Attachments)
{
Mail.AddAttachment(attachment);
}
foreach(varresourceinmail.Resources)
{
Mail.AddImage(resource,Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource))));
}
}

publicvoidSendMail()
{
if(Host!=null&&Mail!=null)
Host.SendMail(Mail);
else
thrownewException("Theseisnotahosttosendmailorthereisnotamailneedtobesent.");
}


3、使用LumiSoft.Net

LumiSoft.Net是非常强大的开源组件,不仅仅发送邮件,同样也可用于接收邮件,是个人认为最好的开源组件了。在这里可以详细了解LumiSoft.Net组件的命名空间,也可以在这里下载其源码和样例。

usingLumiSoft.Net.SMTP.Client;
usingLumiSoft.Net.AUTH;
usingLumiSoft.Net.Mail;
usingLumiSoft.Net.MIME;
publicclassUseLumiSoft:ISendMail
{
privateSMTP_ClientHost{get;set;}
privateMail_MessageMail{get;set;}

publicvoidCreateHost(ConfigHosthost)
{
Host=newSMTP_Client();
Host.Connect(host.Server,host.Port,host.EnableSsl);
Host.EhloHelo(host.Server);
Host.Auth(Host.AuthGetStrongestMethod(host.Username,host.Password));
}

publicvoidCreateMail(ConfigMailmail)
{
Mail=newMail_Message();
Mail.Subject=mail.Subject;
Mail.From=newMail_t_MailboxList();
Mail.From.Add(newMail_t_Mailbox(mail.From,mail.From));
Mail.To=newMail_t_AddressList();
foreach(vartoinmail.To)
{
Mail.To.Add(newMail_t_Mailbox(to,to));
}
varbody=newMIME_b_Text(MIME_MediaTypes.Text.html);
Mail.Body=body;//Needtobeassignedfirstorwillthrow"Bodymustbeboundedtosomeentityfirst"exception.
body.SetText(MIME_TransferEncodings.Base64,Encoding.UTF8,mail.Body);
}

publicvoidCreateMultiMail(ConfigMailmail)
{
CreateMail(mail);

varcontentTypeMixed=newMIME_h_ContentType(MIME_MediaTypes.Multipart.mixed);
contentTypeMixed.Param_Boundary=Guid.NewGuid().ToString().Replace("-","_");
varmultipartMixed=newMIME_b_MultipartMixed(contentTypeMixed);
Mail.Body=multipartMixed;

//Createaentitytoholdmultipart/alternativebody
varentityAlternative=newMIME_Entity();
varcontentTypeAlternative=newMIME_h_ContentType(MIME_MediaTypes.Multipart.alternative);
contentTypeAlternative.Param_Boundary=Guid.NewGuid().ToString().Replace("-","_");
varmultipartAlternative=newMIME_b_MultipartAlternative(contentTypeAlternative);
entityAlternative.Body=multipartAlternative;
multipartMixed.BodyParts.Add(entityAlternative);

varentityTextPlain=newMIME_Entity();
varplain=newMIME_b_Text(MIME_MediaTypes.Text.plain);
entityTextPlain.Body=plain;
plain.SetText(MIME_TransferEncodings.Base64,Encoding.UTF8,"Ifyouseethismessage,itmeansthatyourmailclientdoesnotsupporthtml.");
multipartAlternative.BodyParts.Add(entityTextPlain);

varentityTextHtml=newMIME_Entity();
varhtml=newMIME_b_Text(MIME_MediaTypes.Text.html);
entityTextHtml.Body=html;
html.SetText(MIME_TransferEncodings.Base64,Encoding.UTF8,mail.Body);
multipartAlternative.BodyParts.Add(entityTextHtml);

foreach(stringattachmentinmail.Attachments)
{
multipartMixed.BodyParts.Add(Mail_Message.CreateAttachment(attachment));
}

foreach(stringresourceinmail.Resources)
{
varentity=newMIME_Entity();
entity.ContentDisposition=newMIME_h_ContentDisposition(MIME_DispositionTypes.Inline);
entity.ContentID=Convert.ToBase64String(Encoding.Default.GetBytes(Path.GetFileName(resource)));//eg.<imgsrc="cid:ContentID"/>
varimage=newMIME_b_Image(MIME_MediaTypes.Image.jpeg);
entity.Body=image;
image.SetDataFromFile(resource,MIME_TransferEncodings.Base64);
multipartMixed.BodyParts.Add(entity);
}
}

publicvoidSendMail()
{
if(Host!=null&&Mail!=null)
{
foreach(Mail_t_MailboxfrominMail.From.ToArray())
{
Host.MailFrom(from.Address,-1);
}
foreach(Mail_t_MailboxtoinMail.To)
{
Host.RcptTo(to.Address);
}
using(varstream=newMemoryStream())
{
Mail.ToStream(stream,newMIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8);
stream.Position=0;//Needtoberesetto0,otherwisenothingwillbesent;
Host.SendMessage(stream);
Host.Disconnect();
}
}
else
thrownewException("Theseisnotahosttosendmailorthereisnotamailneedtobesent.");
}
}


阅读LumiSoft.Net的源代码,可以看到LumiSoft.Net编程严格遵循了RFC(RequestForComments)定义的协议规范。通过阅读这些源码对于了解RFC和其中关于邮件网络协议规范也是非常有帮助的。如果想查阅RFC文档可以通过这个链接。

在上面的代码中MIME_MediaTypes类,MIME_TransferEncodings类和Encoding类(System.Text.Encoding)都是或类似于枚举,设置了邮件内容的编码方式或解析方式,这个几个类从根本上决定了邮件的正常传输和显示。MIME_TransferEncodings类设置了文件传输编码,决定邮件头中的Content-Transfer-Encoding字段的值及其他需要传输编码字段的编码方式(如标题中的多国语言)。MIME_MediaTypes类设置邮件各部分内容的类型,决定邮件中Content-Type字段的值。而Encoding类不用说,决定了charset的值。关于这些设置的具体作用下文还将提到,这里略过。

4、测试

下表是通过网络搜集的各大SMTP服务器的配置情况,可以选择使用这些配置进行测试:

服务商SMTP地址SMTP端口EnableSsl
gmailsmtp.google.com25,465or587true
126smtp.126.com25false
163smtp.126.com25false
hotmailsmtp.live.com25true
sinasmtp.sina.com25false
sohusmtp.sohu.com25false
新建控制台应用程序,测试发送只包含正文的简单邮件:

classProgram
{
staticvoidMain(string[]args)
{varh1=newConfigHost()
{
Server="smtp.gmail.com",
Port=465,
Username="******@gmail.com",
Password="******",
EnableSsl=true
};

varm1=newConfigMail()
{
Subject="Test",
Body="Justatest.",
From="******@gmail.com",
To=newstring[]{"******@gmail.com"},

};

varagents=newList<ISendMail>(){newUseNetMail(),newUseOpenSmtp(),newUseLumiSoft()};
foreach(varagentinagents)
{
varoutput="Sendm1viah1"+agent.GetType().Name+"";
Console.WriteLine(output+"start");
try
{
agent.CreateHost(h1);
m1.Subject=output;
agent.CreateMail(m1);
agent.SendMail();
Console.WriteLine(output+"success");
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output+"end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}


通过gmail发送邮件时,OpenSmtp由于不支持SSL发送失败,NetMail使用587端口能够成功发送,LumiSoft使用465端口能够成功发送。查阅Gmail相关文档,描述说Gmail的465端口使用SSL协议,而587端口使用TLS协议,但587是需要STARTTLS命令支持才能提升为TLS。在命令提示符下测试发现的确需要在发送STARTTLS命令后才能使用TLS协议:

>telnetsmtp.gmail.com587
220mx.google.comESMTPo5sm40420786eeg.8-gsmtp
EHLOg1
250-mx.google.comatyourservice,[173.231.8.212]
250-SIZE35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250CHUNKING
AUTHLOGIN
5305.7.0MustissueaSTARTTLScommandfirst.o5sm40420786eeg.8–gsmtpSTARTTLS220

STARTTLS

2.0.0ReadytostartTLS


QUIT


对于TLS与STARTTLS人们经常搞混,这里找到一篇关于它们的解释,请点击这里。

因而LumiSoft如果连接gmail服务器时还需明确发送STARTTLS命令,已经发现LumiSoft有相关方法SMTP_Client.StartTLS(),连接gmail相较其他smtp服务器还是较为复杂些。另外一些服务器要求邮件配置中的Username必须与From相一致,需要特别注意。

测试发送带附件和内嵌资源的邮件:

classProgram
{
staticvoidMain(string[]args)
{
varh2=newConfigHost()
{
Server="smtp.163.com",
Port=25,
Username="******@163.com",
Password="******",
EnableSsl=false
};
varm2=newConfigMail()
{
Subject="Test",
Body="Justatest.<br/><imgsrc='cid:"+Convert.ToBase64String(Encoding.Default.GetBytes("Resource.jpg"))+"'alt=''/>",

From="******@163.com",
To=newstring[]{"******@163.com"},

Attachments=newstring[]{@"E:\Test\SendMail\Attachment.pdf"},

Resources=newstring[]{@"E:\Test\SendMail\Resource.jpg"}
};
varagents=newList<ISendMail>(){newUseNetMail(),newUseOpenSmtp(),newUseLumiSoft()};
foreach(varagentinagents)
{
varoutput="Sendm2viah2"+agent.GetType().Name+"";
Console.WriteLine(output+"start");
try
{
agent.CreateHost(h2);
m2.Subject=output;
agent.CreateMultiMail(m2);
agent.SendMail();
Console.WriteLine(output+"success");
}
catch(Exceptionex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output+"end");
Console.WriteLine("-----------------------------------");
}
Console.Read();
}
}


C#邮件发送问题(二)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: