大家使用C#同步郵件的時(shí)候屎蜓,會(huì)不會(huì)遇到一件頭疼的事情辅鲸,C#只支持POP協(xié)議(只能獲取收件箱的郵件,其他文件夾獲取不到)沼溜,Imap需要用第三方的來潮梯,而常見的幾個(gè)第三方的類庫骗灶,在同步大附件的時(shí)候,總是會(huì)超時(shí)秉馏。迫于無奈耙旦,我只好自己寫一個(gè)Imap協(xié)議獲取郵件。通過不斷調(diào)試萝究,獲取大附件再也不會(huì)超時(shí)了母廷,下面就跟大家分享一下。
準(zhǔn)備知識(shí)
程序原理就是利用TCP請(qǐng)求郵件服務(wù)器糊肤,然后發(fā)送IMAP命令執(zhí)行一系列操作琴昆。
1.登錄
a01 xxx@126.com password
結(jié)果:a01 OK LOGIN completed
2.獲取文件夾列表
a02 list "" *
結(jié)果:
LIST () “/” “INBOX”
LIST (\Drafts) “/” “&g0l6P3ux-”
LIST (\Sent) “/” “&XfJT0ZAB-”
LIST (\Trash) “/” “&XfJSIJZk-”
LIST (\Junk) “/” “&V4NXPpCuTvY-”
LIST () “/” “&dcVr0pCuTvY-”
LIST () “/” “&Xn9USpCuTvY-”
LIST () “/” “&i6KWBZCuTvY-”
LIST () “/” “&YhF2hGWHaGM-”
LIST () “/” “java”
LIST () “/” “&bUuL1WWHTvZZOQ-”
LIST () “/” “&Y6hef5CuTvY-”
LIST () “/” “test”
LIST () “/” “folder”
a02 OK LIST Completed
3.選擇某個(gè)文件夾
a03 select inbox
結(jié)果:
2918 EXISTS
458 RECENT
OK [UIDVALIDITY 1] UIDs valid
FLAGS (\Answered \Seen \Deleted \Draft \Flagged)
OK [PERMANENTFLAGS (\Answered \Seen \Deleted \Draft \Flagged)] Limited
a03 OK [READ-WRITE] SELECT completed
4.獲取日期開始郵件
a04 SEARCH SINCE 日-月-年
其中月為英文縮寫,如Jan馆揉,F(xiàn)eb
5.獲取郵件內(nèi)容
b01 fetch body[header] //郵件頭
b01 fetch body[1] //郵件主體
具體命令大家可自行百度學(xué)習(xí)业舍,這邊就不過多講解。
代碼詳解
連接
public void Connection(string serveraddress, int port)
{
try
{
tcpclient.Connect(serveraddress, port);
sslstream = new SslStream(tcpclient.GetStream());
sslstream.AuthenticateAsClient(serveraddress);
bool flag = sslstream.IsAuthenticated;
if (!flag)
{
throw new Exception("sslstream IsAuthenticated return false");
}
}
catch (Exception ex)
{
throw ex;
}
}
登錄
構(gòu)造imap命令升酣,使用StreamWriter寫入命令舷暮,StreamReader逐行獲取,判斷失敗返回的關(guān)鍵字
public void login(string username, string password)
{
sw = new StreamWriter(sslstream);
// Assigned reader to stream
reader = new StreamReader(sslstream);
sw.WriteLine("a01 LOGIN " + username + " " + password);
sw.Flush();
try
{
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
if (strTemp.IndexOf("OK LOGIN completed") != -1)
{
break;
}
else if (strTemp.IndexOf("NO LOGIN failed") != -1)
{
throw new Exception("NO LOGIN failed");
}
}
}
catch (Exception ex)
{
throw ex;
}
}
查看文件夾
同理提交查看文件夾的命令噩茄,循環(huán)讀取返回內(nèi)容下面,直到"OK LIST completed"結(jié)束
public string FoldersList()
{
sw.WriteLine("a02 list \"\" *");
sw.Flush();
string str = string.Empty;
try
{
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
if (strTemp.IndexOf("OK LIST completed") != -1)
{
break;
}
str += strTemp + "\r";
}
}
catch (Exception ex)
{
return "error:" + ex.Message;
}
return str;
}
選擇文件夾
存在"SELECT completed"返回True,否則False
public bool SelectFolder(string folder)
{
sw.WriteLine("a03 select " + folder);
sw.Flush();
try
{
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
if (strTemp.IndexOf("NO SELECT failed") != -1)
{
return false;
}
if (strTemp.IndexOf("SELECT completed") != -1)
{
return true;
}
}
}
catch (Exception ex)
{
return false;
}
return false;
}
獲取郵件id
使用"SEARCH SINCE 日期"命令獲取某天開始的郵件绩聘,ToSearchDt()方法會(huì)將日期轉(zhuǎn)成IMAP命令需要的格式沥割,返回的是uid數(shù)組
public string[] Search(DateTime dateTime, bool isReverse = true)
{
string strDateTime = ToSearchDt(dateTime);
sw.WriteLine("a04 SEARCH SINCE " + strDateTime);
sw.Flush();
string str = string.Empty;
try
{
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
if (strTemp.IndexOf("SEARCH") != -1)
{
str = strTemp.Replace("SEARCH", "").Replace("*", "").Trim();
}
break;
}
}
catch (Exception ex)
{
;
}
if (string.IsNullOrEmpty(str)) return null;
string[] arr = System.Text.RegularExpressions.Regex.Split(str, @"\s+");
if (isReverse)
{
arr = arr.Reverse().ToArray();
}
return arr;
}
獲取郵件
這邊主要是做了一個(gè)判斷是否附件的處理耗啦,如果是附件設(shè)置一次讀取的大小,再循環(huán)讀取机杜。如果用行讀取的話帜讲,附件太大的話會(huì)卡很久到超時(shí)。這邊設(shè)置的是一次100K椒拗,這個(gè)值可以適當(dāng)調(diào)整
private string fetch(int id, string data, bool line = false)
{
sw.WriteLine("b01 fetch " + id + " " + data);
sw.Flush();
string str = string.Empty;
try
{
System.Text.RegularExpressions.Regex reg1 = new System.Text.RegularExpressions.Regex(@"body\[[1-9][0-9]{0,1}\]");
bool ismatch = reg1.IsMatch(data);
if (ismatch && data != "body[1]")
{
//100k
var clen = 1024 * 100;
var read = new Char[clen];
var count = reader.Read(read, 0, clen);
while (count > 0)
{
var strTemp = new string(read, 0, count);
str += strTemp;
if (strTemp.IndexOf("OK FETCH completed") != -1)
break;
count = reader.Read(read, 0, clen);
}
var arr = str.Split('\r');
string[] b = new string[arr.Length - 3];
Array.Copy(arr, 1, b, 0, arr.Length - 3);
str = string.Join("\r", b).Replace("\r\n", "").Replace(")", "");
}
else
{
string strTemp = string.Empty;
while ((strTemp = reader.ReadLine()) != null)
{
if (strTemp.IndexOf("OK SEARCH completed") != -1 || strTemp.ToLower().IndexOf(data) != -1)
{
continue;
}
if (strTemp.IndexOf("OK FETCH completed") != -1 || strTemp.IndexOf("BAD invalid command or parameters") != -1)
{
break;
}
if (line)
{
str += strTemp;
}
else
{
str += strTemp + "\r";
}
}
}
}
catch (Exception ex)
{
;
}
return str;
}
這邊主要講解核心的方法似将,其余的是一些解析郵件內(nèi)容的方法,大家可下載源碼自行研究蚀苛。
為何需要解析在验?因?yàn)楂@取下來的是一大串的文本,我們需要根據(jù)一些標(biāo)識(shí)堵未,獲取到我們想要的東西译红,如發(fā)件時(shí)間、發(fā)送的郵箱兴溜、標(biāo)題、主題和附件等耻陕。我解析的方法可能略顯粗糙拙徽,希望大家有興趣的話可以來優(yōu)化一下。
調(diào)用實(shí)例
這邊舉例獲取收件箱的郵件诗宣,如果全部文件夾都需要?jiǎng)t可以遍歷list膘怕,
using (ImapClient client = new ImapClient())
{
try
{
client.Connection("imap.mxhichina.com", 993);
client.login("郵箱", "密碼");
//獲取郵箱文件夾
var list = client.FoldersList();
//獲取指定文件夾
var hasFolder = client.SelectFolder("INBOX");
if (hasFolder)
{
//要查詢的時(shí)間
var ids = client.Search(DateTime.Now.AddDays(-1));
if (ids != null)
{
foreach (var id in ids)
{
try
{
var ID = Convert.ToInt32(id);
//獲取郵件頭
var header = client.GetHeader(ID);
//獲取郵件主體
var body = client.GetBody(ID);
//獲取附件
var attachemnts = client.GetAttachments(ID);
foreach (var attachment in attachemnts)
{
byte[] bytes = Convert.FromBase64String(attachment.bs64);
string uploadDir = "img";
if (!Directory.Exists(uploadDir))
{
Directory.CreateDirectory(uploadDir);
}
using (Image img = Image.FromStream(new MemoryStream(bytes)))
{
var fileName = uploadDir += "/" + System.Guid.NewGuid().ToString() + ".jpg";
img.Save(uploadDir, ImageFormat.Jpeg);
}
}
}
catch (Exception ex)
{
Console.WriteLine("獲取郵件異常,id:{0},異常{1}", id, ex.Message);
}
}
}
}
}
catch (Exception ex)
{
;
}
}