`
csstome
  • 浏览: 1467779 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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

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

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

  1. privateTcpClientsendTcp=null;

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

  1. privatevoidMailSocketAlternation(string[]mailto,stringsubject,stringmsg,stringattachpath)
  2. {
  3. boolcheck=false;
  4. NetworkStreamstream=sendTcp.GetStream();

  5. #region发送Hello握手

  6. stringhostName=Dns.GetHostName();

  7. check=SendCommand(refstream,"EHLO"+hostName,"EHLO","250");

  8. intround=0;
  9. //失败重试
  10. while(!check&&round<5)
  11. {
  12. round++;

  13. check=SendCommand(refstream,"EHLO"+hostName,"EHLO","250");
  14. }

  15. #endregion

  16. #region请求审核登录

  17. check=SendCommand(refstream,"AUTHLOGIN","AUTHLOGIN","334");

  18. round=0;
  19. while(!check&&round<5)
  20. {
  21. round++;
  22. check=SendCommand(refstream,"AUTHLOGIN","AUTHLOGIN","334");
  23. }

  24. #endregion

  25. #region身份验证

  26. check=SendCommand(refstream,Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)),"用户名","334");

  27. round=0;
  28. while(!check&&round<5)
  29. {
  30. round++;
  31. check=SendCommand(refstream,Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)),"用户名","334");
  32. }

  33. if(!check)
  34. {
  35. thrownewException("邮件帐户身份验证失败!");
  36. }

  37. check=SendCommand(refstream,Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailPWD)),"密码","235");

  38. round=0;
  39. while(!check&&round<5)
  40. {
  41. round++;

  42. check=SendCommand(refstream,"EHLO"+hostName,"EHLO","250");
  43. intround0=0;
  44. while(!check&&round0<5)
  45. {
  46. round0++;
  47. check=SendCommand(refstream,"EHLO"+hostName,"EHLO","250");
  48. }

  49. check=SendCommand(refstream,"AUTHLOGIN","AUTHLOGIN","334");

  50. check=SendCommand(refstream,Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailAccount)),"用户名","334");

  51. check=SendCommand(refstream,Convert.ToBase64String(Encoding.Default.GetBytes(clsParam.Param.SendMailPWD)),"密码","235");
  52. }

  53. if(!check)
  54. {
  55. thrownewException("邮件帐户身份验证失败!");
  56. }

  57. #endregion

  58. #region发件人

  59. check=SendCommand(refstream,"MAILFROM:<"+clsParam.Param.SendMailAccount+">","MAILFROM","250");

  60. round=0;
  61. while(!check&&round<5)
  62. {
  63. round++;
  64. check=SendCommand(refstream,"MAILFROM:<"+clsParam.Param.SendMailAccount+">","MAILFROM","250");
  65. }

  66. #endregion

  67. #region收件人

  68. check=SendCommand(refstream,"RCPTTO:<"+mailto[0]+">","RCPTTO","250");

  69. round=0;
  70. while(!check&&round<5)
  71. {
  72. round++;
  73. check=SendCommand(refstream,"RCPTTO:<"+mailto[0]+">","RCPTTO","250");
  74. }

  75. #endregion

  76. #region抄送人

  77. if(mailto.Length>1)
  78. {
  79. for(inti=1;i<mailto.Length;i++)
  80. {
  81. check=SendCommand(refstream,"RCPTTO:<"+mailto[i]+">","RCPTTO","250");

  82. round=0;
  83. while(!check&&round<5)
  84. {
  85. round++;
  86. check=SendCommand(refstream,"RCPTTO:<"+mailto[i]+">","RCPTTO","250");
  87. }
  88. }
  89. }

  90. #endregion

  91. #region密送人

  92. //这里看大家是否需要了,可以偷偷给自己发一份,呵呵

  93. #endregion

  94. #region请求发送邮件体

  95. check=SendCommand(refstream,"DATA","DATA","354");

  96. round=0;
  97. while(!check&&round<5)
  98. {
  99. round++;
  100. check=SendCommand(refstream,"DATA","DATA","354");
  101. }

  102. #endregion

  103. #region发送邮件头

  104. StringBuildermailhead=newStringBuilder();
  105. mailhead.Append("Subject:"+subject)
  106. .Append("/nDate:"+DateTime.Now.ToString("yyyy-MM-ddHH: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(inti=1;i<mailto.Length;i++)
  113. {
  114. mailhead.Append(mailto[i]+";");
  115. }
  116. }

  117. mailhead.Append("/nBcc:a@b.com/n/n"); //这个地方大家随便改,注意这只是头,而不是真正的接收者,客户端只会解释,而不会发送,发送的是完全由RCPT TO控制的

  118. #endregion

  119. #region发送邮件内容

  120. check=SendCommand(refstream,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(refstream,mailhead.ToString()
  128. +msg
  129. +"/n/r/n./r/n","信已发出,服务器","250",false);
  130. }
  131. #endregion
  132. }

  133. privateboolSendCommand(refNetworkStreamnetstream,stringcontent,stringrehead,stringreflag)
  134. {
  135. returnSendCommand(refnetstream,content,rehead,reflag,true);
  136. }

  137. privateboolSendCommand(refNetworkStreamnetstream,stringcontent,stringrehead,stringreflag,boolisNewLine)
  138. {
  139. boolretBool=false;

  140. try
  141. {
  142. WriteToNetStream(refnetstream,content,isNewLine);
  143. stringcome=ReadFromNetStream(refnetstream);
  144. AddLog(rehead+"应答:"+come+"/r/n");
  145. retBool=CheckForError(come,reflag);
  146. }
  147. catch
  148. {
  149. retBool=false;
  150. }

  151. returnretBool;
  152. }

  153. privatevoidWriteToNetStream(refNetworkStreamNetStream,stringCommand)
  154. {
  155. WriteToNetStream(refNetStream,Command,true);
  156. }
  157. privatevoidWriteToNetStream(refNetworkStreamNetStream,stringmessage,boolisNewLine)
  158. {
  159. stringstringToSend=isNewLine?message+"/r/n":message;

  160. byte[]arrayToSend=Encoding.Default.GetBytes(stringToSend.ToCharArray());
  161. NetStream.Write(arrayToSend,0,arrayToSend.Length);
  162. }

  163. privatestringReadFromNetStream(refNetworkStreamNetStream)
  164. {
  165. byte[]temp=newbyte[512];
  166. NetStream.Read(temp,0,temp.Length);

  167. returnEncoding.Default.GetString(temp);;
  168. }

  169. privateboolCheckForError(stringstrMessage,stringcheck)
  170. {
  171. if(strMessage.IndexOf(check)==-1)
  172. {
  173. returnfalse;
  174. }
  175. else
  176. {
  177. returntrue;
  178. }
  179. }

3.再包一层方法

  1. privateboolSendMail(stringsubject,stringmsg,stringattachpath)
  2. {
  3. boolretbool=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. caseMailProxyType.HTTP:

  13. #regionHTTP方式处理

  14. if(sendTcp==null)
  15. {
  16. stringauthstr=clsParam.Param.ProxyUID+":"+clsParam.Param.ProxyPWD;

  17. stringauthproxy="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. booltestproxyflag=true;//测试时候使用,设置成false可以不使用代理的Socket方式发邮件

  21. if(testproxyflag)
  22. {
  23. sendTcp=newTcpClient(clsParam.Param.ProxyIP,clsParam.Param.ProxyPort);
  24. }
  25. else
  26. {
  27. sendTcp=newTcpClient(clsParam.Param.SendMailSmtpAdd,clsParam.Param.SendMailSmtpPort);//注释1
  28. }

  29. NetworkStreamstream=sendTcp.GetStream();

  30. //发送代理验证
  31. if(testproxyflag)
  32. {
  33. WriteToNetStream(refstream,authproxy,false);//注释2
  34. }

  35. //获取验证反馈
  36. stringresponse=ReadFromNetStream(refstream);

  37. AddLog("连接应答:"+response+"/r/n");

  38. boolcheck=false;

  39. if(testproxyflag)
  40. {
  41. check=CheckForError(response,"HTTP/1.0200")||
  42. CheckForError(response,"HTTP/1.1200");

  43. if(check)
  44. {
  45. AddLog("邮件代理连接成功/r/n");
  46. }
  47. else
  48. {
  49. thrownewException("邮件代理连接失败/r/n");
  50. }

  51. stringreceive=ReadFromNetStream(refstream);//这个一定要有,自己在这里走了点弯路

  52. AddLog("远端服务器连接应答:"+receive+"/r/n");

  53. check=CheckForError(receive,"220");

  54. if(check)
  55. {
  56. AddLog("远端服务器连接成功/r/n");
  57. }
  58. else
  59. {
  60. thrownewException("远端服务器连接失败/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. thrownewException("邮件服务器连接失败/r/n");
  73. }
  74. }
  75. }

  76. try
  77. {
  78. //发送邮件
  79. MailSocketAlternation(mails,subject,msg,attachpath);
  80. }
  81. catch(Exceptionex)
  82. {
  83. sendTcp.Close();
  84. sendTcp=null;

  85. throwex;
  86. }
  87. finally
  88. {
  89. if(sendTcp!=null)
  90. {
  91. try
  92. {
  93. NetworkStreamstream=sendTcp.GetStream();

  94. WriteToNetStream(refstream,"QUIT");

  95. AddLog("QUIT应答:"+ReadFromNetStream(refstream)+"/r/n");
  96. }
  97. catch(Exceptionex)
  98. {
  99. throwex;
  100. }
  101. finally
  102. {
  103. sendTcp.Close();
  104. sendTcp=null;
  105. }
  106. }
  107. }

  108. #endregion

  109. break;
  110. caseMailProxyType.SOCKS4: //此种方式我软件没让他支持,如果大家想用可以使用CDO的方法试一下,我也没有试是否可行,但是Socket的方式肯定是可以的,只是要有一些变化,如果有人感兴趣可以给我留言。使用CDO需要在项目里引用Microsoft CDO for Windows 2000 Library和Microsoft ActiveX Data Objects 2.8 Library两个COM

  111. #regionSOCKS4方式处理--暂时不予支持

  112. //CDO.MessageoMsg=newCDO.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.IConfigurationiConfg=oMsg.Configuration;
  122. //ADODB.FieldsoFields=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. ////Theconfigurationsendusername/sendpasswordorpostusername/postpasswordfieldsareusedtospecifycredentials.)
  128. ////Value=2代表NTLM验证方式(SecurePasswordAuthenticationinMicrosoftOutlookExpress)
  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;//sendermail
  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. caseMailProxyType.SOCKS5: //同Socket4的注释

  146. #regionSOCKS5方式处理--暂时不予支持

  147. #endregion

  148. break;
  149. }
  150. }
  151. else
  152. { //这种方式我也列出来,方便大家参考,如果能直连外网,这个方式最简单
  153. #region标准方式发送邮件

  154. System.Net.Mail.MailMessagemail=newSystem.Net.Mail.MailMessage();
  155. SmtpClientSmtpServer=newSmtpClient();
  156. SmtpServer.Credentials=newSystem.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=newMailAddress(clsParam.Param.SendMailAccount,"上报软件",System.Text.Encoding.Default);

  161. mail.To.Add(mails[0]);

  162. if(mails.Length>1)
  163. {
  164. for(inti=1;i<mails.Length;i++)
  165. {
  166. mail.CC.Add(mails[i]);
  167. }
  168. }

  169. mail.Bcc.Add("a@b.com");

  170. if(File.Exists(attachpath))
  171. {
  172. mail.Attachments.Add(newAttachment(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(Exceptionex)
  187. {
  188. AddLog("发送邮件异常:"+ex.Message);

  189. retbool=false;
  190. }
  191. finally
  192. {
  193. this.btnSendTestMail.Enabled=true;
  194. }

  195. returnretbool;
  196. }

4.下面我们测试一下

  1. privatevoidbtnSendTestMail_Click(objectsender,EventArgse)
  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们有所帮助,有问题大家留言探讨。


原文链接:http://blog.csdn.net/luyifeiniu/article/details/3320651
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics