2013-04-15 67 views
2

我正在开发一个程序,向我们公司的每位销售员工发送每周提醒/更新。每周大约120次,自动化,在短时间内完成。这是一个内部程序,仅适用于内部收件人,我不关心垃圾邮件或不需要的邮件,并且退订不是一种选择。每条消息都是根据每个销售人员的客户名单进行个性化设置的,并且包含每周“待办事项”,供他们打电话给客户。在内部交换smtp服务器上使用system.net.mail发送大量消息的超时

我跑Exhange 2007年内部与我的两个开发环境和运行这些自动化项目的服务器的开放的内部SMTP连接器。该程序对于一两家商店工作正常,但是当我为每家商店运行它时,我会在发送110封邮件后的某个地方得到一个时间。

我不会试图排队邮件或持有他们的块,因为我通过出售人名单迭代,并做相应的每一个查询和信息积累,我试图用下面的子发送消息。

Sub doMail(ByRef MessageBody As String, ByVal nameString As String, ByVal sendTo As ArrayList, Optional ByVal markurgent As Boolean = False, _ 
       Optional ByVal sendCC As ArrayList = Nothing, Optional ByVal sendBcc As ArrayList = Nothing) 
    Try 
     ' Setup Mail Message 
     Dim oClient As SmtpClient = New SmtpClient(ConfigurationManager.AppSettings("mail_server").ToString()) 
     oClient.Timeout = 20000 
     'oClient.Port = 50747 
     Dim objMessage As New MailMessage() 
     objMessage.From = New System.Net.Mail.MailAddress(ConfigurationManager.AppSettings("mail_from_address").ToString, ConfigurationManager.AppSettings("mail_from_name").ToString) 
     objMessage.Subject = String.Format("Weekly Hitlist Report ~ {0}", nameString) 

     If (ConfigurationManager.AppSettings("debugMode").ToString() = 1) Then 
      Dim debugSB As StringBuilder = New StringBuilder 

      For Each s As String In sendTo 
       debugSB.AppendLine(String.Format("To: {0}<br>", s)) 
      Next 
      For Each s As String In sendCC 
       debugSB.AppendLine(String.Format("cc: {0}<br>", s)) 
      Next 
      For Each s As String In sendBcc 
       debugSB.AppendLine(String.Format("bcc: {0}<br>", s)) 
      Next 
      MessageBody = String.Format("Debug Mode is Active. <br> {0} <br><hr noshade>{1}", debugSB.ToString, MessageBody) 
      objMessage.To.Add(ConfigurationManager.AppSettings("mail_to_address").ToString) 
     Else 
      For Each receiver As String In sendTo 
       Try 
        objMessage.To.Add(receiver.Trim()) 
       Catch ex As Exception 
        ' do nothing 
       End Try 
      Next 
      For Each receiver As String In sendCC.ToArray 
       Try 
        objMessage.CC.Add(receiver.Trim()) 
       Catch ex As Exception 
        ' do nothing 
       End Try 
      Next 
      For Each receiver As String In sendBcc 
       Try 
        objMessage.Bcc.Add(receiver.Trim()) 
       Catch ex As Exception 
        ' do nothing 
       End Try 
      Next 
     End If 



     objMessage.Body = MessageBody 
     objMessage.Priority = IIf(markurgent, MailPriority.High, MailPriority.Normal) 
     objMessage.IsBodyHtml = True 
     oClient.Send(objMessage) 
     oClient = Nothing 
     Console.WriteLine(String.Format("{1} - message sent - {0} ", nameString, Now())) 
    Catch ex As Exception 
     Console.WriteLine(ex.Message) 
     Console.WriteLine("****************") 
     Console.WriteLine(ex.StackTrace) 
    End Try 
End Sub 

我试图增加客户端超时为20秒,我明确试图将其设置为无每个消息后关闭了客户端连接到邮件服务器。

一切看起来都进行确定,直到最后一刻,我从下面的输出片段中删除我们的销售人员的名字。现在,一切都在我的身上,因为我在调试模式下运行它,我们还没有试图对整个公司进行实时运行,所以我不知道是否因为所有事情都发生在一个接收者身上。

后,我得到的错误(连接超时)和系统恢复,我得到的异常后,并只有在程序终止发送的任何消息。在.NET的早期版本中,我知道程序必须退出,或者线程必须在发送消息之前结束,但是我有其他程序,我通常不会在.NET 4中再使用它。

输出包括错误;第一部分已经被剪断

... 
4/15/2013 9:46:36 AM - message sent - 
4/15/2013 9:46:41 AM - message sent - 
4/15/2013 9:46:46 AM - message sent - 
4/15/2013 9:46:51 AM - message sent - Store 14 Unassigned 
4/15/2013 9:46:56 AM - message sent - 
4/15/2013 9:47:01 AM - message sent - 
4/15/2013 9:47:06 AM - message sent - 
4/15/2013 9:47:11 AM - message sent - 
4/15/2013 9:47:16 AM - message sent - 
4/15/2013 9:47:21 AM - message sent - 
4/15/2013 9:47:26 AM - message sent - 
4/15/2013 9:47:31 AM - message sent - 
4/15/2013 9:47:36 AM - message sent - 
4/15/2013 9:47:41 AM - message sent - 
Service not available, closing transmission channel. The server response was: 4.4.1 Connection timed out 
**************** 
    at System.Net.Mail.MailCommand.CheckResponse(SmtpStatusCode statusCode, String response) 
    at System.Net.Mail.MailCommand.Send(SmtpConnection conn, Byte[] command, String from) 
    at System.Net.Mail.SmtpTransport.SendMail(MailAddress sender, MailAddressCollection recipients, String deliveryNotify, SmtpFailedRecipientException& exception) 
    at System.Net.Mail.SmtpClient.Send(MailMessage message) 
    at HitListRunner_Main.Module1.doMail(String& MessageBody, String nameString, ArrayList sendTo, Boolean markurgent, ArrayList sendCC, ArrayList sendBcc) in C:\Users\markl\Documents\Visual Studio 2010\Projects\HitListRunner\HitListRunner-Main\Module1.vb:line 391 
4/15/2013 9:47:46 AM - message sent - Store 20 Unassigned 
4/15/2013 9:47:51 AM - message sent - Store 98 Unassigned 
4/15/2013 9:47:58 AM - message sent - !! - UNDETERMINED LIST - !! 
ending - enter to exit 

我也曾尝试发送每个个体商店和较小的子集。我只在一次跑完整个公司时才收到这个错误。

我没有看到任何东西交换这种禁止,我没有看到一个高的消息队列等待。任何建议在不同的方法发送邮件或其他东西尝试? (我不想使用第三方DLL或外部组件)。

+0

多少来,CC,BCC是这样被一次送往?你可以尝试在每次发送后延迟一段时间,看看是否有影响。 – dbasnett

回答

2

我已经经历过这个问题的痛苦。经过2年的研究后,我无法用我的.net应用程序查明问题的确切原因。有许多问题和实施选项可以帮助最大限度地减少此例外的影响。我们现在正处在一个问题上,这个问题不会以明显的方式影响最终用户。

  1. 您需要确保在* .config中设置了一些SMTP配置变量,以帮助将应用程序调整到您的环境。 SMTP客户端超时,重试尝试次数,重试次数等。我们在尝试之间发现了大约20秒,在完全停止工作之前发现了大约5次尝试。
  2. 您将需要实现一个日志记录机制来记录它们发生的时间和频率,因此您可以设置上面的变量。我一直在寻找电子邮件的数量,时间,服务包安装,病毒检查程序或来自机器和服务器事件日志的防火墙干扰,以尝试找到原因。
  3. 执行非Blockingcollection来管理所有电子邮件的发送,因为如果您通过代码发送大量音量,您将遇到SMTP 4.4.1超时错误。这是一个producer consumer模式,我发现这是一个相对容易实现的实现并且对于这个问题非常成功。该队列允许您确保电子邮件将最终发送。如果你想要一个完整的失败安全,你可以将该队列保存到磁盘。我们永远不需要回到那段代码。
  4. 如果没有第三方工具,伪造或模拟.net SMTP实现来单元测试这个问题的代码并不容易。我没有走这条路,因为在一天结束时,我的控制之外有太多的零件。基本上我只是假设它总是会失败,我会放置很多后备选项来等待问题自行清除。

我们试过的事情似乎没有对4.4.1超时异常的频率产生影响。

  1. 限制电子邮件发送到交换的速率。
  2. 更改他们发送的时间(即时间外)
  3. 将电子邮件分组为小块,然后通过垃圾回收从内存中清除所有.net组件。

希望有所帮助。

代码Snippits

''' <summary> 
''' Provide a mechanism to throttle the subsequent attempts to send emails. 
''' </summary> 
''' <param name="emailAddress"></param> 
''' <param name="attemptNumber"></param> 
''' <remarks>Need to implement a more dynamic function as attempts persist, pause for longer for a number of attempt, then add the email to a failed queue.</remarks> 
Private Shared Sub RestBeforeSendingAgain(ByVal emailAddress As String, ByVal attemptNumber As Integer) 

    If attemptNumber > 1 Then 

     ' Wait 30 seconds in between every attempt, or the value configured in the config file. 
     Dim retryIntervalInSeconds As Integer = 30 
     If Not String.IsNullOrEmpty(System.Configuration.ConfigurationManager.AppSettings.Item("SMTPFailureReTryIntervalInSeconds")) Then 
      Integer.TryParse(System.Configuration.ConfigurationManager.AppSettings.Item("SMTPFailureReTryIntervalInSeconds"), retryIntervalInSeconds) 
     End If 
     logger.Warn("Problem emailing {0}. Waiting {1} seconds to help clear technical issues.", emailAddress, retryIntervalInSeconds.ToString) 
     System.Threading.Thread.Sleep(System.TimeSpan.FromSeconds(retryIntervalInSeconds)) 

    End If 
End Sub 

    ''' <summary> 
''' Attempt to send an email message and record any issues with that mailmessage to the application log. 
''' Allows the process to know the success or failure of that approach. 
''' </summary> 
''' <param name="mailClient"></param> 
''' <param name="mailMessage"></param> 
''' <param name="attemptNumber"></param> 
''' <returns>The success or failure of the SMTP server to send the message without causing an error.</returns> 
''' <remarks></remarks> 
Private Shared Function AttemptToSendEmail(ByRef mailClient As System.Net.Mail.SmtpClient, ByRef mailMessage As MailMessage, ByVal attemptNumber As Integer) As Boolean 
    Dim result As Boolean = False 
    Try 
     logger.Trace("Started: AttemptToSendEmail", attemptNumber) 

     RestBeforeSendingAgain(mailMessage.To.ToString, attemptNumber) 
     mailClient.Send(mailMessage) 
     result = True 

    Catch smtpEx As SmtpException 
     result = False 
     logger.ErrorException(String.Format("{3}{1}{0}{1}{2}", ExceptionFormater.FormatException(smtpEx), Environment.NewLine, ExceptionFormater.FormatStack(smtpEx), "AttemptToSendEmail failed with Smtp exception."), smtpEx) 

    Catch ex As Exception 
     result = False 
     logger.ErrorException(String.Format("{3}{1}{0}{1}{2}", ExceptionFormater.FormatException(ex), Environment.NewLine, ExceptionFormater.FormatStack(ex), "AttemptToSendEmail failed with generic exception."), ex) 

    Finally 

     If result Then 
      logger.Trace("Succeeded: To email {0} on attempt {1}.", mailMessage.To.ToString, attemptNumber) 
     Else 
      logger.Warn("Failed: To email {0} on attempt {1}.", mailMessage.To.ToString, attemptNumber) 
     End If 

    End Try 

    Return result 

End Function 

' Configure the timeout in milliseconds from the App.Config file. 
' 200 seconds = 200,000 miliseconds 
MySmtpClient.Timeout = CInt(TimeSpan.FromSeconds(SmtpClientTimeoutSeconds).TotalMilliseconds) 
相关问题