APNs的学习和使用

    
APNs(英文全称:Apple Push Notification service),中文翻译为:苹果推送通知服务。 该技术由 苹果公司提供的APNs服务。
苹果推送通知服务的传输和路由的通知从一个给定的供应商给定的设备。通知是由两个主要部分组成的数据:设备令牌和有效载荷的短消息。设备令牌是类似于一个电话号码,它包含的信息,使的APN定位的设备上安装客户端应用程序。APN还用它来验证通知的 路由有效载荷是一个JSON定义的属性列表中指定的设备上的应用程序的用户将被提醒。

    APNs在java的使用,这篇文章非常有效

APNs入门学习和使用

https://www.jianshu.com/p/bcdf0c609f7d

作者:Coselding

基本对着这个就能写出基本的APNs实例,本文主要讨论代码编写过程中出现的问题

    1.java.lang.NoClassDefFoundError: sun/security/ssl/EllipticCurvesExtension

假如你已经加了java虚拟机参数


里面的alpn-boot-8.1.12.v20180117.jar是用来替换虚拟机中的jar包的,这时候出了异常,异常信息可能如下:

[nioEventLoopGroup-2-1] WARN io.netty.handler.ssl.ApplicationProtocolNegotiationHandler - [id: 0x548cffb1, L://*你的ip*/:11364 - R:api.development.push.apple.com/17.188.166.29:443] Failed to select the application-level protocol:
java.lang.NoClassDefFoundError: sun/security/ssl/EllipticCurvesExtension
	at sun.security.ssl.ClientHandshaker.getKickstartMessage(ClientHandshaker.java:1526)
	at sun.security.ssl.Handshaker.kickstart(Handshaker.java:1061)
	at sun.security.ssl.SSLEngineImpl.kickstartHandshake(SSLEngineImpl.java:734)
	at sun.security.ssl.SSLEngineImpl.beginHandshake(SSLEngineImpl.java:756)
	at io.netty.handler.ssl.JdkSslEngine.beginHandshake(JdkSslEngine.java:147)
	at io.netty.handler.ssl.SslHandler.handshake(SslHandler.java:1360)
	at io.netty.handler.ssl.SslHandler.channelActive(SslHandler.java:1399)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:202)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1322)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
	at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:902)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:311)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:341)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:627)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:551)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:465)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:437)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
	at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
	at java.lang.Thread.run(Thread.java:748)
[nioEventLoopGroup-2-1] INFO com.relayrides.pushy.apns.ApnsClient - Failed to connect.
java.lang.IllegalStateException: Channel closed before HTTP/2 preface completed.
	at com.relayrides.pushy.apns.ApnsClient$2.operationComplete(ApnsClient.java:412)
	at com.relayrides.pushy.apns.ApnsClient$2.operationComplete(ApnsClient.java:404)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:514)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:488)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:427)
	at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:111)
	at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:82)
	at io.netty.channel.AbstractChannel$CloseFuture.setClosed(AbstractChannel.java:1058)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.doClose0(AbstractChannel.java:686)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:664)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:607)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.close(DefaultChannelPipeline.java:1276)
	at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
	at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
	at io.netty.channel.ChannelOutboundHandlerAdapter.close(ChannelOutboundHandlerAdapter.java:71)
	at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
	at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
	at io.netty.handler.ssl.SslHandler$8.operationComplete(SslHandler.java:1437)
	at io.netty.handler.ssl.SslHandler$8.operationComplete(SslHandler.java:1428)
	at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:514)
	at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:488)
	at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:427)
	at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:170)
	at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:93)
	at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:28)
	at io.netty.handler.ssl.SslHandler.safeClose(SslHandler.java:1428)
	at io.netty.handler.ssl.SslHandler.closeOutboundAndChannel(SslHandler.java:1259)
	at io.netty.handler.ssl.SslHandler.close(SslHandler.java:449)
	at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
	at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
	at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:475)
	at io.netty.handler.ssl.ApplicationProtocolNegotiationHandler.exceptionCaught(ApplicationProtocolNegotiationHandler.java:122)
	at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:295)
	at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:274)
	at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:266)
	at io.netty.handler.ssl.SslHandler.exceptionCaught(SslHandler.java:743)
	at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:295)
	at io.netty.channel.AbstractChannelHandlerContext.notifyHandlerException(AbstractChannelHandlerContext.java:862)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:225)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:202)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1322)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
	at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:902)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:311)
	at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:341)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:627)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:551)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:465)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:437)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
	at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
	at java.lang.Thread.run(Thread.java:748)
[nioEventLoopGroup-2-1] WARN io.netty.handler.ssl.ApplicationProtocolNegotiationHandler - [id: 0x548cffb1, L:/192.168.2.71:11364 ! R:api.development.push.apple.com/17.188.166.29:443] TLS handshake failed:
java.nio.channels.ClosedChannelException
	at io.netty.handler.ssl.SslHandler.channelInactive(...)(Unknown Source)

每个alpn-boot包都对应一个单独的jdk版本,并且严格对照版本,因此需要找到对应jdk版本的包,下面是包与jdk的对照表:


具体参照https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html

看网上说用

jetty-alpn-agent-2.0.6.jar  
可以自动选择与jdk版本对应的包

2.slf4报错

pushy依赖于slf4j-api的包,但是并没有slf4j的具体实现,因此会出现如下错误:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

这时只需要添加slf4j-simple的依赖就可以了

<dependency>
	<groupId>org.slf4j</groupId>
	<artifactId>slf4j-simple</artifactId>
	<version>1.7.25</version>
</dependency>

3.消息发送阻塞

	final Future<PushNotificationResponse<SimpleApnsPushNotification>> sendNotificationFuture = apnsClient.sendNotification(pushNotification);
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future.get();

如果有多条消息发送,在下一条消息发送之前,只能等上一条消息发送之后得到响应才能发送下一条消息,这无疑是相当耗费时间的,如果想并行的发送消息,就是其他文章说的异步回调方式,可以用:

final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future.getNow();
这样的话不用等消息发送后得到响应才发送下一条消息了


贴一下项目代码

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLException;

import com.relayrides.pushy.apns.ApnsClient;
import com.relayrides.pushy.apns.ApnsClientBuilder;
import com.relayrides.pushy.apns.PushNotificationResponse;
import com.relayrides.pushy.apns.util.ApnsPayloadBuilder;
import com.relayrides.pushy.apns.util.SimpleApnsPushNotification;
import com.relayrides.pushy.apns.util.TokenUtil;

import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public class MyApnsClient {
	static ApnsClient apnsClient;

	/*
	 * static URL url = MyApnsClient.class.getClassLoader().getResource("dev.p12");
	 * static File file = new File(url.getFile());
	 */
	public static void main(String[] args) {
		try {
			//并行线程数
			EventLoopGroup loopGroup = new NioEventLoopGroup(4);
			//建立apns的客户端,可添加自己写的监听类,实现ApnsClientMetricsListener接口
			apnsClient = new ApnsClientBuilder().setEventLoopGroup(loopGroup)
					.setMetricsListener(new MyApnsClientListener())
					.setClientCredentials(new File("G:\\path\\test.p12"), "123456").build();
			//设置apns服务器host
			final Future<Void> connectFuture = apnsClient.connect(ApnsClient.DEVELOPMENT_APNS_HOST);
			// 设置等待时间,可抛出中断异常
			connectFuture.await();
			//connectFuture.await(10, TimeUnit.MINUTES);
			List<SimpleApnsPushNotification> pushNotifications=new ArrayList<SimpleApnsPushNotification>();
			for(int i=0;i<10;i++) {
				final SimpleApnsPushNotification pushNotification;
				{
					//消息的载体
					final ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
					payloadBuilder.setAlertBody("hello world");
					payloadBuilder.setSoundFileName("system_sing.mp3");
					payloadBuilder.setBadgeNumber(1);
					payloadBuilder.setContentAvailable(true);
					final String payload = payloadBuilder.buildWithDefaultMaximumLength();
					//设备的唯一标识号devicetoken
					final String token = TokenUtil
							.sanitizeTokenString("your device token");
					pushNotification = new SimpleApnsPushNotification(token, null, payload);
				}
				pushNotifications.add(pushNotification);
			}
			for(SimpleApnsPushNotification pushNotification:pushNotifications) {
				//发送信息得到回应的实例,是异步的,会立马创建一个实例,但可能是发送未成功的状态
				final Future<PushNotificationResponse<SimpleApnsPushNotification>> sendNotificationFuture = apnsClient
						.sendNotification(pushNotification);
				sendNotificationFuture.addListener(
						new GenericFutureListener<Future<PushNotificationResponse<SimpleApnsPushNotification>>>() {

							@Override
							public void operationComplete(
									Future<PushNotificationResponse<SimpleApnsPushNotification>> future) throws Exception {
								final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future
										.getNow();
								if (pushNotificationResponse.isAccepted()) {
									System.out.println("Push notification accepted by APNs gateway.");
								} else {
									System.out.println("Notification rejected by the APNs gateway: "
											+ pushNotificationResponse.getRejectionReason());

									if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) {
										System.out.println("\t…and the token is invalid as of "
												+ pushNotificationResponse.getTokenInvalidationTimestamp());
									}
								}
							}
						});
			}
			final Future<Void> disconnectFuture = apnsClient.disconnect();
			try {
				disconnectFuture.await();
				if (disconnectFuture.isSuccess()) {
					System.out.println("连接关闭");
				}
			} catch (InterruptedException e) {
				System.out.println("Failed to disconnect APNs , timeout");
				e.printStackTrace();
			}
		} catch (SSLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			System.out.println("Failed to connect APNs , timeout");
			e.printStackTrace();
		} /*
			 * catch (ExecutionException e) {
			 * System.err.println("Failed to send push notification."); if (e.getCause()
			 * instanceof ClientNotConnectedException) {
			 * System.out.println("Waiting for client to reconnect…"); try {
			 * apnsClient.getReconnectionFuture().await(); } catch (InterruptedException e1)
			 * { System.out.println("Failed to connect APNs , timeout");
			 * e1.printStackTrace(); } System.out.println("Reconnected."); }
			 * e.printStackTrace(); }
			 */
	}

}

Apple APNs java client, based on netty4. 基于netty4实现的苹果通知推送服务Java客户端。 特点: 支持第三版通知推送,即command = 2。目前的绝大部分Java客户端都只支持command = 1,即第二版。 支持SSL握手成功才返回,可以调用 pushManager.start().sync(); 等待握手成功才开始发送; 最大限度重试发送,内部自动处理重连,错误重发机制; 支持配置RejectListener,即通知被Apple服务器拒绝之后的回调接口; 支持配置ShutdownListener,即当shutdown时,没有发送完的消息处理的回调接口; 支持发送统计信息; 实现组件分离,可以利用PushClient,FeedbackClient来写一些灵活的代码。 Notification发送者可以自己定义设置发送的Queue,自己灵活处理阻塞,超时等问题。     Example: 更多的例子在src/test/java 目录下。 public class MainExample {     public static void main(String[] args) throws InterruptedException {         Environment environment = Environment.Product;         String password = "123456";         String keystore = "/home/hengyunabc/test/apptype/app_type_1/productAPNS.p12";         PushManager pushManager = new PushManagerImpl(keystore, password, environment);         //set a push queue         BlockingQueuequeue = new LinkedBlockingQueue(8192);         pushManager.setQueue(queue );         //waiting for SSL handshake success         pushManager.start().sync();         //build a notification         String token = "5f6aa01d8e3358949b7c25d461bb78ad740f4707462c7eafbebcf74fa5ddb387";         Notification notification = new NotificationBuilder()                 .setToken(token)                 .setBadge(1)                 .setPriority(5)                 .setAlertBody("xxxxx").build();         //put notification into the queue         queue.put(notification);         TimeUnit.SECONDS.sleep(10);         //get statistic info         Statistic statistic = pushManager.getStatistic();         System.out.println(statistic);     } } 标签:zpush
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值