经过几天的努力,从完全不懂SMTP到折腾出个可以发送邮件内容和附件的DEMO。话少说,直接上代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.IO; namespace UtilSp.ClassLib { public class SmtpSp { #region Member #region boundary normal Property private string boundary_ = Guid.NewGuid().ToString();//Generate unique boundary. public string boundary_pro { get { return boundary_; } set { boundary_ = value; } } #endregion #region exception Property private Exception exception_ = null; public Exception exception_pro { get { return exception_; } set { exception_ = value; } } #endregion #region encoding_smtp normal Property private Encoding encoding_smtp_ = Encoding.ASCII;//smtp encoding must be ascii. private Encoding encoding_smtp_pro { get { return encoding_smtp_; } } #endregion #region encoding_subject normal Property private Encoding encoding_subject_ = Encoding.UTF8; public Encoding encoding_subject_pro { get { return encoding_subject_; } set { encoding_subject_ = value; } } #endregion #region encoding_content normal Property private Encoding encoding_content_ = Encoding.GetEncoding("gb2312"); public Encoding encoding_content_pro { get { return encoding_content_; } set { encoding_content_ = value; } } #endregion #region receiveInfo normal Property private string receiveInfo_ = ""; public string receiveInfo_pro { get { return receiveInfo_; } set { receiveInfo_ = value; } } #endregion #region socket normal Property private Socket socket_; public Socket socket_pro { get { return socket_; } private set { socket_ = value; } } #endregion #endregion private sealed class SMTP_BACKS { public const string SERVER_READY_OK = "220"; public const string OPERATE_COMPLETE = "250"; public const string AUTH_LOGIN_OK = "334"; public const string USER_NAME_LOGIN_OK = "334"; public const string PASSWORD_LOGIN_OK = "235"; public const string DATA_OK = "354"; } private sealed class SMTP_CMD { public const string AUTH_LOGIN = "auth login "; public const string MAIL_FROM = "mail from:"; public const string RCPT_TO = "rcpt to:"; public const string SEND_BODY = "data "; public const string VERIFY = "ehlo hello "; } public sealed class MailInfo { #region attachments normal Property private List<string> attachments_ = new List<string>(); public List<string> attachments_pro { get { return attachments_; } set { attachments_ = value; } } #endregion #region content normal Property private string content_ = ""; public string content_pro { get { return content_; } set { content_ = value; } } #endregion #region password normal Property private string password_ = ""; public string password_pro { get { return password_; } set { password_ = value; } } #endregion #region receiverAddresses normal Property private List<string> receiverAddresses_ = new List<string>(); public List<string> receiverAddresses_pro { get { return receiverAddresses_; } set { receiverAddresses_ = value; } } #endregion #region senderAddress normal Property private string senderAddress_ = ""; public string senderAddress_pro { get { return senderAddress_; } set { senderAddress_ = value; } } #endregion #region subject normal Property private string subject_ = ""; public string subject_pro { get { return subject_; } set { subject_ = value; } } #endregion #region userName normal Property private string userName_ = ""; public string userName_pro { get { return userName_; } set { userName_ = value; } } #endregion } #region send Function public bool send(string mailHostIP, int port, MailInfo mailInfo) { try { exception_pro = null; if (!isLegal(mailHostIP, port, mailInfo)) { return false; } socket_pro = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket_pro.Connect(mailHostIP, port); if (!socket_pro.Connected) { socket_pro.Close(); exception_pro = new Exception(Tip.ConnectFail); return false; } if (!isServerReady()) { exception_pro = new Exception(Tip.ServerReadyFail + analyReceiveError()); socket_pro.Close(); return false; } if (!verify()) { exception_pro = new Exception(Tip.VerifyFail + analyReceiveError()); socket_pro.Close(); return false; } if (!authLogin()) { exception_pro = new Exception(Tip.AuthLoginFail + analyReceiveError()); socket_pro.Close(); return false; } if (!userNameLogin(mailInfo.userName_pro)) { exception_pro = new Exception(Tip.UserNameLoginFail + analyReceiveError()); socket_pro.Close(); return false; } if (!passowrdLogin(mailInfo.password_pro)) { exception_pro = new Exception(Tip.PasswordLoginFail + analyReceiveError()); socket_pro.Close(); return false; } if (!mailFrom(mailInfo.senderAddress_pro)) { exception_pro = new Exception(Tip.MailFromFail + analyReceiveError()); socket_pro.Close(); return false; } if (!rcptTo(mailInfo.receiverAddresses_pro)) { exception_pro = new Exception(Tip.RcptToFail + analyReceiveError()); socket_pro.Close(); return false; } if (!sendMail(mailInfo)) { exception_pro = new Exception(Tip.SendMailFail + analyReceiveError()); socket_pro.Close(); return false; } socket_pro.Close(); return true; } catch (System.Exception ex) { exception_pro = ex; return false; } } private string analyReceiveError() { if (string.IsNullOrEmpty(receiveInfo_pro)) { return Tip.ReceiveInfoEmpty; } return receiveInfo_pro; } #endregion private int socketSend(string sendStr) { byte[] sendBuffer = encoding_smtp_pro.GetBytes(sendStr); return socket_pro.Send(sendBuffer); } private string receive() { byte[] receiveData = new byte[10240]; int receiveLen = socket_pro.Receive(receiveData); receiveInfo_pro = encoding_smtp_pro.GetString(receiveData, 0, receiveLen); Console.WriteLine(receiveInfo_pro); return receiveInfo_pro; } #region send ready functions #region isLegal Function public bool isLegal(string mailHostIP, int port, MailInfo mailInfo) { if (!IpPortSp.isPort(port.ToString())) { exception_pro = new Exception(Tip.PortIllegal); return false; } if (!MailSp.isMail(mailInfo.senderAddress_pro)) { exception_pro = new Exception(Tip.SenderIllegal); return false; } return true; } #endregion #region isServerReady Function public bool isServerReady() { string back = receive(); return back.Substring(0, 3).Equals(SMTP_BACKS.SERVER_READY_OK);//Response code from server always is 3 byte length. } #endregion #region verify Function public bool verify() { socketSend(SMTP_CMD.VERIFY); string[] verifyBacks = receive().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); for (int index = 0; index < verifyBacks.Length; index++) { string verifyBack = verifyBacks[index]; if (verifyBack.Length <= 3) { return false; } if (!verifyBack.Substring(0, 3).Equals(SMTP_BACKS.OPERATE_COMPLETE)) { return false; } } return true; } #endregion #region authLogin function private bool authLogin() { socketSend(SMTP_CMD.AUTH_LOGIN); string authLoginBack = receive(); if (string.IsNullOrEmpty(authLoginBack) || authLoginBack.Length <= 3) { return false; } return authLoginBack.Substring(0, 3).Equals(SMTP_BACKS.AUTH_LOGIN_OK); } #endregion #region userNameLogin function private bool userNameLogin(string userName) { if (string.IsNullOrEmpty(userName)) { return false; } string base64UserName = Base64Sp.tobase64Str(userName); socketSend(base64UserName + " "); string userNameLoginBack = receive(); if (string.IsNullOrEmpty(userNameLoginBack) || userNameLoginBack.Length <= 3) { return false; } return userNameLoginBack.Substring(0, 3).Equals(SMTP_BACKS.USER_NAME_LOGIN_OK); } #endregion #region passowrdLogin function private bool passowrdLogin(string password) { if (string.IsNullOrEmpty(password)) { return false; } socketSend(Base64Sp.tobase64Str(password) + " "); string passowrdLoginBack = receive(); if (string.IsNullOrEmpty(passowrdLoginBack) || passowrdLoginBack.Length <= 3) { return false; } return passowrdLoginBack.Substring(0, 3).Equals(SMTP_BACKS.PASSWORD_LOGIN_OK); } #endregion #region mailFrom function private bool mailFrom(string senderAddress) { socketSend(SMTP_CMD.MAIL_FROM + "<" + senderAddress + "> "); string mailFromBack = receive(); if (string.IsNullOrEmpty(mailFromBack) || mailFromBack.Length <= 3) { return false; } return mailFromBack.Substring(0, 3).Equals(SMTP_BACKS.OPERATE_COMPLETE); } #endregion #region rcptTo function private bool rcptTo(List<string> receiverAddresses) { foreach (string receiverAddress in receiverAddresses) { socketSend(SMTP_CMD.RCPT_TO + "<" + receiverAddress + "> "); string rcptBack = receive(); if (string.IsNullOrEmpty(rcptBack) || rcptBack.Length <= 3) { return false; } if (!rcptBack.Substring(0, 3).Equals(SMTP_BACKS.OPERATE_COMPLETE)) { return false; } } return true; } #endregion #endregion #region send mail body functions private bool sendMail(MailInfo mailInfo) { socketSend(SMTP_CMD.SEND_BODY); string back = receive(); if (string.IsNullOrEmpty(back) || back.Length <= 3) { return false; } if (!back.Substring(0, 3).Equals(SMTP_BACKS.DATA_OK)) { return false; } sendMailHeader(mailInfo); sendMailContent(mailInfo.content_pro); sendMailAttachment(mailInfo.attachments_pro); sendMailTail(); back = receive(); if (string.IsNullOrEmpty(back) || back.Length <= 3) { return false; } if (!back.Substring(0, 3).Equals(SMTP_BACKS.OPERATE_COMPLETE)) { return false; } return true; } private void sendMailAttachment(List<string> attachments) { foreach (string attachment in attachments)//Add header per attachment. { if (!File.Exists(attachment)) { continue; } sendAttachmentHeader(attachment); sendAttachmentData(attachment); } } private void sendAttachmentData(string attachment) { byte[] attachmentData = FileSp.readFileBytes(attachment); socketSend(Convert.ToBase64String(attachmentData)); socketSend(" "); } private void sendAttachmentHeader(string attachment) { string contentType = "application/octet-stream";//Easy to transmission more format file; string header = "--" + boundary_pro + " " + "Content-Type:"+contentType+";name=" + attachment + " " + "Content-Transfer-Encoding:base64 " + "Content-Disposition:attachment;filename="" + attachment + "" "; socketSend(header + " "); } private void sendMailTail() { socketSend(" . ");//. is content end flag. } private void sendMailHeader(MailInfo mailInfo) { string to = ""; foreach (string receiverAddress in mailInfo.receiverAddresses_pro) { to += "To:" + receiverAddress + " "; } //if you want to hide receiver,may assign to="To:abc@a.com "; //string to = "To:abc@a.com "; socketSend(to); string from = "From:" + mailInfo.senderAddress_pro + " "; socketSend(from); //Avoid subject messy code,adopt encoding header statement. string subject = "Subject:=?" + encoding_subject_pro.BodyName + "?B?" + Base64Sp.tobase64Str(mailInfo.subject_pro, Encoding.UTF8) + "?= "; socketSend(subject); string header = "Mime-Version:1.0 " + "Content-type:multipart/mixed;" + "boundary="" + boundary_pro + ""; "//define content type and boundary value. + "Content-Transfer-Encoding:7bit " + "This is a multi-part message in MIME format "; socketSend(header + " "); } private void sendMailContent(string content) { sendContentHeader(); sendContentBody(content); } private void sendContentBody(string content) { //If content is too long,need split some blocks and send. //Length of one line is always 80 byte. string newContent = Base64Sp.tobase64Str(content, encoding_content_pro); socketSend(newContent + " "); } private void sendContentHeader() { string header = "--" + boundary_pro + " "//boundary must begin -- + "Content-type:text/plain;charset=" + encoding_content_pro.BodyName + " "//Avoid messy code,add charset statement. + "Content-Transfer-Encoding:base64 "//Avoid messy code,use encoding base64. socketSend(header + " "); } #endregion private class Tip//Easy to change other language tip. { public static string AuthLoginFail = "Auth login fail!"; public static string ConnectFail = "Connect mail host fail!"; public static string IpIllegal = "Ip is illegal!"; public static string MailFromFail = "Input sender address fail!"; public static string PasswordLoginFail = "Password login fail!"; public static string PortIllegal = "Port is illegal!"; public static string ReceiveInfoEmpty = "Receive info is empty!"; public static string RcptToFail = "Input receiver address fail!"; public static string SenderIllegal = "Sender mail address is illegal!"; public static string SendMailFail = "Send mail fail!"; public static string ServerReadyFail = "Server ready fail!"; public static string UserNameLoginFail = "User name login fail!"; public static string VerifyFail = "Verify identity fail!"; } } }