移动互联网实战--Apple的APNS桩推送服务的实现(2)
前记:
  相信大家在搞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)的更多相关文章
- 移动互联网实战--Apple的APNS桩推送服务的实现(1)
		
前记: 相信大家在搞IOS推送服务的开发时, 会直接使用javapns api来简单实现, 调试也直连Apple的APNS服务(产品/测试版)来实现. 很少有人会写个APNS的桩服务, 事实也是如此. ...
 - APNS/苹果推送服务
		
Apple Push Notification Service Google Cloud Message/Google 云 消息 Firebase Cloud Messaging
 - 2014年国内经常使用移动client推送服务介绍和比較
		
经过5年移动互联网的迅速发展,如今推送服务方面国内已经出现了非常多产品,比如极光推送,个推,一推,百度推送,友盟推送等,我们在选择推送服务时,首先排除了付费的推送服务,重点调查了免费的推送服务. ...
 - 基于APNs最新HTTP/2接口实现iOS的高性能消息推送(服务端篇)
		
1.前言 本文要分享的消息推送指的是当iOS端APP被关闭或者处于后台时,还能收到消息/信息/指令的能力. 这种在APP处于后台或关闭情况下的消息推送能力,通常在以下场景下非常有用: 1)IM即时通讯 ...
 - 基于Codeigniter框架实现的APNS批量推送—叮咚,查水表
		
最近兼职公司已经众筹成功的无线门铃的消息推送出现了问题,导致有些用户接收不到推送的消息,真是吓死宝宝了,毕竟自己一手包办的后台服务,影响公司信誉是多么的尴尬,容我简单介绍一下我们的需求:公司开发的是一 ...
 - IOS本地,APNS远程推送(具体过程)
		
添加本地推送 ///本地添加 -(void)addLocalPushNotification:(UIButton*)sender; { NSLog(@"%s",__FUNCTION ...
 - IOS 基于APNS消息推送原理与实现(JAVA后台)
		
Push的原理: Push 的工作机制可以简单的概括为下图 图中,Provider是指某个iPhone软件的Push服务器,这篇文章我将使用.net作为Provider. APNS 是Apple Pu ...
 - oc学习之路----APNS消息推送从证书到代码(2015年4月26号亲试可用)
		
前言:看这篇博客之前要准备:首先的有一个99刀的个人开发者账号或者199刀的企业开发者账号,其次你用的是apns消息推送,(本人之前四处打听有没有其他消息推送的方法:收获如下:首先如果想做到apns的 ...
 - 基于C++ 苹果apns消息推送实现(2)
		
1.该模块的用途C++ 和 Openssl 代码 它实现了一个简单的apns顾客 2.配套文件:基于boost 的苹果apns消息推送实现(1) 3.最初使用的sslv23/sslv2/sslv3仅仅 ...
 
随机推荐
- js基础之arguments、css
			
arguments就是一个包含传入的参数的数组对象 栗子一: function sum(){ var result=0; for(var i=0;i<arguments.length;i++){ ...
 - 用C#操作vss、msbuild、reactor
			
一.命令行 凡是支持命令行的工具,都可以通过cmd.exe操作.如下: var p = new Process(); p.StartInfo.FileName = "cmd.exe" ...
 - 【codevs1036】商务旅行 LCA 倍增
			
1036 商务旅行 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 某首都城市的商人要经常到各城镇去做生意,他们按自己的 ...
 - C++-不要在构造和析构函数中调用虚函数
			
在实习的单位搞CxImage库时不知为什么在Debug时没有问题,但是Release版里竟然跳出个Pure virtual function call error! 啥东西呀,竟然遇上了,就探个究竟吧 ...
 - C# WinForm程序向datagridview里添加数据
			
在C#开发的winform程序中,datagridview是一个经常使用到的控件.它可以以类似excel表格的形式规范的展示或操作数据,我也经常使用这个控件.使用这个控件首先要掌握的就是如何向其中插入 ...
 - 记录一些容易忘记的属性 -- UIGestureRecognize手势
			
//一个手势只能添加到一个view上面 //设置当前手势需要的点击次数 _tapRec.numberOfTapsRequired = 1;//(默认为1) //设置当前需要几个手指同时点击 ...
 - Oracle Enterprise Metadata Management (简称OEMM,Oracle元数据管理)12.1.3.0.1已经发布
			
在数据处理及数据仓库建设中,元数据管理是必不可少的,OEMM可以解决元数据管理过程中各种关键业务问题和技术挑战,其中包括如何元数据的统计信息,了解变更数据之后对下游的影响范围,而且OEMM站在业务的角 ...
 - Apache目录结构(一)
			
一.Apache 目录结构 bin: 该目录用于存放apache常用的命令,比如httpd cig-bin:该目录存放linux下的常用命令 .sh conf:存放配置文件httpd.conf,在ht ...
 - 单例模式简单解析--Singleton 单例模式(懒汉方式和饿汉方式)
			
单例模式的概念: 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 关键点: 1)一个类只有一个实例 这是最基本 ...
 - GridView导出Excel
			
public void OUTEXCEL() { DataSet ds = new GW_T_DemandDAL().GetWzH(GetPersonInfoData(UserInfo), Reque ...