MailKit调用DisconnectAsync仍出现连接数过多异常问题咨询
异常成因及解决方案
核心异常成因
- SMTP服务端连接限制触发:绝大多数SMTP服务(包括自建邮局、第三方邮件推送服务)都会配置两个核心限制:一是短时间内允许的新建TCP连接速率上限,二是同时存活的最大连接数上限。你每次调用
TransmitMail都会新建一个独立SmtpClient连接,即使客户端主动执行了断开操作,只要短时间内新建连接的频率/同时存活连接数超过服务端阈值,就会抛出"Too many connections open"错误。 - TCP TIME_WAIT状态的影响:TCP协议要求主动发起断开的一方(此处是你的客户端程序)在完成四次挥手后,需要保持2MSL(通常为1-4分钟,不同操作系统配置有差异)的TIME_WAIT状态,该状态下对应的端口、连接记录不会被立即回收。即使你
await了DisconnectAsync执行完成,也只是完成了应用层的断开逻辑,操作系统底层的TCP连接资源还没有释放,服务端的连接计数也不会立刻清零,短时间频繁新建连接就会导致计数堆积超限。 - 并发调用的叠加影响:如果你的
TransmitMail方法是被多线程/并发场景调用,即使每个方法内部都await了所有异步操作,只要同一时间有多个方法实例在执行,就会同时存在多个活跃连接,很容易触达服务端连接数上限。
你新增的5秒休眠逻辑,本质上限制了新建SMTP连接的速率,同时给足了时间让上一个连接的TIME_WAIT状态完成回收、服务端连接计数下降,因此暂时规避了错误,但这属于治标不治本的方案,会大幅降低邮件发送效率。
优化方案
- 复用SmtpClient连接:MailKit的
SmtpClient支持单连接下批量发送多封邮件,你可以在程序初始化时创建一个SmtpClient实例,完成连接、鉴权后,批量调用SendAsync发送所有邮件,所有邮件发送完成后再统一执行DisconnectAsync,从根源上避免频繁新建销毁连接。
示例代码逻辑参考:
' 初始化全局复用的SmtpClient Private Shared _smtpClient As SmtpClient Private Shared Async Function InitSmtpClient() As Task _smtpClient = New SmtpClient() Await _smtpClient.ConnectAsync(Host, 587, False) Await _smtpClient.AuthenticateAsync(UserName, Password) End Function ' 发送邮件直接复用已有连接 Private Shared Async Function TransmitMail(mail As MimeMessage) As Task(Of Boolean) Try Await _smtpClient.SendAsync(mail) Return True Catch ex As Exception ' 连接断开时主动重连 Await InitSmtpClient() Await _smtpClient.SendAsync(mail) Return True End Try End Function
- 配置连接池:如果需要并发发送邮件,可以自行实现简单的
SmtpClient连接池,控制最大连接数(参考服务端给出的连接数上限设置),避免无限制新建连接。 - 适配服务端限流规则:如果使用第三方SMTP服务,可以查阅官方文档的限流规则,调整发送速率,避免触发拦截。
内容的提问来源于stack exchange,提问作者Gerhard
相关产品推荐
相关产品推荐

