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. 使用Apache的Base64类实现Base64加解密

    包名称:org.apache.commons.codec.binary 类名称:org.apache.commons.codec.binary.Base64 1.Base64加密 public sta ...

  2. http gzip 解压缩

    var sContentEncoding = httpRespone.Headers["Content-Encoding"]; if(sContentEncoding == &qu ...

  3. Linux学习笔记15-YUM安装

    rpm软件包缺点:需要手工解决软件包的依赖关系.使用YUM可解决该问题. YUM(Yellodog Updater, Modified)是一个RPM前端程序,主要目的是设计用来自动解决RPM的依赖关系 ...

  4. jbox演示30种不同的调用方法

     在线预览 插件说明 - jbox 是一款基于 jQuery 的多功能对话框插件,能够实现网站的整体风格效果,给用户一个新的视觉享受. 运行环境 - 兼容 IE6+.Firefox.Chrome.Sa ...

  5. 对jQuery选择器的总结

    jQuery基础选择器 $("div*")获取div下面的所有元素 $(".red,.green").html("怎么") // 需要注意的 ...

  6. Rotating Image Slider - 图片旋转切换特效

    非常炫的图片旋转滑动特效,相信会给你留下深刻印象.滑动图像时,我们会稍稍旋转它们并延缓各元素的滑动.滑块的不寻常的形状是由一些预先放置的元素和使用边框创建.另外支持自动播放选项,鼠标滚轮的功能. 在线 ...

  7. 20个最新的照片 PS 技巧,提升摄影水平

    相信很多人都知道 Photoshop 在照片编辑方面的强大,所以几乎每张照片经过 PS 处理后都可以变成一个真正的杰作.这里分享的这组 Photoshop 教程可以帮助你学习到新的照片处理技术.你会发 ...

  8. 关于AutoCAD 2014的securityload…

    昨天一个朋友给我打电话提到他关于AutoCAD 2014中安全加载SECURTY LOAD的一些困惑,我的同事Fenton写了一篇博客,对AutoCAD 2014安全加载的来龙去脉做了深入详细的阐述, ...

  9. android之HttpClient

    Apache包是对android联网访问封装的很好的一个包,也是android访问网络最常用的类. 下面分别讲一下怎么用HttpClient实现get,post请求. 1.Get 请求 HttpGet ...

  10. 操作系统开发系列—12.g.在内核中设置键盘中断

    8259A虽然已经设置完成,但是我们还没有真正开始使用它呢. 所有的中断都会触发一个函数spurious_irq(),这个函数的定义如下: PUBLIC void spurious_irq(int i ...