前记:
  相信大家在搞IOS推送服务的开发时, 会直接使用javapns api来简单实现, 调试也直连Apple的APNS服务(产品/测试版)来实现. 很少有人会写个APNS的桩服务, 事实也是如此. 只是当时我所面临的应用场景有些特殊, 为了测试服务的性能和调试功能方便, 特地写了APNS的桩服务(其实主要原因是当时的iphone测试机, 被小组长"霸占"占为己用, ⊙﹏⊙b汗). 在此写一篇关于APNS桩服务的文章, 以此纪念逝去的"青春", 也希望对读者有所帮助.
  有些事情回想起来, 历历在目, 清晰异常, 仿佛发生在昨天. 犹记一年前, 开发IOS的APNS推送后端服务, 吐血三升...

秉承:
  上一篇文章, 详见: Apple的APNS桩推送服务的实现(1) 
  文章简要介绍了APNS协议/证书管理/JSSE的API的一些基础概念和实现原理.

需求分析:
  从开发者的角度去理解和分析需求. 对于IOS的消息推送的功能和性能测试, 大致可分为如下几种:
  1). 推送平台QPS/RT的性能指标
  2). Bad Device Token对服务的影响评估
  3). 证书对服务的影响评估
  4). 推送可靠性评估(推送弱ack机制, 丢消息和消息重复之间的折中)
  通过记录日志来评估QPS/RT和服务的质量, 引入黑名单机制来模拟bad device token对推送服务的影响评估.

技术分析:
  对APNS服务的理解和自身的功能/性能测试需求:
  1). APNS推送服务采用SSL长连接/二进制流/异步的方式来实现, 追求推送消息的吞吐量.
  2). APNS支持V1/V2两种二进制协议, 遇到异常时, APNS的默认行为不一样.
  3). APNS客户端是TSL(SSL)的单向认证(客户端携带证书, 服务端信任检查).
  结合桩的定位, 我们采用One Connection Per Thread的线程池模式去模拟处理它, 一方面能基本满足需求, 另一方面实现高效简单. 同时为了方便测试, 服务端关闭对客户端认证机制.

服务实现:
  APNS模拟桩的实现过程
  1). 服务端证书引入

keytool -genkey -v -alias ssl-server -keyalg RSA -keystore ./server_ks -storepass server -keypass 123456 -dname "CN=Unknown"

  生成效果如图

  2). 自定义TrustManager的实现类

private class MyX509TrustManager implements X509TrustManager {

  @Override
  public X509Certificate[] getAcceptedIssuers() {
    return null;
  }
  // 对服务端证书的认证, 抛出异常表示不通过
  @Override
  public void checkServerTrusted(X509Certificate[] chain, String authType)
      throws CertificateException {
  }
  // 对客户端证书进行认证, 抛出异常表示不通过
  @Override
  public void checkClientTrusted(X509Certificate[] chain, String authType)
      throws CertificateException {
  } }; 

  评注: 这边提供空实现, 对客户端的证书不进行任何校验
  3). 创建SSLServerSocket代码片段

SSLContext ctx = SSLContext.getInstance("SSL");

// *) 创建KeyManagerFactory类实例
KeyManagerFactory kmf = KeyManagerFactory
    .getInstance(KeyManagerFactory.getDefaultAlgorithm()); // *) 初始化KeyStore, 这边的KEY_PASSWORD为"123456"
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(ApnsStubServer.class.getResourceAsStream("/server_ks"), null);
kmf.init(ks, KEY_PASSWOR.toCharArray()); // *) 进行SSLContext类的初始化工作
ctx.init(kmf.getKeyManagers(),
    new TrustManager[] { new MyX509TrustManager() },
    new SecureRandom()); // *) SSLServerSocket的生成
SSLServerSocket serverSocket = (SSLServerSocket) ctx
    .getServerSocketFactory()
    .createServerSocket(serverPort);
serverSocket.setReuseAddress(true);
// *) 设置Client认证开关关闭
serverSocket.setNeedClientAuth(false);

  评注: 把server_ks代码放到classpath目录下, 同时这边的key_password为"123456", 而不是keystore的密码"server", 注意keypass和storepass的区别
  4). 服务端连接处理代码

// One Connection Per Thread的机制
ExecutorService workerPools = Executors.newCachedThreadPool(); while ( true ) {
  final Socket cliSocket = sslServerSocket.accept();
  workerPools.execute(new Runnable() {
    @Override
    public void run() {
      onHandle(cliSocket);
    }
  });
}

  评注: serverSocket用于接受客户端连接, 并放入工作池去处理
  onHandle函数定义如下:

private void onHandle(Socket cliSocket) {

  DeviceTokenManager dtm = apnsStubController.getDeviceTokenManager();
  DataInputStream dis = new DataInputStream(cliSocket.getInputStream());
  int ch = 0;
  while ( (ch = dis.read()) != -1 ) {
    IVResult<ApnsMessage> result = ApnsProtocolUtility.readMessage(ch, dis);
    ApnsMessage message = result.getValue();
    // 属于不同版本的消息, 进行分发和处理
    if ( ApnsVersion.APNS_BINARY_PROTOCOL_V1 == message.getVersion() ) {
      // *) 如果属于bad device token黑名单
      if ( dtm.judgeBadDeviceToken(message.getDeviceToken()) ) {
        break;
      }
      printMessage(message);
    } else if ( ApnsVersion.APNS_BINARY_PROTOCOL_V2 == message.getVersion() ) {
      // *) 如果属于bad device token黑名单
      if ( dtm.judgeBadDeviceToken(message.getDeviceToken()) ) {
        // 8 为令牌错误, 就是bad device token
        ApnsProtocolUtility. writeResponse(8, message.getIdentifier(), cliSocket.getOutputStream());
        break;
      }
      printMessage(message);
    }
  }
}

  5). 黑名单的引入和接口定义

  由外置黑名单文件来指定

public class DeviceTokenManager {

  // 用于配置参数的初始化
  public void init(Properties prop);   // 判断是否为bad device token
  public boolean judgeBadDeviceToken(String deviceToken);   // 载入bad device token 名单
  private void loadBadDeviceTokenFile(String filename); }

  总结: 难点在于JSSE和证书的理解, 至于APNS的网络协议, 处理还是相对简单的.

APNS推送服务平台:
  对于APNS推送服务平台, 还是有些优化技巧, 这边并不深入展开, 仅从个人的观点描述几个.
  1). 对gateway.push.apple.com的ip地址解析, 挑选rrt时间最短的服务ip进行连接
  
  2). 引入队列异步化/构建DadDeviceToken仓库
  作为推送服务, 对于推送请求, 作简单的校验(消息格式合法/是否属于bad device token). 然后储存消息于队列中, 由后端worker慢慢消化. 不再等待真实的APNS服务反馈(APNS服务弱ACK机制), 立即返回结果(允许一定程度的推送成功误报率).
  构建自己的Bad Device Token仓库, 一方来自Feedback服务接受, 一方来自APNS推送的响应结果收集.

后记:
  感觉自己还是没有把自己想表达的事情说清楚, 甚是遗憾. 无论如何, 还是希望对读者有些帮助, 对我而言, 则是充满了回忆. Fighting, Let it go!!!

移动互联网实战--Apple的APNS桩推送服务的实现(2)的更多相关文章

  1. 移动互联网实战--Apple的APNS桩推送服务的实现(1)

    前记: 相信大家在搞IOS推送服务的开发时, 会直接使用javapns api来简单实现, 调试也直连Apple的APNS服务(产品/测试版)来实现. 很少有人会写个APNS的桩服务, 事实也是如此. ...

  2. APNS/苹果推送服务

    Apple Push Notification Service Google Cloud Message/Google 云 消息 Firebase Cloud Messaging

  3. 2014年国内经常使用移动client推送服务介绍和比較

    经过5年移动互联网的迅速发展,如今推送服务方面国内已经出现了非常多产品,比如极光推送,个推,一推,百度推送,友盟推送等,我们在选择推送服务时,首先排除了付费的推送服务,重点调查了免费的推送服务.   ...

  4. 基于APNs最新HTTP/2接口实现iOS的高性能消息推送(服务端篇)

    1.前言 本文要分享的消息推送指的是当iOS端APP被关闭或者处于后台时,还能收到消息/信息/指令的能力. 这种在APP处于后台或关闭情况下的消息推送能力,通常在以下场景下非常有用: 1)IM即时通讯 ...

  5. 基于Codeigniter框架实现的APNS批量推送—叮咚,查水表

    最近兼职公司已经众筹成功的无线门铃的消息推送出现了问题,导致有些用户接收不到推送的消息,真是吓死宝宝了,毕竟自己一手包办的后台服务,影响公司信誉是多么的尴尬,容我简单介绍一下我们的需求:公司开发的是一 ...

  6. IOS本地,APNS远程推送(具体过程)

    添加本地推送 ///本地添加 -(void)addLocalPushNotification:(UIButton*)sender; { NSLog(@"%s",__FUNCTION ...

  7. IOS 基于APNS消息推送原理与实现(JAVA后台)

    Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple Pu ...

  8. oc学习之路----APNS消息推送从证书到代码(2015年4月26号亲试可用)

    前言:看这篇博客之前要准备:首先的有一个99刀的个人开发者账号或者199刀的企业开发者账号,其次你用的是apns消息推送,(本人之前四处打听有没有其他消息推送的方法:收获如下:首先如果想做到apns的 ...

  9. 基于C++ 苹果apns消息推送实现(2)

    1.该模块的用途C++ 和 Openssl 代码 它实现了一个简单的apns顾客 2.配套文件:基于boost 的苹果apns消息推送实现(1) 3.最初使用的sslv23/sslv2/sslv3仅仅 ...

随机推荐

  1. QPS

    你想建设一个能承受500万PV/每天的网站吗? 博客分类: 移动行业 PV  转自:http://elf8848.iteye.com/blog/967049 你想建设一个能承受500万PV/每天的网站 ...

  2. K2十年:专注BPM

    <聚·谋·变——K2中国用户大会> 导演:K2中国 主演:K2用户 时长:420分钟 票价:免费 上映日期:2015年7月17日 查看完整视频请关注K2官方微信账号

  3. PowerShell并发控制-命令行参数之四问

    传教士问: win下如何 获取进程命令行,及命令行参数? 传教士答: 可以用这个powershell命令(实际上是wmi查询): (get-wmiobject -query "select ...

  4. [转]change the linux startup logo

    1. Make sure that you have the kernel sources installed. As annoying as this may seem, you will need ...

  5. Maven 建立web项目 The import javax.servlet cannot be resolved

    右击项目,选择Java Build Path->Libraries->Add External JARs,tomcat的路径lib文件夹下选中"servlet-api.jar&q ...

  6. sql 解析字符串添加到临时表中 sql存储过程in 参数输入

    sql 解析字符串添加到临时表中  sql存储过程in 参数输入 解决方法 把字符串解析 添加到 临时表中 SELECT * into #临时表   FROM dbo.Func_SplitOneCol ...

  7. ajax 中的一些方法应用

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. C#基础--面向过程计算器

    //面向过程计算器 //思路: 需要注意的是: 两个数相除 除数不能为0: //1.提示用户输入 //2.进行运算 //3.得到结果 Console.WriteLine("请输入第一个数字: ...

  9. 嵌入dll到rtf文件

    嵌入文件到doc文件中, 打开word--->插入菜单---->对象--->新建---->对象类型----->Package---->弹出创建软件包界面-----& ...

  10. redis简介以及与memcached比较

    一.redis (1)简介: Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API.是noSql数据库的一种. re ...