vb.net 在内部交换 smtp 服务器上使用 system.net.mail 的大量消息超时
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16019264/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Timeout on high amount of messages with system.net.mail on internal exchange smtp server
提问by TNGPicard_Work
I'm working on a program to send weekly reminders / updates to each sales employee at our company. About 120 per week, automated, in a short amount of time. This is an internal program, to internal recipients only and I am not concerned about spam or unwanted messages and unsubscription is not an option. Each message is personalized for each sales staff member based on their customer list and contains weekly "to do" items for them to call customers.
我正在开发一个计划,每周向我们公司的每位销售员工发送提醒/更新。每周大约 120 个,自动化,在短时间内。这是一个内部程序,仅供内部收件人使用,我不担心垃圾邮件或不需要的邮件,而且取消订阅不是一种选择。每条消息都是根据每个销售人员的客户列表为他们个性化的,并包含每周“要做”的项目,供他们打电话给客户。
I'm running Exhange 2007 internally with an open internal SMTP connector for both my development environment and the server that runs these automation projects. The program is working fine for one or two stores but when I run it for every store I get a time out somewhere after 110 messages have been sent.
我在内部运行 Exhange 2007,并为我的开发环境和运行这些自动化项目的服务器提供一个开放的内部 SMTP 连接器。该程序对一两个商店运行良好,但是当我为每个商店运行它时,我在发送了 110 条消息后在某处超时。
I'm not attempting to queue mail or hold them in blocks, as I iterate through the sale person list, and do each respective lookup and message build, I am attempting to send messages with the following sub.
当我遍历销售人员列表并进行每个相应的查找和消息构建时,我并没有尝试将邮件排队或将它们放在块中,我正在尝试使用以下子发送消息。
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
I have attempted to increase the client time out to 20 seconds and I'm explicitly attempting to close out the client connection to the mail server after each message by setting it to nothing.
我试图将客户端超时增加到 20 秒,并且我明确地尝试在每条消息之后通过将其设置为空来关闭与邮件服务器的客户端连接。
Everything LOOKS to proceed OK until the very end, I have removed our sales person names from the below output snippet. Right now everything is coming to me since I'm running this in debug mode, we have not attempted a live run to the entire company yet so I don't know if it is since everything is going to one recipient.
一切看起来都很好,直到最后,我已经从下面的输出片段中删除了我们的销售人员姓名。现在一切都来找我了,因为我在调试模式下运行它,我们还没有尝试对整个公司进行实时运行,所以我不知道是否是因为一切都将发送给一个收件人。
After I get the error (connection time out) and the system resumes, I get any messages that are sent after the exception and only AFTER the program terminates. In prior versions of .NET I know that programs had to exit or the thread had to end before messages would be sent but other programs I have going I don't generally have this in .NET 4 any more.
在我收到错误(连接超时)并且系统恢复后,我会收到在异常之后并且仅在程序终止之后发送的任何消息。在 .NET 的早期版本中,我知道在发送消息之前程序必须退出或线程必须结束,但我正在使用的其他程序我通常不再在 .NET 4 中使用它。
Output including error; first part has been snipped
输出包括错误;第一部分已被剪断
...
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
I have also tried sending for each individual store and smaller subsets. I only receive this error when I run for the entire company all at one time.
我还尝试为每个单独的商店和较小的子集发送。当我一次为整个公司竞选时,我只会收到此错误。
I don't see anything in Exchange prohibiting this and I don't see a high message queue waiting. Any suggestions on a different method to send the messages or something else to try? (I would prefer not to use a 3rd party dll or outside component).
我在 Exchange 中没有看到任何禁止这样做的内容,也没有看到等待的高消息队列。关于发送消息的不同方法或其他尝试的任何建议?(我不想使用第 3 方 dll 或外部组件)。
回答by Jamie Clayton
I've experienced the pain of this issue. After 2 years of research I've not been able to pinpoint an exact cause of the issue with my .net application. There are a number of issues and implementation options that will help minimize the impact of this exception. We are now at a point this problem doesn't impact the end users in a noticeable way.
我经历过这个问题的痛苦。经过 2 年的研究,我无法确定我的 .net 应用程序出现问题的确切原因。有许多问题和实施选项将有助于最大限度地减少此例外的影响。我们现在处于这个问题不会以明显的方式影响最终用户的程度。
- You will need to ensure some of the SMTP configuration variables are set in the *.config to help tune the application to your environment. SMTP Client Timeout, Retry Attempts, Time between retries etc. We found about 20 seconds between attempts and about 5 attempts before completely stopping worked.
- You will need to implement a logging mechanism to record when and how frequently they occur, so you can set the variables above. I was looking for volume of emails, time of day, service pack installations, virus checker or firewall interference from the machines and servers event log, to try and find a cause.
- Implement a non Blockingcollectionto manage the sending of all the emails, because if your sending a large volume via code, your going to run into the SMTP 4.4.1 timeout error. This a producer consumerpattern and I've found this a relatively easy implementation to implement and very successful for this issue. The queue allows you to ensure the emails will get sent eventually. If you want a full fail safe you can persist that queue to disk as well. We never need to fall back onto that piece of code.
- Faking or Mocking the .net SMTP implementation to unit test your code for this issue was not easy to do without third party tools. I didn't go down this path, because at the end of the day there were too many parts outside of my control. Basically I'm just going to assume it will always fail and I place lots of fallback options to wait for the problem to clear itself.
- 您需要确保在 *.config 中设置了一些 SMTP 配置变量,以帮助将应用程序调整到您的环境。SMTP 客户端超时、重试尝试、重试之间的时间等。我们发现尝试之间大约有 20 秒,在完全停止工作之前大约有 5 次尝试。
- 您将需要实施日志记录机制来记录它们发生的时间和频率,以便您可以设置上面的变量。我正在寻找来自机器和服务器事件日志的电子邮件量、一天中的时间、服务包安装、病毒检查程序或防火墙干扰,以尝试找出原因。
- 实施非Blockingcollection来管理所有电子邮件的发送,因为如果您通过代码发送大量邮件,您将遇到 SMTP 4.4.1 超时错误。这是一个生产者消费者模式,我发现这是一个相对容易实现的实现,并且在这个问题上非常成功。队列允许您确保最终发送电子邮件。如果你想要一个完整的故障安全,你也可以将该队列持久化到磁盘。我们永远不需要回到那段代码上。
- 如果没有第三方工具,伪造或模拟 .net SMTP 实现以针对此问题对您的代码进行单元测试并不容易。我没有走这条路,因为在一天结束时,有太多我无法控制的部分。基本上我只是假设它总是会失败,并且我放置了很多后备选项来等待问题自行解决。
Things we tried that that didn't seem to make a difference to the frequency of 4.4.1 timeout exceptions.
我们尝试过的事情似乎对 4.4.1 超时异常的频率没有影响。
- Throttling the rate the emails get sent to exchange.
- Changing the time of day they get sent (aka out of hours)
- Batching the emails into small blocks and then clearing all the .net components from memory via garbage collection.
- 限制电子邮件发送到交换的速率。
- 更改他们发送的时间(又名非工作时间)
- 将电子邮件分成小块,然后通过垃圾收集从内存中清除所有 .net 组件。
Hope that helps.
希望有帮助。
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)

