C#邮件发送问题(一)

三、C#下创建基于TcpClient发送邮件组件

在上一节在Dos命令行下测试SMTP服务器连接时,已经使用了SMTP的部分命令,但是当时无法对信息进行编码和解码,也就无法继续进行身份验证和信息传输。在.Net库中,我们可以使用System.Net.Sockets.TcpClient类实现上一节发送邮件组件的同样功能(其实OpenSmtp也同样是基于这个组件开发的),这里仅作为测试以充分了解SMTP协议规范。

1、SMTP命令及其响应

邮件发送的基本过程是一问一答的方式与服务器交流的,所以我们需要先了解关于SMTP命令及其响应,详情请查阅RFC821

常用的SMTP/ESMTP命令(命令的执行有一定顺序)包括:

命令 作用
HELO 使用标准的SMTP,向服务器标识用户身份
EHLO 使用ESMTP,向服务器标识用户身份,针对支持ESMTP的服务器
STARTTLS 启用TLS,将普通连接提升为安全连接,针对支持STARTTLS 的服务器
AUTH LOGIN 开始认证程序
MAIL FROM 指定发件人地址
RCPT TO 指定单个邮件接收人;可以有多个RCPT TO
DATA 传输数据,服务器接收到<CRLF>.<CRLF>就停止接收数据
VRFY 验证指定的用户/邮箱是否存在,常被禁用
EXPN 验证指定的邮箱列表是否存在,常被禁用
HELP 查询服务器支持的命令
NOOP 无操作,服务器响应 250 OK
RSET 重置会话,取消当前传输,服务器响应 250 OK
QUIT 结束会话

常见SMTP服务器响应:

500 语法错误,未知命令
501 参数语法错误
502 命令未执行
503 命令顺序错误
504 参数未赋值
211 系统状态,或者系统帮助响应
214 帮助信息
220 <domain> 服务就绪
221 <domain> 服务正在关闭传输通道
421 <domain> 服务不可用,正在关闭传输通道
250 操作完成
251 非本地用户;将转发至 <forward-path>
450 操作未完成:邮箱不可用[例如:邮箱忙]
550 操作未完成:邮箱不可用[例如:邮箱不存在,不可访问]
451 操作取消:处理过程中出错
551 非本地用户;请尝试 <forward-path>
452 操作未完成:系统存储空间不足
552 操作取消:超过分配的存储空间
553 操作未完成:邮箱名不可用[例如:邮箱名语法错误]
354 开始邮件数据输入,以 <CRLF>.<CRLF> 结束
554 操作失败

所以如果我们在控制台输出邮件发送全过程应该大体如下(不同服务器反馈的信息不同,且如果发送带多媒体邮件结构更为复杂),其中Receive是服务器接收数据,Send是向服务器发送数据:

Send:    EHLO g1
Receive: -mail
-PIPELINING
-AUTH LOGIN PLAIN
-AUTH=LOGIN PLAIN
-STARTTLS
8BITMIME
Send: AUTH LOGIN
Receive: dXNlcm5hbWU6
Send: cWluZ3NwYWNl
Receive: UGFzc3dvcmQ6
Send: NINULFzLnhtdQ==
Receive: Authentication successful
Send: MAIL FROM: ******@***.com
Receive: Mail OK
Send: RCPT TO: <******@***.com>
Receive: Mail OK
Send: DATA
Receive: End data with <CR><LF>.<CR><LF>
Send: From: <<******@***.com>
Send: To: <<******@***.com>
Send: Subject: =?utf-?B?5Y+R6YCBIG0yIHZpYSBoMiBVc2VUY3BDbGllbnQg?=
Send: Date: Fri, May :: GMT
Send: MIME-Version: 1.0
Send: Content-Type: text/html;
Send: charset="utf-8"
Send: Content-Transfer-Encoding: base64
Send:
Send: 5rWL6K+V44CCSnVzdCBhIHRlc3QuPGJyLz48aW1nIHNyYz0nY2lkOlVtVnpiM1Z5WTJVdWFu
Send: Qm4nIGFsdD0nJy8+
Send: .
Receive: Mail OK queued as AgAi0gCXn8M0Z3VTmF4QAA--.4500S2
Send: QUIT
Receive: Bye

2、C#编码实现邮件发送

接下来我们基于.Net类库中TcpClient类实现与服务器的交互:

先建立同样继承于ISendMail接口的类UseTcpClient

同时设定一个内部类Message作为数据载体,定义utf-8作为全局的字符编码,定义base64为全局的传输编码。

    using System.Net.Sockets;
public class UseTcpClient : ISendMail
{
private TcpClient Tcp { get; set; }
private Stream Stream { get; set; }
private Message Mail { get; set; } private string ContentTransferEncoding = "base64";
private Encoding Charset = Encoding.UTF8; private class Message
{
public Message()
{ } public Message(string from, string[] to)
{
From = from;
To = to;
Data = new List<string>();
}
public string From { get; set; }
public string[] To { get; set; }
public List<string> Data { get; set; }
} public void CreateHost(ConfigHost host)
{
throw new NotImplementedException();
} public void CreateMail(ConfigMail mail)
{
throw new NotImplementedException();
} public void CreateMultiMail(ConfigMail mail)
{
throw new NotImplementedException();
} public void SendMail()
{
throw new NotImplementedException();
} }

接下来实现CreateHost方法

在使用SSL连接服务器时需要将TcpClient.GetStream()返回的NetworkStream使用SslStream进行包装。在于服务器进行前期沟通的过程中,一问一答式是显而易见的

        public void CreateHost(ConfigHost host)
{
if (host.Server != null && host.Port != )
{
Tcp = new TcpClient(host.Server, host.Port);
Tcp.SendTimeout = ;
Tcp.SendBufferSize = ;
Tcp.ReceiveTimeout = ;
Tcp.ReceiveBufferSize = ; if (host.EnableSsl)
{
var ssl = new SslStream(Tcp.GetStream());
ssl.AuthenticateAsClient(host.Server, null, System.Security.Authentication.SslProtocols.Tls, false);
Stream = ssl;
}
else
Stream = Tcp.GetStream(); LingerOption lingerOption = new LingerOption(true, );
Tcp.LingerState = lingerOption;
CheckErrorCode(ReadStream(), "");
if (!string.IsNullOrEmpty(host.Username) && !string.IsNullOrEmpty(host.Password))
{
WriteStream("EHLO " + Dns.GetHostName() + "\r\n");
CheckErrorCode(ReadStream(), "");
WriteStream("AUTH LOGIN\r\n");
if (CheckReplyCode(ReadStream(), ""))
{
WriteStream(ConvertToBase64(host.Username) + "\r\n");
CheckErrorCode(ReadStream(), "");
WriteStream(ConvertToBase64(host.Password) + "\r\n");
CheckErrorCode(ReadStream(), "");
}
}
else
{
WriteStream("HELO " + Dns.GetHostName() + "\r\n");
CheckErrorCode(ReadStream(), "");
}
}
}

我们使用WriteStream()方法发送命令和数据,ReadStream()方法获得服务器反馈,CheckErrorCode()和CheckReplyCode()方法判断反馈的信息不是异常,以确保进行下一步。

由于TcpClient发送的数据是有限制的,因而当发送较长数据时最好将数据分几次发送。其实这样依然会带来问题,由于我们采用同步写入数据流的方式,大数据如附件的发送常常会因网络传输或服务器交互问题造成异常,因而在LumiSoft项目采用的是异步方式,这里我们全当测试,测试时使用较小的附件以避免这样的问题。

        private void WriteStream(string request)
{
byte[] buffer = Charset.GetBytes(request);
var pageSize = ;
var totalPages = (int)Math.Ceiling(((double)buffer.Length) / pageSize);
for (var i = ; i < totalPages; i++)
{
Stream.Write(buffer, i * pageSize, i == totalPages - ? buffer.Length - i * pageSize : pageSize);
Console.WriteLine("Send(" + i + "):" + Charset.GetString(buffer, i * pageSize, i == totalPages - ? buffer.Length - i * pageSize : pageSize));
}
} private string ReadStream()
{
var buffer = new byte[];
var size = Stream.Read(buffer, , buffer.Length);
var response = Charset.GetString(buffer, , size);
Console.WriteLine("Receive: " + response);
return response;
} private void CheckErrorCode(string response, string code)
{
if (response.IndexOf(code) == -)
{
throw new Exception("Exception: " + response);
}
} private bool CheckReplyCode(string response, string code)
{
if (response.IndexOf(code) == -)
{
return false;
}
else
{
return true;
}
}

下面的一些方法用来对数据进行base64编码,以便在网络中传输。

ConvertToBase64()方法:

ConvertToBase64()方法只是简单的将utf-8编码的字符串进行base64编码。传输编码定义了邮件标题、正文(包含多国语言),附件、嵌入资源(二进制数据)等转换为特定字符集的方式,以便适应纯文本的邮件传输环境。主要编码方式有quoted-printablebase64

“Base64编码是将输入的数据全部转换成由64个指定ASCII字符组成的字符序列,这64个字符由{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}构成。编码时将需要转换的数据每次取出6bit,然后将其转换成十进制数字,这个数字的范围最小为0,最大为63,然后查询{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}构成的字典表,输出对应位置的ASCII码字符,这样每3个字节的数据内容会被转换成4个字典中的ASCII码字符,当转换到数据末尾不足3个字节时,则用“=”来填充。 ”

“Quoted-printable编码也是将输入的信息转换成可打印的ASCII码字符,但它是根据信息的内容来决定是否进行编码,如果读入的字节处于33-60、62-126范围内的,这些都是可直接打印的ASCII字符,则直接输出,如果不是,则将该字节分为两个4bit,每个用一个16进制数字来表示,然后在前面加“=”,这样每个需要编码的字节会被转换成三个字符来表示。”

为得到对中文更好的支持,建议设置为base64为宜。

ConvertHeaderToBase64()方法:

在邮件内容的各个类型中,包括邮件正文,附件和嵌入资源,可设定Content-Transfer-Encoding字段值来定义这个类型的传输编码。而在标题和文件名等本身就是字段的,设定其值的传输编码需要一种特殊方式,即ConvertHeaderToBase64()所要做的事。

这样邮件标题字段的值会被定义为:=?{字符编码}?{传输编码}?{编码后的字符串}?=。其中传输编码使用简称,B代表base64,Q代表quoted-printable,所以一个中文标题可能会定义成这样:=?utf-?B?5Y+R6YCBIG0yIHZpYSBoMiBVc2VUY3BDbGllbnQg?= 。

ConvertFileToBase64()方法:

ConvertFileToBase64()方法将附件和其他内嵌资源由二进制的形式转换成base64位编码,这使得各种类型的文件可以通过邮件进行传输成为可能。

        private string ConvertToBase64(string str)
{
byte[] buffer = Charset.GetBytes(str.ToCharArray());
return Convert.ToBase64String(buffer);
} private string ConvertHeaderToBase64(string str)
{
if (MustEncode(str))
{
return "=?" + Charset.WebName + "?B?" + ConvertToBase64(str) + "?=";
}
return str;
} private string ConvertFileToBase64(string file)
{
var fs = new FileStream(file, FileMode.Open, FileAccess.Read);
var buffer = new byte[(int)fs.Length];
fs.Read(buffer, , buffer.Length);
var fileStr = Convert.ToBase64String(buffer);
fs.Close();
return fileStr;
} private bool MustEncode(string str)
{
if (!string.IsNullOrEmpty(str))
{
foreach (char c in str)
{
if (c > )
{
return true;
}
}
}
return false;
}

接下来实现CreateMail方法

这个方法创建邮件的内容,但不包括附件和内嵌资源,只是正文。可以看到它主要是简单的创建邮件内容字符串数组,以便在Data命令后,逐行发送到服务器。

        public void CreateMail(ConfigMail mail)
{
Mail = new Message(mail.From, mail.To);
Mail.Data.Add("From: <" + mail.From + ">\r\n");
foreach (var to in mail.To)
{
Mail.Data.Add("To: <" + mail.From + ">\r\n");
}
Mail.Data.Add("Subject: " + ConvertHeaderToBase64(mail.Subject) + "\r\n");
Mail.Data.Add("Date: " + DateTime.Now.ToUniversalTime().ToString("R") + "\r\n");
Mail.Data.Add("MIME-Version: 1.0\r\n"); Mail.Data.Add("Content-Type: text/html;\r\n");
Mail.Data.Add(" charset=\"" + Charset.WebName + "\"\r\n");
Mail.Data.Add("Content-Transfer-Encoding: " + ContentTransferEncoding + "\r\n");
Mail.Data.Add("\r\n"); // It is important, otherwise the body may be missing.
Mail.Data.Add(ConvertToBase64(mail.Body) + "\r\n");
}

实现CreateMultiMail方法

这个方法创建邮件的内容,且包括附件和内嵌资源。邮件内容将被分为各个部分,各个部分标明了Content-Type和Charset,同时也设置了Content-Transfer-Encoding。上面已经讨论过Content-Transfer-Encoding,现在我们需要详细了解Content-Type。

Content-Type字段定义了邮件内容各部分的类型和相关属性。邮件内容中处于外围的都是multipart类型,而multipart包含3个子类型:multipart/mixed, multipart/related, multipart/alternative。这3种multipart的子类型在邮件内容中呈现的是一种嵌套关系:

multipart/mixed

multipart/related

multipart/alternative

text/plain

纯文本正文

text/html

超文本正文

内嵌资源

附件

如上图,如果包含附件则在附件外围声明multipart/mixed,如果包含内嵌资源则在内嵌资源外围声明multipart/related,如果同时存在text/plain 和text/html 则在文本外围声明multipart/alternative。这些类型内容范围由boundary属性定义的唯一标识决定,以 “—{boundary}”开始,以“--{boundary}--”结束,不同类型内容之间需要用空行分隔,所以邮件内容大概如下:

From: <******@***.com>
To: <******@***.com>
Subject: =?utf-?B?5Y+R6YCBIG0yIHZpYSBoMiBVc2VUY3BDbGllbnQg?=
Date: Thu, May :: GMT
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="b4ed1357_39ae_4098_a043_df80407fb136"
Message-Id: <53749FCC.00C525.@***.com> This is a multi-part message in MIME format. --b4ed1357_39ae_4098_a043_df80407fb136
Content-Type: multipart/related;
boundary="4954e4a1_b756_497d_8daa_458ecf101a1c" --4954e4a1_b756_497d_8daa_458ecf101a1c
Content-Type: multipart/alternative;
boundary="91de458a_e772_46ac_ab87_0c6fa2009e35" --91de458a_e772_46ac_ab87_0c6fa2009e35
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: base64 SWYgeW91IHNlZSB0aGlzIG1lc3NhZ2UsIGl0IG1lYW5zIHRoYXQgeW91ciBtYWlsIGNsaWVudCBkb2VzIG5vdCBzdXBwb3J0IGh0bWwu --91de458a_e772_46ac_ab87_0c6fa2009e35
Content-Type: text/html;
charset="utf-8"
Content-Transfer-Encoding: base64 5rWL6K+V44CCSnVzdCBhIHRlc3QuPGJyLz48aW1nIHNyYz0nY2lkOlVtVnpiM1Z5WTJVdWFuQm4nIGFsdD0nJy8+ --91de458a_e772_46ac_ab87_0c6fa2009e35-- --4954e4a1_b756_497d_8daa_458ecf101a1c
Content-ID: <UmVzb3VyY2UuanBn>
Content-Type: application/octet-stream;
name="Resource.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="Resource.jpg" /9j/4QCpRXhpZgAASUkqAAgAAAAFABIBAwABAAAAAQAAADEBAgAVAAAASgAAADIBAgAUAAAAXwAAABMCAwABAAAAAQAAAGmHBAABAAAAcwAAAAAAAABBQ0QgU3lzdGVtcyDK/cLrs8nP8QAyMDEwOjExOjE2IDE1OjExOjQ5AAMAkJICAAQAAAA4MTIAAq --4954e4a1_b756_497d_8daa_458ecf101a1c-- --b4ed1357_39ae_4098_a043_df80407fb136
Content-Type: application/octet-stream;
name="Attachment.docx"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="Attachment.docx" FvoPRtdhKeiil2M6hP8c20PQBFGmdiNqSkDZ/b9+145hSEocmGIwZrUTrZVGa3BB21NxsbJiEVgpFXaLDL2NXuLH1kUUBglSmsgYzsIbJLf3qSznYMQ0bQJGVsiuifOg1xCJUJiHRj6UlhfCaRXvBOyGxAH4/Gj1waQ2CwRhrBsvTDzLgtYJoKjy --b4ed1357_39ae_4098_a043_df80407fb136--

下面我们通过编码实现:

        public void CreateMultiMail(ConfigMail mail)
{
Mail = new Message(mail.From, mail.To);
Mail.Data.Add("From: <" + mail.From + ">\r\n");
foreach (var to in mail.To)
{
Mail.Data.Add("To: <" + mail.From + ">\r\n");
}
Mail.Data.Add("Subject: " + ConvertHeaderToBase64(mail.Subject) + "\r\n");
Mail.Data.Add("Date: " + DateTime.Now.ToUniversalTime().ToString("R") + "\r\n");
Mail.Data.Add("MIME-Version: 1.0\r\n"); var mixedBoundary = Guid.NewGuid().ToString().Replace("-", "_");
if (mail.Attachments != null && mail.Attachments.Length > )
{
Mail.Data.Add("Content-Type: multipart/mixed;\r\n");
Mail.Data.Add(" boundary=\"" + mixedBoundary + "\"\r\n");
Mail.Data.Add("\r\n");
Mail.Data.Add("This is a multi-part message in MIME format.\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add("--" + mixedBoundary + "\r\n");
} var relatedBoundary = Guid.NewGuid().ToString().Replace("-", "_");
if (mail.Resources != null && mail.Resources.Length > )
{
Mail.Data.Add("Content-Type: multipart/related;\r\n");
Mail.Data.Add(" boundary=\"" + relatedBoundary + "\"\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add("--" + relatedBoundary + "\r\n");
} var altBoundary = Guid.NewGuid().ToString().Replace("-", "_");
Mail.Data.Add("Content-Type: multipart/alternative;\r\n");
Mail.Data.Add(" boundary=\"" + altBoundary + "\"\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add("--" + altBoundary + "\r\n");
Mail.Data.Add("Content-Type: text/plain;\r\n");
Mail.Data.Add(" charset=\"" + Charset.WebName + "\"\r\n");
Mail.Data.Add("Content-Transfer-Encoding: " + ContentTransferEncoding + "\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add(ConvertToBase64("If you see this message, it means that your mail client does not support html.") + "\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add("--" + altBoundary + "\r\n");
Mail.Data.Add("Content-Type: text/html;\r\n");
Mail.Data.Add(" charset=\"" + Charset.WebName + "\"\r\n");
Mail.Data.Add("Content-Transfer-Encoding: " + ContentTransferEncoding + "\r\n");
Mail.Data.Add("\r\n");
Mail.Data.Add(ConvertToBase64(mail.Body) + "\r\n"); Mail.Data.Add("\r\n");
Mail.Data.Add("--" + altBoundary + "--\r\n"); if (mail.Resources != null && mail.Resources.Length > )
{
foreach (var resource in mail.Resources)
{
var fileInfo = new FileInfo(resource);
if (fileInfo.Exists)
{
Mail.Data.Add("\r\n");
Mail.Data.Add("\r\n");
Mail.Data.Add("--" + relatedBoundary + "\r\n");
Mail.Data.Add("Content-ID: <" + ConvertToBase64(fileInfo.Name) + ">\r\n");
Mail.Data.Add("Content-Type: " + GetMimeType(fileInfo.Extension) + ";\r\n");
Mail.Data.Add(" name=\"" + ConvertHeaderToBase64(fileInfo.Name) + "\"\r\n");
Mail.Data.Add("Content-Transfer-Encoding: " + ContentTransferEncoding + "\r\n");
Mail.Data.Add("Content-Disposition: attachment;\r\n");
Mail.Data.Add(" filename=\"" + ConvertHeaderToBase64(fileInfo.Name) + "\"\r\n"); Mail.Data.Add("\r\n");
var fileStr = ConvertFileToBase64(resource);
Mail.Data.Add(fileStr + "\r\n");
}
}
Mail.Data.Add("\r\n\r\n--" + relatedBoundary + "--\r\n");
} if (mail.Attachments != null && mail.Attachments.Length > )
{
foreach (var attachment in mail.Attachments)
{
var fileInfo = new FileInfo(attachment);
if (fileInfo.Exists)
{
Mail.Data.Add("\r\n");
Mail.Data.Add("\r\n");
Mail.Data.Add("--" + mixedBoundary + "\r\n");
Mail.Data.Add("Content-Type: " + GetMimeType(fileInfo.Extension) + ";\r\n");
Mail.Data.Add(" name=\"" + ConvertHeaderToBase64(fileInfo.Name) + "\"\r\n");
Mail.Data.Add("Content-Transfer-Encoding: " + ContentTransferEncoding + "\r\n");
Mail.Data.Add("Content-Disposition: attachment;\r\n");
Mail.Data.Add(" filename=\"" + ConvertHeaderToBase64(fileInfo.Name) + "\"\r\n"); Mail.Data.Add("\r\n");
var fileStr = ConvertFileToBase64(attachment);
Mail.Data.Add(fileStr + "\r\n");
}
} Mail.Data.Add("\r\n");
Mail.Data.Add("\r\n");
Mail.Data.Add("--" + mixedBoundary + "--\r\n");
}
}

实现SendMail方法

        public void SendMail()
{
if (Tcp != null && Stream != null)
{
WriteStream("MAIL FROM: <" + Mail.From + ">\r\n");
CheckErrorCode(ReadStream(), "");
foreach (var to in Mail.To)
{
WriteStream("RCPT TO: <" + to + ">\r\n");
CheckErrorCode(ReadStream(), "");
}
WriteStream("DATA\r\n");
CheckErrorCode(ReadStream(), ""); foreach (var item in Mail.Data)
{
WriteStream(item);
}
WriteStream("\r\n.\r\n");
CheckErrorCode(ReadStream(), ""); WriteStream("QUIT\r\n");
CheckErrorCode(ReadStream(), ""); Stream.Close();
Tcp.Close();
}
}

3、测试

测试发送只包含正文的简单邮件:

    class Program
{
static void Main(string[] args)
{
var h1 = new ConfigHost()
{
Server = "smtp.gmail.com",
Port = ,
Username = "******@gmail.com",
Password = "******",
EnableSsl = true
};
var m1 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test.",
From = "******@gmail.com",
To = new string[] { "******@gmail.com" },
}; var agent = new UseTcpClient();
var output = "Send m1 via h1 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h1);
m1.Subject = output;
agent.CreateMail(m1);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------"); Console.Read();
}
}

测试发送多媒体邮件:

    class Program
{
static void Main(string[] args)
{
var h2 = new ConfigHost()
{
Server = "smtp.163.com",
Port = ,
Username = "******@163.com",
Password = "******",
EnableSsl = false
};
var m2 = new ConfigMail()
{
Subject = "Test",
Body = "Just a test. <br/><img src='cid:" + Convert.ToBase64String(Encoding.Default.GetBytes("Resource.jpg")) + "' alt=''/> ",
From = "******@163.com",
To = new string[] { "******@163.com" },
Attachments = new string[] { @"E:\Test\SendMail\Attachment.pdf" },
Resources = new string[] { @"E:\Test\SendMail\Resource.jpg" }
}; var agent = new UseTcpClient();
var output = "Send m2 via h2 " + agent.GetType().Name + " ";
Console.WriteLine(output + "start");
try
{
agent.CreateHost(h2);
m2.Subject = output;
agent.CreateMultiMail(m2);
agent.SendMail();
Console.WriteLine(output + "success");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(output + "end");
Console.WriteLine("-----------------------------------"); Console.Read(); }
}

测试过程中使用较小的附件或图片可以发送成功且一切正常,但大附件一般是失败的,因而代码是存在缺陷的,其原因可能是在复杂的网络环境下使用同步发送出现异常或服务器失去响应,也可能是对数据流的操作不够谨慎,或者兼而有之,这有待进一步深入研究。

C#邮件发送问题(二)的更多相关文章

  1. postal邮件发送(二):Email headers,附件,图片介绍

    接上篇 http://www.cnblogs.com/mybky/p/5690567.html 1.邮件headers 除此之外,还有Reply-To,用于回复邮箱 2.邮件带有图片 邮件发送图片,p ...

  2. Linux mail 邮件发送

    Linux mail 邮件介绍 在Linux系统下我们可以通过”mail“命令,发送邮件,在运维中通常我们它来实现邮件告警. 安装 (方案1) 一.安装邮件服务 yum install -y send ...

  3. Java实现QQ邮件发送客户端

    目录 一.前言:QQ邮件发送程序 二.封装SMTP操作 三.实现多线程接收 四.QQ邮件客户端界面设计 1.连接按钮 2.发送按钮 五.QQ邮件发送效果演示 六.总结 一.前言:QQ邮件发送程序 在上 ...

  4. IntelliJ IDEA 2017版 spring-boot 2.0.3 邮件发送搭建,概念梳理 (二)

    第二部分 邮件发送历史   一.第一封邮件   1.1969年10月,世界上的第一封电子邮件    1969年10月世界上的第一封电子邮件是由计算机科学家Leonard K.教授发给他的同事的一条简短 ...

  5. spring-boot-route(二十二)实现邮件发送功能

    在项目开发中,除了需要短信验证外,有时候为了节省 短信费也会使用邮件发送.在Spring项目中发送邮件需要封装复杂的消息体,不太方便.而在Spring Boot项目中发送邮件就太简单了,下面一起来看看 ...

  6. 【干货】.NET开发通用组件发布(二) 邮件发送组件

    组件介绍和合作开发 http://www.cnblogs.com/MrHuo/p/MrHuoControls.html 邮件发送组件 邮件发送组件采用常用的SMTP发送方式,需要添加以下格式的配置文件 ...

  7. ABP框架系列之二十四:(Email-Sending-EF-电子邮件发送)

    Introduction Email sending is a pretty common task for almost every application. ASP.NET Boilerplate ...

  8. .NET开发邮件发送功能的全面教程(含邮件组件源码)

    今天,给大家分享的是如何在.NET平台中开发“邮件发送”功能.在网上搜的到的各种资料一般都介绍的比较简单,那今天我想比较细的整理介绍下: 1)         邮件基础理论知识 2)         ...

  9. Java邮件发送与接收原理

    一. 邮件开发涉及到的一些基本概念 1.1.邮件服务器和电子邮箱 要在Internet上提供电子邮件功能,必须有专门的电子邮件服务器.例如现在Internet很多提供邮件服务的厂商:sina.sohu ...

随机推荐

  1. 泛函编程(32)-泛函IO:IO Monad

    由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...

  2. 字符串模板替换方法 MessageFormat.format

    String content = "ab,cc,{名称},{密码},{日期},dd,ff"; String array[] = {userName, password, forma ...

  3. java枚举与.net中的枚举区别

    通过一段时间的项目实践,发现java中的枚举与.net中的枚举有很大的差别,初期造成了我对java中的枚举一些错误理解及部分有缺陷的应用,其实追其原因还是因为我会习惯性的认为java的枚举在作用以及定 ...

  4. JVM的生命周期、体系结构、内存管理和垃圾回收机制

    一.JVM的生命周期 JVM实例:一个独立运行的java程序,是进程级别 JVM执行引擎:用户运行程序的线程,是JVM实例的一部分 JVM实例的诞生 当启动一个java程序时.一个JVM实例就诞生了, ...

  5. js的动态加载、缓存、更新以及复用(一)

    使用范围: OA.MIS.ERP等信息管理类的项目,暂时不考虑网站. 遇到的问题: 完成一个项目,往往需要引用很多js文件,比如jQuery.js.easyUI等.还有自己写的一些列js文件,那么这些 ...

  6. 一款经典的jQuery slidizle 幻灯片

    jQuery广告幻灯片进度条,水平/左右切换,垂直/上下切换,自动播放,缩略图列表切换 在线实例 默认效果 水平/左右切换 垂直/上下切换 循环 自动播放 缩略图 进度条 回调函数 使用方法 < ...

  7. Oracle EBS Form Builder使用Java beans创建窗体

    最近有个项目,需要研究一下Oracle的E-Business Sutie(EBS),对于以前没接触此套件的我来说,简直太痛苦了.在网上找了一堆资料,试着进行Form二次开发,也遇到各类奇葩问题.目前遇 ...

  8. Progress.js – 为页面上的任意对象创建进度条效果

    Progress.js 是一个 JavaScript 和 CSS3 的库,它帮助开发人员为网页上的每个对象创建和管理进度条效果.你可以设计自己的模板,进度条或者干脆定制. 您可以使用 Progress ...

  9. 【追寻javascript高手之路02】变量、作用域知多少?

    前言 本来想把这个与上篇博客写到一起的,但是考虑到是两个知识点还是分开算了,于是我们继续今天的学习吧. 基本类型与引用类型 ECMAScript的的变量有两种类型: 基本类型(值类型):简单数据段 引 ...

  10. 【web前端面试题整理01】各位加班累了吧,来做点前端面试题吧

    前言 最近小叶子有点疲惫,主要是在外地工作生活上不太适应,吃一样的东西,我居然会拉肚子,而且是一个星期一个星期的.... 脸上长了一个豆豆一个星期还没消,我那个去啊. 昨天上午上班后,本来想继续研究j ...