内部交换smtp服务器上带有system.net.mail的大量消息超时

时间:2013-04-15 15:39:40

标签: vb.net system.net.mail

我正在制定一项计划,向我们公司的每位销售员工发送每周提醒/更新。每周大约120个,在很短的时间内自动化。这是一个内部程序,仅限内部收件人,我不关心垃圾邮件或不需要的邮件,取消订阅不是一种选择。每条消息都根据客户列表为每位销售人员个性化,并包含每周“待办事项”项目,以便他们致电客户。

我在内部使用开放式内部SMTP连接器运行Exhange 2007,用于我的开发环境和运行这些自动化项目的服务器。该程序适用于一个或两个商店,但是当我为每个商店运行它时,我发送了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

我也尝试过发送每个商店和较小的子集。当我一次竞选整个公司时,我只收到这个错误。

我在Exchange中没有看到任何禁止此操作的内容,我看不到高消息队列在等待。有关使用其他方法发送消息或其他内容的任何建议吗? (我不想使用第三方dll或外部组件)。

1 个答案:

答案 0 :(得分:3)

我经历过这个问题的痛苦。经过2年的研究,我无法通过.net应用程序查明问题的确切原因。有许多问题和实现选项有助于最大限度地减少此异常的影响。我们现在处于这个问题,这个问题并没有以明显的方式影响最终用户。

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

    1. 限制电子邮件发送到交易所的费率。
    2. 更改他们发送的时间(也就是非工作时间)
    3. 将电子邮件分成小块,然后通过垃圾回收清除内存中的所有.net组件。
    4. 希望有所帮助。

      Code 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)