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. 【FOL】万里长征第一步

    准备了好久了,中间断断续续看了些资料,也写了几个小demo练手,今天正式开始. 因为要模拟debug和release环境,手上资源又很缺,必须把一些已经拼好的图片进行切割,网络上找了半天倒是有几个切图 ...

  2. Linux下如何查看哪个进程占用内存多?

    1.top top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器 可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者 ...

  3. Linux学习笔记16--Linux扩展权限

    默认权限: 每一个终端都有一个umask属性,用于确定新建文件.新文件夹的默认权限. umask使用数字权限方式表示,如:022   一般来说,普通用户的默认umask是002,root用户的默认um ...

  4. padding下中英文左右两端对齐

    <!DOCTYPE html> <html> <head> <meta charset='UTF-8'> <title>helo</t ...

  5. Bootstrap源码分析之transition、affix

    一.Transition(过滤) 作为一个基础支持的组件,被其他组件多次引用.实现根据浏览器支持transition的能力,然后绑定动画的结束事件:首先:创建一个Element:然后:迭代查看此元素支 ...

  6. Materialize - 响应式 Material Design 框架

    由谷歌创建和设计的 Material Design(材料设计)是一种设计语言,结合成功的设计的经典原则以及创新科技.谷歌的目标是开发一个设计系统,让所有的产品在任何平台上拥有统一的用户体验. Mate ...

  7. 经典案例:那些让人赞不绝口的创新 HTML5 网站

    在过去的10年里,网页设计师使用 Flash.JavaScript 或其他复杂的软件和技术来创建网站.但现在你可以前所未有的快速.轻松地设计或创造互动的.有趣好看的网站.如何创建?答案是 HTML5 ...

  8. HTML中input标签的alt属性和title属性的比较

    经常用到这两个属性,但是一直没有总结他们的区别.现在我对他们两个的用法做一下总结: 相同点:他们都会飘出一个小浮层,显示文本内容. 不同点: 1.alt只能是元素的属性,而title即可以是元素的属性 ...

  9. 一个python线程池的源码解析

    python为了方便人们编程高度封装了很多东西,比如进程里的进程池,大大方便了人们编程的效率,但是默认却没有线程池,本人前段时间整理出一个线程池,并进行了简单的解析和注释,本人水平有限,如有错误希望高 ...

  10. ID卡和IC卡

    1.ID卡 ID卡就是一种身份识别卡,卡内除了卡号之外,无任何加密功能. ID卡的工作原理:它是由卡.读卡器.后台控制器组成的. (1)读卡器通过天线发射射频信号 (2)当卡进入信号范围内后卡被激活 ...