三、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: 250-mail 250-PIPELINING 250-AUTH LOGIN PLAIN 250-AUTH=LOGIN PLAIN 250-STARTTLS 250 8BITMIME Send: AUTH LOGIN Receive: 334 dXNlcm5hbWU6 Send: cWluZ3NwYWNl Receive: 334 UGFzc3dvcmQ6 Send: NINULFzLnhtdQ== Receive: 235 Authentication successful Send: MAIL FROM: ******@***.com Receive: 250 Mail OK Send: RCPT TO: <******@***.com> Receive: 250 Mail OK Send: DATA Receive: 354 End data with <CR><LF>.<CR><LF> Send: From: <<******@***.com> Send: To: <<******@***.com> Send: Subject: =?utf-8?B?5Y+R6YCBIG0yIHZpYSBoMiBVc2VUY3BDbGllbnQg?= Send: Date: Fri, 16 May 2014 01:17:40 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: 250 Mail OK queued as AgAi0gCXn8M0Z3VTmF4QAA--.4500S2 Send: QUIT Receive: 221 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 != 0) { Tcp = new TcpClient(host.Server, host.Port); Tcp.SendTimeout = 50000; Tcp.SendBufferSize = 1024; Tcp.ReceiveTimeout = 50000; Tcp.ReceiveBufferSize = 1024; 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, 10); Tcp.LingerState = lingerOption; CheckErrorCode(ReadStream(), "220"); if (!string.IsNullOrEmpty(host.Username) && !string.IsNullOrEmpty(host.Password)) { WriteStream("EHLO " + Dns.GetHostName() + "\r\n"); CheckErrorCode(ReadStream(), "250"); WriteStream("AUTH LOGIN\r\n"); if (CheckReplyCode(ReadStream(), "334")) { WriteStream(ConvertToBase64(host.Username) + "\r\n"); CheckErrorCode(ReadStream(), "334"); WriteStream(ConvertToBase64(host.Password) + "\r\n"); CheckErrorCode(ReadStream(), "235"); } } else { WriteStream("HELO " + Dns.GetHostName() + "\r\n"); CheckErrorCode(ReadStream(), "250"); } } }
我們使用WriteStream()方法發送命令和數據,ReadStream()方法獲得服務器反饋,CheckErrorCode()和CheckReplyCode()方法判斷反饋的信息不是異常,以確保進行下一步。
由於TcpClient發送的數據是有限制的,因而當發送較長數據時最好將數據分幾次發送。其實這樣依然會帶來問題,由於我們采用同步寫入數據流的方式,大數據如附件的發送常常會因網絡傳輸或服務器交互問題造成異常,因而在LumiSoft項目采用的是異步方式,這里我們全當測試,測試時使用較小的附件以避免這樣的問題。
private void WriteStream(string request) { byte[] buffer = Charset.GetBytes(request); var pageSize = 72; var totalPages = (int)Math.Ceiling(((double)buffer.Length) / pageSize); for (var i = 0; i < totalPages; i++) { Stream.Write(buffer, i * pageSize, i == totalPages - 1 ? buffer.Length - i * pageSize : pageSize); Console.WriteLine("Send(" + i + "):" + Charset.GetString(buffer, i * pageSize, i == totalPages - 1 ? buffer.Length - i * pageSize : pageSize)); } } private string ReadStream() { var buffer = new byte[1024]; var size = Stream.Read(buffer, 0, buffer.Length); var response = Charset.GetString(buffer, 0, size); Console.WriteLine("Receive: " + response); return response; } private void CheckErrorCode(string response, string code) { if (response.IndexOf(code) == -1) { throw new Exception("Exception: " + response); } } private bool CheckReplyCode(string response, string code) { if (response.IndexOf(code) == -1) { return false; } else { return true; } }
下面的一些方法用來對數據進行base64編碼,以便在網絡中傳輸。
ConvertToBase64()方法:
ConvertToBase64()方法只是簡單的將utf-8編碼的字符串進行base64編碼。傳輸編碼定義了郵件標題、正文(包含多國語言),附件、嵌入資源(二進制數據)等轉換為特定字符集的方式,以便適應純文本的郵件傳輸環境。主要編碼方式有quoted-printable和base64。
“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-8?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, 0, 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 > 127) { 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/mixed,如果包含內嵌資源則在內嵌資源外圍聲明multipart/related,如果同時存在text/plain 和text/html 則在文本外圍聲明multipart/alternative。這些類型內容范圍由boundary屬性定義的唯一標識決定,以 “—{boundary}”開始,以“--{boundary}--”結束,不同類型內容之間需要用空行分隔,所以郵件內容大概如下:
From: <******@***.com> To: <******@***.com> Subject: =?utf-8?B?5Y+R6YCBIG0yIHZpYSBoMiBVc2VUY3BDbGllbnQg?= Date: Thu, 15 May 2014 11:06:51 GMT MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="b4ed1357_39ae_4098_a043_df80407fb136" Message-Id: <53749FCC.00C525.01636@***.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 > 0) { 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 > 0) { 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 > 0) { 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 > 0) { 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(), "250"); foreach (var to in Mail.To) { WriteStream("RCPT TO: <" + to + ">\r\n"); CheckErrorCode(ReadStream(), "250"); } WriteStream("DATA\r\n"); CheckErrorCode(ReadStream(), "354"); foreach (var item in Mail.Data) { WriteStream(item); } WriteStream("\r\n.\r\n"); CheckErrorCode(ReadStream(), "250"); WriteStream("QUIT\r\n"); CheckErrorCode(ReadStream(), "221"); Stream.Close(); Tcp.Close(); } }
3、測試
測試發送只包含正文的簡單郵件:
class Program { static void Main(string[] args) { var h1 = new ConfigHost() { Server = "smtp.gmail.com", Port = 465, 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 = 25, 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(); } }
測試過程中使用較小的附件或圖片可以發送成功且一切正常,但大附件一般是失敗的,因而代碼是存在缺陷的,其原因可能是在復雜的網絡環境下使用同步發送出現異常或服務器失去響應,也可能是對數據流的操作不夠謹慎,或者兼而有之,這有待進一步深入研究。