如何在代理环境下发送邮件?——C#实现Socket代理方式的邮件发送

       前一阵子做项目的时候遇到一个功能需求:当程序异常或者重大事件时候,发送邮件通知管理员。按理说这是一个很简单的需求,但是在开发当中遇到了一个问题:因为客户那里的网络拓扑是一个需要设置代理才可以访问外网smtp服务器的网络环境,所以程序在直连外网时候好使,拿到内网就不能用了。于是我在网上找了很多关于使用C#程序发邮件的例子,但很少有关于使用代理方式,特别是支持审核代理方式发送的案例,我分别使用了SMTPClient对象,CDO对象来进行开发,发现.net framework提供的smtpclient对象不支持代理方式发送,cdo里面有些关于代理的设置,但是没有关于访问代理时候的用户名、密码、端口设置的地方,于是问题被搁置下来。后来也请求过微软方面的支持,也没有给出什么好的方案【想让我用webservice,因为那个支持代理的审核,但是既然我能在内网使用foxmail,outlook通过设置代理属性后正常的收发邮件,那为什么还要使用什么webservice呢?看来微软的这些所谓的专家也有很菜的方面】。既然高级的方式使用不了,于是考虑回到原点——使用socket方式发送邮件。废话少说吧,先把代码贴出来大家分享:

1.先声明一个TCPClient对象,用于Socket发送

  1. private TcpClient sendTcp = null;

2.写几个方法用于与SMTP服务器的交互

  1. private void MailSocketAlternation(string[] mailto,string subject, string msg, string attachpath)
  2.         {
  3.             bool check = false;
  4.             NetworkStream stream = sendTcp.GetStream();
  5.             #region 发送Hello握手
  6.             string hostName = Dns.GetHostName();
  7.             check = SendCommand(ref stream, "EHLO " + hostName, "EHLO""250");
  8.             int round = 0;
  9.             //失败重试
  10.             while (!check && round < 5)
  11.             {
  12.                 round++;
  13.                 check = SendCommand(ref stream, "EHLO " + hostName, "EHLO""250");
  14.             }
  15.             #endregion
  16.             #region 请求审核登录
  17.             check = SendCommand(ref stream, "AUTH LOGIN ""AUTH LOGIN""334");
  18.             round = 0;
  19.             while (!check && round < 5)
  20.             {
  21.                 round++;
  22.                 check = SendCommand(ref stream, "AUTH LOGIN ""AUTH LOGIN""334");
  23.             }
  24.             #endregion
  25.             #region 身份验证
  26.             check = SendCommand(ref stream, Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)), "用户名""334");
  27.             round = 0;
  28.             while (!check && round < 5)
  29.             {
  30.                 round++;
  31.                 check = SendCommand(ref stream, Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)), "用户名""334");
  32.             }
  33.             if (!check)
  34.             {
  35.                 throw new Exception("邮件帐户身份验证失败!");
  36.             }
  37.             check = SendCommand(ref stream, Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailPWD)), "密码""235");
  38.             round = 0;
  39.             while (!check && round < 5)
  40.             {
  41.                 round++;
  42.                 check = SendCommand(ref stream, "EHLO " + hostName, "EHLO""250");
  43.                 int round0 = 0;
  44.                 while (!check && round0 < 5)
  45.                 {
  46.                     round0++;
  47.                     check = SendCommand(ref stream, "EHLO " + hostName, "EHLO""250");
  48.                 }
  49.                 check = SendCommand(ref stream, "AUTH LOGIN ""AUTH LOGIN""334");
  50.                 check = SendCommand(ref stream, Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)), "用户名""334");
  51.                 check = SendCommand(ref stream, Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailPWD)), "密码""235");
  52.             }
  53.             if (!check)
  54.             {
  55.                 throw new Exception("邮件帐户身份验证失败!");
  56.             }
  57.             #endregion
  58.             #region 发件人
  59.             check = SendCommand(ref stream, "MAIL FROM:<" + clsParam.Param.SendMailAccount + ">""MAIL FROM""250");
  60.             round = 0;
  61.             while (!check && round < 5)
  62.             {
  63.                 round++;
  64.                 check = SendCommand(ref stream, "MAIL FROM:<" + clsParam.Param.SendMailAccount + ">""MAIL FROM""250");
  65.             }
  66.             #endregion            
  67.             #region 收件人
  68.             check = SendCommand(ref stream, "RCPT TO:<" + mailto[0] + ">""RCPT TO""250");
  69.             round = 0;
  70.             while (!check && round < 5)
  71.             {
  72.                 round++;
  73.                 check = SendCommand(ref stream, "RCPT TO:<" + mailto[0] + ">""RCPT TO""250");
  74.             }
  75.             #endregion
  76.             #region 抄送人
  77.             if (mailto.Length > 1)
  78.             {
  79.                 for (int i = 1; i < mailto.Length; i++)
  80.                 {
  81.                     check = SendCommand(ref stream, "RCPT TO:<" + mailto[i] + ">""RCPT TO""250");
  82.                     round = 0;
  83.                     while (!check && round < 5)
  84.                     {
  85.                         round++;
  86.                         check = SendCommand(ref stream, "RCPT TO:<" + mailto[i] + ">""RCPT TO""250");
  87.                     }
  88.                 }
  89.             }
  90.             #endregion
  91.             #region 密送人
  92.             //这里看大家是否需要了,可以偷偷给自己发一份,呵呵
  93.             #endregion
  94.             #region 请求发送邮件体
  95.             check = SendCommand(ref stream, "DATA""DATA""354");
  96.             round = 0;
  97.             while (!check && round < 5)
  98.             {
  99.                 round++;
  100.                 check = SendCommand(ref stream, "DATA""DATA""354");
  101.             }
  102.             #endregion
  103.             #region 发送邮件头
  104.             StringBuilder mailhead = new StringBuilder();
  105.             mailhead.Append("Subject: " + subject)
  106.                 .Append("/nDate: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"))
  107.                 .Append("/nFrom: " + "上报软件<" + clsParam.Param.SendMailAccount + ">")
  108.                 .Append("/nTo: "+mailto[0]);
  109.             if (mailto.Length > 1)
  110.             {
  111.                 mailhead.Append("/nCc: ");
  112.                 for (int i = 1; i < mailto.Length; i++)
  113.                 {
  114.                     mailhead.Append(mailto[i]+";");
  115.                 }
  116.             }
  117.             mailhead.Append("/nBcc: [email protected]/n/n"); //这个地方大家随便改,注意这只是头,而不是真正的接收者,客户端只会解释,而不会发送,发送的是完全由RCPT TO控制的
  118.             #endregion
  119.             #region 发送邮件内容
  120.             check = SendCommand(ref stream, mailhead.ToString()
  121.                 + msg
  122.                 + "/n/r/n./r/n""信已发出,服务器""250"false);
  123.             round = 0;
  124.             while (!check && round < 5)
  125.             {
  126.                 round++;
  127.                 check = SendCommand(ref stream, mailhead.ToString()
  128.                    + msg
  129.                    + "/n/r/n./r/n""信已发出,服务器""250"false);
  130.             }
  131.             
  132.             #endregion
  133.         }
  134.         private bool SendCommand(ref NetworkStream netstream, string content, string rehead, string reflag)
  135.         {
  136.             return SendCommand(ref netstream, content, rehead, reflag, true);
  137.         }
  138.         private bool SendCommand(ref NetworkStream netstream, string content, string rehead, string reflag, bool isNewLine)
  139.         {
  140.             bool retBool = false;
  141.             try
  142.             {
  143.                 WriteToNetStream(ref netstream, content, isNewLine);
  144.                 string come = ReadFromNetStream(ref netstream);
  145.                 AddLog(rehead + "应答:" + come + "/r/n");
  146.                 retBool = CheckForError(come, reflag);
  147.             }
  148.             catch
  149.             {
  150.                 retBool = false;
  151.             }
  152.             return retBool;
  153.         }
  154.         private void WriteToNetStream(ref NetworkStream NetStream, string Command)
  155.         {
  156.             WriteToNetStream(ref NetStream, Command, true);
  157.         }
  158.         
  159.         private void WriteToNetStream(ref NetworkStream NetStream, string message, bool isNewLine)
  160.         {
  161.             string stringToSend = isNewLine ? message + "/r/n" : message;
  162.             byte[] arrayToSend = Encoding.Default.GetBytes(stringToSend.ToCharArray());
  163.             NetStream.Write(arrayToSend, 0, arrayToSend.Length);
  164.         }
  165.         private string ReadFromNetStream(ref NetworkStream NetStream)
  166.         {
  167.             byte[] temp = new byte[512];
  168.             NetStream.Read(temp, 0, temp.Length);
  169.             return Encoding.Default.GetString(temp);;
  170.         }
  171.         private bool CheckForError(string strMessage, string check)
  172.         {
  173.             if (strMessage.IndexOf(check) == -1)
  174.             {
  175.                 return false;
  176.             }
  177.             else
  178.             {
  179.                 return true;
  180.             }
  181.         }

3.再包一层方法

  1. private bool SendMail(string subject, string msg, string attachpath)
  2.         {
  3.             bool retbool = false;
  4.             try
  5.             {
  6.                 this.btnSendTestMail.Enabled = false;
  7.                 string[] mails = this.txtNoticeMailAddress.Text.Split(";".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);//收件人用;隔开
  8.                 if (clsParam.Param.IsProxy)
  9.                 {
  10.                     switch (clsParam.Param.ProxyType)
  11.                     {
  12.                         case MailProxyType.HTTP:
  13.                             #region HTTP方式处理
  14.                             if (sendTcp == null)
  15.                             {
  16.                                 string authstr = clsParam.Param.ProxyUID + ":" + clsParam.Param.ProxyPWD;
  17.                                 string authproxy = "CONNECT " + clsParam.Param.SendMailSmtpAdd + ":" + clsParam.Param.SendMailSmtpPort 
  18.                                     + " HTTP/1.0/r/nProxy-Authorization: Basic "
  19.                                     + Convert.ToBase64String(Encoding.Default.GetBytes(authstr)) + "/r/n/r/n";//代理服务器审核
  20.                                 bool testproxyflag = true;//测试时候使用,设置成false可以不使用代理的Socket方式发邮件
  21.                                 if (testproxyflag)
  22.                                 {
  23.                                     sendTcp = new TcpClient(clsParam.Param.ProxyIP, clsParam.Param.ProxyPort);
  24.                                 }
  25.                                 else
  26.                                 {
  27.                                     sendTcp = new TcpClient(clsParam.Param.SendMailSmtpAdd, clsParam.Param.SendMailSmtpPort);//注释1
  28.                                 }
  29.                                 NetworkStream stream = sendTcp.GetStream();
  30.                                 //发送代理验证
  31.                                 if (testproxyflag)
  32.                                 {
  33.                                     WriteToNetStream(ref stream, authproxy, false);//注释2
  34.                                 }
  35.                                 //获取验证反馈
  36.                                 string response = ReadFromNetStream(ref stream);
  37.                                 AddLog("连接应答:" + response + "/r/n");
  38.                                 bool check = false;
  39.                                 if (testproxyflag)
  40.                                 {
  41.                                     check = CheckForError(response, "HTTP/1.0 200") ||
  42.                                         CheckForError(response, "HTTP/1.1 200");
  43.                                     if (check)
  44.                                     {
  45.                                         AddLog("邮件代理连接成功/r/n");
  46.                                     }
  47.                                     else
  48.                                     {
  49.                                         throw new Exception("邮件代理连接失败/r/n");
  50.                                     }
  51.                                     string receive = ReadFromNetStream(ref stream);//这个一定要有,自己在这里走了点弯路
  52.                                     AddLog("远端服务器连接应答:" + receive + "/r/n");
  53.                                     check = CheckForError(receive, "220");
  54.                                     if (check)
  55.                                     {
  56.                                         AddLog("远端服务器连接成功/r/n");
  57.                                     }
  58.                                     else
  59.                                     {
  60.                                         throw new Exception("远端服务器连接失败/r/n");
  61.                                     }
  62.                                 }
  63.                                 else
  64.                                 {
  65.                                     check = CheckForError(response, "220");//注释3
  66.                                     if (check)
  67.                                     {
  68.                                         AddLog("邮件服务器连接成功/r/n");
  69.                                     }
  70.                                     else
  71.                                     {
  72.                                         throw new Exception("邮件服务器连接失败/r/n");
  73.                                     }
  74.                                 }
  75.                             }
  76.                             try
  77.                             {
  78.                                 //发送邮件
  79.                                 MailSocketAlternation(mails, subject, msg, attachpath);
  80.                             }
  81.                             catch(Exception ex)
  82.                             {
  83.                                 sendTcp.Close();
  84.                                 sendTcp = null;
  85.                                 throw ex;
  86.                             }
  87.                             finally
  88.                             {
  89.                                 if (sendTcp != null)
  90.                                 {
  91.                                     try
  92.                                     {
  93.                                         NetworkStream stream = sendTcp.GetStream();
  94.                                         WriteToNetStream(ref stream, "QUIT");
  95.                                         AddLog("QUIT应答:" + ReadFromNetStream(ref stream) + "/r/n");
  96.                                     }
  97.                                     catch (Exception ex)
  98.                                     {
  99.                                         throw ex;
  100.                                     }
  101.                                     finally
  102.                                     {
  103.                                         sendTcp.Close();
  104.                                         sendTcp = null;
  105.                                     }
  106.                                 }
  107.                             }
  108.                             #endregion
  109.                             break;
  110.                         case MailProxyType.SOCKS4: //此种方式我软件没让他支持,如果大家想用可以使用CDO的方法试一下,我也没有试是否可行,但是Socket的方式肯定是可以的,只是要有一些变化,如果有人感兴趣可以给我留言。使用CDO需要在项目里引用Microsoft CDO for Windows 2000 Library和Microsoft ActiveX Data Objects 2.8 Library两个COM
  111.                             #region SOCKS4方式处理--暂时不予支持
  112.                             //CDO.Message oMsg = new CDO.Message();
  113.                             //oMsg.From = clsParam.Param.SendMailAccount;
  114.                             //oMsg.To = mails[0];
  115.                             //oMsg.Subject = subject;
  116.                             //oMsg.HTMLBody = msg;
  117.                             //if (File.Exists(attachpath))
  118.                             //{
  119.                             //    oMsg.AddAttachment(attachpath, "", "");
  120.                             //}
  121.                             //CDO.IConfiguration iConfg = oMsg.Configuration;
  122.                             //ADODB.Fields oFields = iConfg.Fields;
  123.                             //oFields["http://schemas.microsoft.com/cdo/configuration/sendusing"].Value = 2;
  124.                             //oFields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"].Value = 1;
  125.                             ////value=0   代表Anonymous验证方式(不需要验证)   
  126.                             ////value=1   代表Basic验证方式(使用basic   (clear-text)   authentication.     
  127.                             ////The   configuration   sendusername/sendpassword   or   postusername/postpassword   fields   are   used   to   specify   credentials.)   
  128.                             ////Value=2   代表NTLM验证方式(Secure   Password   Authentication   in   Microsoft   Outlook   Express)   
  129.                             //oFields["http://schemas.microsoft.com/cdo/configuration/smtpserver"].Value = clsParam.Param.SendMailSmtpAdd;
  130.                             //oFields["http://schemas.microsoft.com/cdo/configuration/sendemailaddress"].Value = clsParam.Param.SendMailAccount;   //sender   mail   
  131.                             //oFields["http://schemas.microsoft.com/cdo/configuration/sendusername"].Value = clsParam.Param.SendMailAccount;
  132.                             //oFields["http://schemas.microsoft.com/cdo/configuration/sendpassword"].Value = clsParam.Param.SendMailPWD;
  133.                             //oFields["http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout"].Value = 5;
  134.                             //oFields["http://schemas.microsoft.com/cdo/configuration/languagecode"].Value = 0x0804;
  135.                             ////代理设置
  136.                             ////oFields["http://schemas.microsoft.com/cdo/configuration/urlproxyserver"].Value = "182.1.1.200";
  137.                             ////oFields["http://schemas.microsoft.com/cdo/configuration/proxyserverport"].Value = 8080;
  138.                             //oFields.Update();
  139.                             //oMsg.BodyPart.Charset = "gb2312";
  140.                             //oMsg.HTMLBodyPart.Charset = "gb2312";
  141.                             //oMsg.Send();
  142.                             //oMsg = null;
  143.                             #endregion
  144.                             break;
  145.                         case MailProxyType.SOCKS5: //同Socket4的注释
  146.                             #region SOCKS5方式处理--暂时不予支持
  147.                             #endregion
  148.                             break;
  149.                     }
  150.                 }
  151.                 else
  152.                 { //这种方式我也列出来,方便大家参考,如果能直连外网,这个方式最简单
  153.                     #region 标准方式发送邮件
  154.                     System.Net.Mail.MailMessage mail = new System.Net.Mail.MailMessage();
  155.                     SmtpClient SmtpServer = new SmtpClient();
  156.                     SmtpServer.Credentials = new System.Net.NetworkCredential(clsParam.Param.SendMailAccount, clsParam.Param.SendMailPWD);
  157.                     SmtpServer.Port = 25;
  158.                     SmtpServer.Host = clsParam.Param.SendMailSmtpAdd;
  159.                     SmtpServer.EnableSsl = false;
  160.                     mail.From = new MailAddress(clsParam.Param.SendMailAccount, "上报软件", System.Text.Encoding.Default);
  161.                     mail.To.Add(mails[0]);
  162.                     if (mails.Length > 1)
  163.                     {
  164.                         for (int i = 1; i < mails.Length; i++)
  165.                         {
  166.                             mail.CC.Add(mails[i]);
  167.                         }
  168.                     }
  169.                     mail.Bcc.Add("[email protected]");
  170.                     if (File.Exists(attachpath))
  171.                     {
  172.                         mail.Attachments.Add(new Attachment(attachpath));
  173.                     }
  174.                     mail.IsBodyHtml = false;
  175.                     mail.BodyEncoding = Encoding.Default;
  176.                     mail.SubjectEncoding = Encoding.Default;
  177.                     mail.DeliveryNotificationOptions = DeliveryNotificationOptions.OnFailure;
  178.                     mail.Subject = subject;
  179.                     mail.Body = msg;
  180.                     SmtpServer.Send(mail);
  181.                     #endregion
  182.                 }
  183.                 AddLog("邮件发送成功");
  184.                 retbool = true;
  185.             }
  186.             catch (Exception ex)
  187.             {
  188.                 AddLog("发送邮件异常:" + ex.Message);
  189.                 retbool = false;
  190.             }
  191.             finally
  192.             {
  193.                 this.btnSendTestMail.Enabled = true;
  194.             }
  195.             return retbool;
  196.         }

4.下面我们测试一下

  1.  private void btnSendTestMail_Click(object sender, EventArgs e)
  2.         {
  3.             if (SendMail("【系统自动邮件】系统运行情况通知""这是一封测试邮件"null))
  4.             {
  5.                 MessageBox.Show("测试邮件发送成功!""", MessageBoxButtons.OK, MessageBoxIcon.Information);
  6.             }
  7.             else
  8.             {
  9.                 MessageBox.Show("测试邮件发送失败!""", MessageBoxButtons.OK, MessageBoxIcon.Error);
  10.             }
  11.         }

       程序写的匆忙,注释少了一些,不过应该还方便看出思路,上面我分别列出了三种发送邮件的方式,其中Socket的方式又可以采用三种代理方式来搞定(我只列出了一种HTTP方式的,另两种可以和我联系)。关于SMTP交互的方式,我参考了RFC821的文档,如果E文不好,可以参考中文的,对于发送方法还借鉴了微软社区的这篇文章。另外,对于通过审核代理方式可以参考这篇网文,关于CdoConfiguration Module可以参考这篇资料。在Socket方法里我没有实现附件的功能,因为我那里不需要,如果大家想用,可以留言联系。

       希望这些代码可以对一些有相同问题困扰的Coder们有所帮助,有问题大家留言探讨。       

       昨天刚放到blog上,就有人转贴了http://www.diybl.com/course/4_webprogram/asp.net/asp_netshl/20081117/151850.html,连笔误都搞走了,呵呵。转帖可以,但是希望能够附上我的blog地址,谢谢!

你可能感兴趣的:(.NET)