C#使用dotAPNS发送iOS推送运行一段时间失效需重启服务器如何解决
问题根因及修复方案
首先梳理你遇到的运行一段时间后无响应的核心原因,对应修复方案如下:
核心问题点
- HttpClient实例重复创建导致端口耗尽:你每次调用
SendIOSNotification方法都会新建HttpClient实例,HttpClient设计为单例复用,频繁创建会导致服务器TCP端口被快速占满,AWS环境下默认端口回收速度慢,端口耗尽后新的请求无法建立连接,就会卡在发送请求的位置,重启服务器后端口全部释放,所以会恢复正常,完全符合你描述的现象。 .Result阻塞异步调用导致死锁:代码使用.Result同步等待异步方法返回,在ASP.NET(代码中存在HttpContext.Current判定为ASP.NET框架)的同步上下文机制下,非常容易出现死锁:异步回调需要等待同步上下文释放才能继续执行,而.Result会阻塞当前线程占用同步上下文,两者互相等待就会出现永久无响应的情况。ApplePush对象复用导致逻辑错误:你在循环外部创建了ApplePush实例,在循环遍历设备token的过程中不断往同一个实例里新增token、自定义属性,会导致后续推送的payload里携带大量前序推送的冗余数据,payload体积不断膨胀,轻则推送逻辑错误,重则触发APNs的请求大小限制,导致请求卡住。
修复方案
1. 改造为单例复用ApnsClient
将ApnsClient的初始化逻辑从方法中移出,改为全局单例,整个应用生命周期只初始化一次:
// 全局静态字段,仅初始化一次 private static readonly ApnsClient _apnsClient; // 应用启动时执行一次初始化(比如Global.asax的Application_Start方法里) static YourClassName() { string p8File = HttpContext.Current.Server.MapPath("*******"); var options = new ApnsJwtOptions() { BundleId = "*****", CertFilePath = p8File, KeyId = "******", TeamId = "*****" }; // 给HttpClient设置超时,避免永久卡住 var httpClient = new HttpClient(new WinHttpHandler()) { Timeout = TimeSpan.FromSeconds(15) }; _apnsClient = ApnsClient.CreateUsingJwt(httpClient, options); }
2. 修复异步调用和ApplePush复用问题
方法改为异步实现,避免死锁,每次循环新建ApplePush实例:
public async Task<bool> SendIOSNotification(Dictionary<string, Dictionary<string, string>> iosDictionary, ILog log = null) { foreach (var itemKey in iosDictionary) { _log.DebugFormat("========== IOS =========="); _log.DebugFormat("Device Item {0}", itemKey); string payLoadCatgory = string.Empty; // 每次发新的推送都新建ApplePush实例 var push = new ApplePush(ApplePushType.Alert) .AddBadge(1) .AddSound("sound.caf") .AddCustomProperty("content-available", "1", true) .AddToken(itemKey.Key.ToString()); foreach (var itemValue in itemKey.Value) { try { if (!itemValue.Key.ToLower().Contains("id")) { push.AddCustomProperty(itemValue.Key, itemValue.Value, true); } else { push.AddCustomProperty(itemValue.Key, Convert.ToInt64(itemValue.Value), true); } if (itemValue.Key.ToLower().Contains("category")) { payLoadCatgory = itemValue.Value; } } catch (Exception ex) { log.Error(string.Format("Error in send Notification to IOS :: DeviceToken is {0}", itemKey.Key)); } } try { _log.Debug("Start send notification"); // 用await替代.Result,避免死锁 var response = await _apnsClient.SendAsync(push).ConfigureAwait(false); _log.Debug("Sent notification"); if (response.IsSuccessful) { log.Info(string.Format("An alert push has been successfully sent!")); return true; } else { switch (response.Reason) { case ApnsResponseReason.BadCertificateEnvironment: break; default: throw new ArgumentOutOfRangeException(nameof(response.Reason), response.Reason, null); } log.Error(string.Format("Failed to send a push, APNs reported an error: " + response.ReasonString)); } } catch (TaskCanceledException) { log.Error(string.Format("Failed to send a push: HTTP request timed out.")); } catch (HttpRequestException ex) { log.Error(string.Format("Failed to send a push. HTTP request failed: " + ex)); } catch (Exception ex) { log.Error(string.Format("Failed to send a push. HTTP request failed: " + ex)); return false; } } return false; }
如果实在不能改方法为异步,也可以用以下方式替代.Result避免死锁:
var response = _apnsClient.SendAsync(push).ConfigureAwait(false).GetAwaiter().GetResult();
内容的提问来源于stack exchange,提问作者Pummy Kumari
相关产品推荐
相关产品推荐

