apns_apns推送 - CSDN
  • IOS消息推送之APNS

    2015-12-09 16:32:06
    APNS:Apple Push

    一、背景概述:

    1,环境配置

    APNS:Apple Push Notification Service。本文对推送相关概念不再赘述,只侧重完整流程。 

    Demo 开发环境:Mac os 10.9.4  ,Xcode 6.0.1 ;测试设备:iphone 4s(ios 7.1)

    服务端开发环境:mac 10.9.4  + php 5.4.24、

    Demo 下载地址:点击打开链接

    2,APNS 相关博客

    如对apns相关概念不清楚,可参考以下几个博客:(博客中部分内容重复,但总体来说,通读一遍,还是大有裨益的)

     http://cshbbrain.iteye.com/blog/1859810  =》IOS 基于APNS消息推送原理与实现(JAVA后台)

    http://www.cnblogs.com/qq78292959/archive/2012/07/16/2593651.html   =》iOS消息推送机制的实现

    http://blog.csdn.net/xunyn/article/details/8243573  =》APNS编程----iOS真机测试消息推送

    http://blog.csdn.net/wswqiang/article/details/8208581  =》IOS APNS 处理

    http://eric-gao.iteye.com/blog/1567777  =》 IOS PEM 文件的生成

    http://www.36coder.com/study/996.html  =》PHP 实现APNS 推送

    http://blog.csdn.net/sxfcct/article/details/7939082  =》 APNS 相关总结(推荐)

    3,APNS 接口

    消息推送:

    开发接口:gateway.sandbox.push.apple.com:2195

    发布接口:gateway.push.apple.com:2195

    反馈服务:

    开发接口:feedback.sandbox.push.apple.com:2196

    发布接口:产品接口:feedback.push.apple.com:2196

    二、制作Push证书和Pem文件

    1,新建一个App ID

    新建流程不再赘述,这里只提醒两点:1》App ID Suffix 中,一定要选择Explicit App ID;2》App Services 中,记得勾选Push Notifications。这里以新建一个id为:com.eversoft.PushDemo 为例。

    2,配置push开发证书

    在App IDs中,选中刚才新建的App id:com.eversoft.PushDemo ,单击,展开详细信息属性。

    在详细信息属性中,单击下方的“Edit”按钮,
    在新打开的编辑界面,单击“Create Certificate”,

    在新打开的界面中,会提示我们,创建一个csr 证书签名请求文件。具体的创建步骤,界面中已经给出了详细的英文说明。

    在进行下一步之前,我们先按照英文说明,创建一个 CSR 文件。
    • 在mac电脑上,打开应用程序  keychain(钥匙串访问);
    • 在keychain菜单栏中,依次选择“钥匙串访问”=》“证书助理”=》“从证书颁发机构请求证书”;
    • 在新打开的“证书助理”界面中,填写用户电子邮件地址,常用名称,CA电子邮件地址,这两个邮件地址直接填写你的苹果账号的邮件地址即可,然后选择“存储到磁盘”,然后点击“继续”;
    • 选择CSR文件保存位置,“存储”即可。至此, CSR 文件,制作完成。

    回到刚才我们的web页面上,点击“Continue”,进入下一页面;新的页面中,会要求我们上传刚才制作的csr文件,选择“Choose File”,找到我们刚才存储的csr文件,单击“打开”,最后,点击页面上的“Generate”按钮,到此,开发使用的push证书制作完毕。
    证书生成成功后,选择“Download”,将制作好的证书下载到本地。然后双击下载的证书aps_development.cer,双击后,证书就自动导入到钥匙串中了。

    打开 keychain,左侧钥匙串选择“登录”,种类选择“所有项目”,在右侧窗口中,选中刚才导入的Apple Development IOS Push Services证书(不用选中专用密钥),右键,选择导出,命名为:ck.p12 ,存储时,会提示输入保护密码,这里为演示方便,就输入了123456。之后又会要求输入电脑登录密码,输入即可。

    3,生成PEM文件

    最后,打开终端,执行以下命令,生成pem文件

    openssl pkcs12 -in ck.p12 -out ck.pem -nodes 

    执行时,会要求输入导入密码,这里输入刚才的保护密码123456即可。


    到此,php 服务端使用的pem证书就制作完毕了。

    Development PP 文件制作不再赘述。

    三、IOS 代码编写

    首先,在AppDelegate.m 中:

    1,注册通知

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        ViewController *mainCtrl=[[ViewController alloc] init];
        self.window.rootViewController=mainCtrl;
        
        //注册通知
        if ([UIDevice currentDevice].systemVersion.doubleValue<8.0) {
            [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
        }
        else {
            [[UIApplication sharedApplication] registerForRemoteNotifications];
            [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]];
        }
        
        //判断是否由远程消息通知触发应用程序启动
        if (launchOptions) {
            //获取应用程序消息通知标记数(即小红圈中的数字)
            NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
            if (badge>0) {
                //如果应用程序消息通知标记数(即小红圈中的数字)大于0,清除标记。
                badge--;
                //清除标记。清除小红圈中数字,小红圈中数字为0,小红圈才会消除。
                [UIApplication sharedApplication].applicationIconBadgeNumber = badge;
                NSDictionary *pushInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
                
                //获取推送详情
                NSString *pushString = [NSString stringWithFormat:@"%@",[pushInfo  objectForKey:@"aps"]];
                UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"finish Loaunch" message:pushString delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
                [alert show];
            }
        }
        
        return YES;
    }

    2,注册通知后,获取device token

    - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        NSString *token = [NSString stringWithFormat:@"%@", deviceToken];
        NSLog(@"My token is:%@", token);
        //这里应将device token发送到服务器端
    }
    
    - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        NSString *error_str = [NSString stringWithFormat: @"%@", error];
        NSLog(@"Failed to get token, error:%@", error_str);
    }

    3,接收推送通知

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    {
        [UIApplication sharedApplication].applicationIconBadgeNumber=0;
        for (id key in userInfo) {
            NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);
        }
        /* eg.
        key: aps, value: {
            alert = "\U8fd9\U662f\U4e00\U6761\U6d4b\U8bd5\U4fe1\U606f";
            badge = 1;
            sound = default;
        }
         */
        UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"remote notification" message:userInfo[@"aps"][@"alert"] delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
        [alert show];
    }

    注意:app 前台运行时,会调用 remote notification;app后台运行时,点击提醒框,会调用remote notification,点击app 图标,不调用remote notification,没反应;app 没有运行时,点击提醒框,finishLaunching   中,launchOptions 传参,点击app 图标,launchOptions 不传参,不调用remote notification。

    四、服务器端代码编写

    此章不在IOS程序员职责范围之内,故只给出示例代码,不做深入讨论。

    1,php 源码:

    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <title>APNS</title>
    </head>
    <body>
    <?php
    /**
    * @file apns.php
    * @synopsis  apple APNS class
    * @author Yee, <rlk002@gmail.com>
    * @version 1.0
    * @date 2012-09-17 11:27:59
    */
        class APNS
        {
            const ENVIRONMENT_PRODUCTION = 0;
            const ENVIRONMENT_SANDBOX = 1;
            const DEVICE_BINARY_SIZE = 32;
            const CONNECT_RETRY_INTERVAL = 1000000;
            const SOCKET_SELECT_TIMEOUT = 1000000;
            const COMMAND_PUSH = 1;
            const STATUS_CODE_INTERNAL_ERROR = 999;
            const ERROR_RESPONSE_SIZE = 6;
            const ERROR_RESPONSE_COMMAND = 8;
            const PAYLOAD_MAXIMUM_SIZE = 256;
            const APPLE_RESERVED_NAMESPACE = 'aps';
            protected $_environment;
            protected $_providerCertificateFile;
            protected $_rootCertificationAuthorityFile;
            protected $_connectTimeout;
            protected $_connectRetryTimes = 3;
            protected $_connectRetryInterval;
            protected $_socketSelectTimeout;
            protected $_hSocket;
            protected $_deviceTokens = array();
            protected $_text;
            protected $_badge;
            protected $_sound;
            protected $_customProperties;
            protected $_expiryValue = 604800;
            protected $_customIdentifier;
            protected $_autoAdjustLongPayload = true;
            protected $asurls = array('ssl://gateway.push.apple.com:2195','ssl://gateway.sandbox.push.apple.com:2195');
            protected $_errorResponseMessages = array
                                (
                                    0   => 'No errors encountered',
                                    1 => 'Processing error',
                                    2 => 'Missing device token',
                                    3 => 'Missing topic',
                                    4 => 'Missing payload',
                                    5 => 'Invalid token size',
                                    6 => 'Invalid topic size',
                                    7 => 'Invalid payload size',
                                    8 => 'Invalid token',
                                    self::STATUS_CODE_INTERNAL_ERROR => 'Internal error'
                                );
            
            function __construct($environment,$providerCertificateFile)
            {
                if($environment != self::ENVIRONMENT_PRODUCTION && $environment != self::ENVIRONMENT_SANDBOX) 
                {
                    throw new Exception(
                        "Invalid environment '{$environment}'"
                    );
                }
                $this->_environment = $environment;
    
                if(!is_readable($providerCertificateFile)) 
                {
                    throw new Exception(
                        "Unable to read certificate file '{$providerCertificateFile}'"
                    );
                }
                $this->_providerCertificateFile = $providerCertificateFile;
    
                $this->_connectTimeout = @ini_get("default_socket_timeout");
                $this->_connectRetryInterval = self::CONNECT_RETRY_INTERVAL;
                $this->_socketSelectTimeout = self::SOCKET_SELECT_TIMEOUT;
            }
    
            public function setRCA($rootCertificationAuthorityFile)
            {
                if(!is_readable($rootCertificationAuthorityFile)) 
                {
                    throw new Exception(
                        "Unable to read Certificate Authority file '{$rootCertificationAuthorityFile}'"
                    );
                }
                $this->_rootCertificationAuthorityFile = $rootCertificationAuthorityFile;
            }
    
            public function getRCA()
            {
                return $this->_rootCertificationAuthorityFile;
            }
    
            protected function _connect()
            {
                $sURL = $this->asurls[$this->_environment];
                $streamContext = stream_context_create(
                    array
                        (
                            'ssl' => array
                            (
                                'verify_peer' => isset($this->_rootCertificationAuthorityFile),
                                'cafile' => $this->_rootCertificationAuthorityFile,
                                'local_cert' => $this->_providerCertificateFile
                            )
                        )
                    );
    
                $this->_hSocket = @stream_socket_client($sURL,$nError,$sError,$this->_connectTimeout,STREAM_CLIENT_CONNECT, $streamContext);
    
                if (!$this->_hSocket) 
                {
                    throw new Exception
                    (
                        "Unable to connect to '{$sURL}': {$sError} ({$nError})"
                    );
                }
                stream_set_blocking($this->_hSocket, 0);
                stream_set_write_buffer($this->_hSocket, 0);
                return true;
            }
    
            public function connect()
            {
                $bConnected = false;
                $retry = 0;
                while(!$bConnected) 
                {
                    try 
                    {
                        $bConnected = $this->_connect();
                    }catch (Exception $e) 
                    {
                        if ($nRetry >= $this->_connectRetryTimes) 
                        {
                            throw $e;
                        }else 
                        {
                            usleep($this->_nConnectRetryInterval);
                        }
                    }
                    $retry++;
                }
            }
    
            public function disconnect()
            {
                if (is_resource($this->_hSocket)) 
                {
                    return fclose($this->_hSocket);
                }
                return false;
            }
    
            protected function getBinaryNotification($deviceToken, $payload, $messageID = 0, $Expire = 604800)
            {
                $tokenLength = strlen($deviceToken);
                $payloadLength = strlen($payload);
    
                $ret  = pack('CNNnH*', self::COMMAND_PUSH, $messageID, $Expire > 0 ? time() + $Expire : 0, self::DEVICE_BINARY_SIZE, $deviceToken);
                $ret .= pack('n', $payloadLength);
                $ret .= $payload;
                return $ret;
            }
    
            protected function readErrorMessage()
            {
                $errorResponse = @fread($this->_hSocket, self::ERROR_RESPONSE_SIZE);
                if ($errorResponse === false || strlen($errorResponse) != self::ERROR_RESPONSE_SIZE) 
                {
                    return;
                }
                $errorResponse = $this->parseErrorMessage($errorResponse);
                if (!is_array($errorResponse) || empty($errorResponse)) 
                {
                    return;
                }
                if (!isset($errorResponse['command'], $errorResponse['statusCode'], $errorResponse['identifier'])) 
                {
                    return;
                }
                if ($errorResponse['command'] != self::ERROR_RESPONSE_COMMAND) 
                {
                    return;
                }
                $errorResponse['timeline'] = time();
                $errorResponse['statusMessage'] = 'None (unknown)';
                if (isset($this->_aErrorResponseMessages[$errorResponse['statusCode']])) 
                {
                    $errorResponse['statusMessage'] = $this->_errorResponseMessages[$errorResponse['statusCode']];
                }
                return $errorResponse;
            }
    
            protected function parseErrorMessage($errorMessage)
            {
                return unpack('Ccommand/CstatusCode/Nidentifier', $errorMessage);
            }
    
            public function send()
            {
                if (!$this->_hSocket) 
                {
                    throw new Exception
                    (
                        'Not connected to Push Notification Service'
                    );
                }
                $sendCount = $this->getDTNumber();
                $messagePayload = $this->getPayload();
                foreach($this->_deviceTokens AS $key => $value)
                {
                    $apnsMessage = $this->getBinaryNotification($value, $messagePayload, $messageID = 0, $Expire = 604800);
                    $nLen = strlen($apnsMessage);
                    $aErrorMessage = null;
                    if ($nLen !== ($nWritten = (int)@fwrite($this->_hSocket, $apnsMessage))) 
                    {
                        $aErrorMessage = array
                        (
                            'identifier' => $key,
                            'statusCode' => self::STATUS_CODE_INTERNAL_ERROR,
                            'statusMessage' => sprintf('%s (%d bytes written instead of %d bytes)',$this->_errorResponseMessages[self::STATUS_CODE_INTERNAL_ERROR], $nWritten, $nLen)
                        );
                    }
                }
            }
    
    
            public function addDT($deviceToken)
            {
                if (!preg_match('~^[a-f0-9]{64}$~i', $deviceToken)) 
                {
                    throw new Exception
                    (
                        "Invalid device token '{$deviceToken}'"
                    );
                }
                $this->_deviceTokens[] = $deviceToken;
            }       
            
            public function getDTNumber()
            {
                return count($this->_deviceTokens);
            }
    
            public function setText($text)
            {
                $this->_text = $text;
            }
    
            public function getText()
            {
                return $this->_text;
            }
    
            public function setBadge($badge)
            {
                if (!is_int($badge)) 
                {
                    throw new Exception
                    (
                        "Invalid badge number '{$badge}'"
                    );
                }
                $this->_badge = $badge;
            }
    
            public function getBadge()
            {
                return $this->_badge;
            }
    
            public function setSound($sound = 'default')
            {
                $this->_sound = $sound;
            }
    
            public function getSound()
            {
                return $this->_sound;
            }
    
            public function setCP($name, $value)
            {
                if ($name == self::APPLE_RESERVED_NAMESPACE) 
                {
                    throw new Exception
                    (
                        "Property name '" . self::APPLE_RESERVED_NAMESPACE . "' can not be used for custom property."
                    );
                }
                $this->_customProperties[trim($name)] = $value;
            }
    
            protected function _getPayload()
            {
                $aPayload[self::APPLE_RESERVED_NAMESPACE] = array();
    
                if (isset($this->_text)) 
                {
                    $aPayload[self::APPLE_RESERVED_NAMESPACE]['alert'] = (string)$this->_text;
                }
                if (isset($this->_badge) && $this->_badge > 0) 
                {
                    $aPayload[self::APPLE_RESERVED_NAMESPACE]['badge'] = (int)$this->_badge;
                }
                if (isset($this->_sound)) 
                {
                    $aPayload[self::APPLE_RESERVED_NAMESPACE]['sound'] = (string)$this->_sound;
                }
    
                if (is_array($this->_customProperties)) 
                {
                    foreach($this->_customProperties as $propertyName => $propertyValue) 
                    {
                        $aPayload[$propertyName] = $propertyValue;
                    }
                }
                return $aPayload;
            }
    
            public function setExpiry($expiryValue)
            {
                if (!is_int($expiryValue)) 
                {
                    throw new Exception
                    (
                        "Invalid seconds number '{$expiryValue}'"
                    );
                }
                $this->_expiryValue = $expiryValue;
            }
    
            public function getExpiry()
            {
                return $this->_expiryValue;
            }
    
            public function setCustomIdentifier($customIdentifier)
            {
                $this->_customIdentifier = $customIdentifier;
            }
    
            public function getCustomIdentifier()
            {
                return $this->_customIdentifier;
            }       
    
            public function getPayload()
            {
                $sJSONPayload = str_replace
                (
                    '"' . self::APPLE_RESERVED_NAMESPACE . '":[]',
                    '"' . self::APPLE_RESERVED_NAMESPACE . '":{}',
                    json_encode($this->_getPayload())
                );
                $nJSONPayloadLen = strlen($sJSONPayload);
    
                if ($nJSONPayloadLen > self::PAYLOAD_MAXIMUM_SIZE)
                {
                    if ($this->_autoAdjustLongPayload) 
                    {
                        $maxTextLen = $textLen = strlen($this->_text) - ($nJSONPayloadLen - self::PAYLOAD_MAXIMUM_SIZE);
                        if ($nMaxTextLen > 0)
                        {
                            while (strlen($this->_text = mb_substr($this->_text, 0, --$textLen, 'UTF-8')) > $maxTextLen);
                            return $this->getPayload();
                        }else
                        {
                            throw new Exception
                            (
                                "JSON Payload is too long: {$nJSONPayloadLen} bytes. Maximum size is " .
                                self::PAYLOAD_MAXIMUM_SIZE . " bytes. The message text can not be auto-adjusted."
                            );
                        }
                    }else
                    {
                        throw new Exception
                        (
                            "JSON Payload is too long: {$nJSONPayloadLen} bytes. Maximum size is " .
                            self::PAYLOAD_MAXIMUM_SIZE . " bytes"
                        );
                    }
                }
                return $sJSONPayload;
            }   
        }
    
    ?>
    <?php
    date_default_timezone_set('PRC');
    echo "we are young,test apns.  -".date('Y-m-d h:i:s',time());
    
    $rootpath = 'entrust_root_certification_authority.pem';  //ROOT证书地址
    $cp = 'ck.pem';  //provider证书地址
    $apns = new APNS(1,$cp);
    try
    {
        //$apns->setRCA($rootpath);  //设置ROOT证书
        $apns->connect(); //连接
        $apns->addDT('acc5150a4df26507a84f19ba145ca3c1be5842a6177511ce7c43d01badb1bd96');  //加入deviceToken
        $apns->setText('这是一条测试信息');  //发送内容
        $apns->setBadge(1);  //设置图标数
        $apns->setSound();  //设置声音
        $apns->setExpiry(3600);  //过期时间
        $apns->setCP('custom operation',array('type' => '1','url' => 'http://www.google.com.hk'));  //自定义操作
        $apns->send();  //发送
        echo ' sent ok';
    }catch(Exception $e)
    {
        echo $e;
    }
    ?>
    
    </body>
    </html>


    2,启动 Apache 

    mac 自带apache,可直接运行php。

    打开“终端(terminal)”,输入 sudo apachectl -v,可显示Apache的版本;

    输入 sudo apachectl start,这样Apache就启动了。

    编辑文件 /etc/apache2/httpd.conf  ,  把  LoadModule php5_module libexec/apache2/libphp5.so 前面的注释去掉;然后重启apache: sudo apachectl restart

    打开Safari浏览器地址栏输入 “http://localhost”,可以看到内容为“It works!”的页面。其位

    于“/Library/WebServer/Documents/”下,这就是Apache的默认根目录。

    3,如何调试

    将服务器端写好的apns.php 文件以及生成的 ck.pem 文件,直接拷贝到 /Library/WebServer/Documents/  下,在浏览器中,直接浏览: http://localhost/apns.php  。这样消息就发送到了苹果服务器。

    展开全文
  • Provider是指某个iPhone软件的Push服务器,APNS是Apple Push Notification Service的缩写,是苹果的服务器。   上图可以分为三个阶段: 第一阶段:应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。  ...


    iOS消息推送的工作机制可以简单的用下图来概括:



     

    Provider是指某个iPhone软件的Push服务器,APNS是Apple Push Notification Service的缩写,是苹果的服务器。

     

    上图可以分为三个阶段:

    第一阶段:应用程序把要发送的消息、目的iPhone的标识打包,发给APNS。 

    第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发送到iPhone。 

    第三阶段:iPhone把发来的消息传递给相应的应用程序,并且按照设定弹出Push通知。

     

    从上图我们可以看到:

    1、应用程序注册消息推送。

    2、iOS从APNS Server获取device token,应用程序接收device token。

    3、应用程序将device token发送给PUSH服务端程序。

    4、服务端程序向APNS服务发送消息。

    5、APNS服务将消息发送给iPhone应用程序。

     

    无论是iPhone客户端和APNS,还是Provider和APNS,都需要通过证书进行连接。

     

    下面我介绍一下几种用到的证书。

     

    一、CSR文件

     1、生成Certificate Signing Request(CSR)


     

    2、填写你的邮箱和常用名称,并选择保存到硬盘。



     

    点击继续:


     

    这样就在本地生成了一个Push.certSigningRequest文件。

     

    二、p12文件

     

    1、导出密钥。




     

    2、输入你的密码。

     

     

    这样就生成了一个Push.p12文件。

     

    三、SSL certificate文件

     

    1、用你付过费的帐号登录到iOS Provisioning Portal,并新建一个Explicit App ID,这个过程可以参考:我的另一篇博客

    iOS申请真机调试证书 图文详解

    ,这样就会生成下面这条记录:



     

    2、点击Edit:



     

    3、勾选Push Notification -> Development SSL Certificate ->Creat Certifica...


    4、点击Continue

    ,



    5、选择前面生成好的Push.certSigningRequest文件,点击Generate,出现如下所示的页面:


    6、点击Download,并将文件命名为aps_development.cer。

     

    7、点击左侧App IDs, 找到刚才新建的App ID, (testAppId) 你会发现状态变成了Enabled:



     

    注意:有的App ID的Apple Push Notification service列是灰色的,并且不允许使用Configure按钮,这是因为APNS不支持带通配符的App ID。

     

    到现在为止,我们已经生成了三个文件:

     

    1、Push.certSigningRequest

    2、Push.p12

    3、aps_development.cer

     

    在项目的AppDelegate中的didFinishLaunchingWithOptions方法中加入下面的代码:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        //注册APNS
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
        return YES;
    }

     

    通过registerForRemoteNotificationTypes方法,告诉应用程序,能接受push来的通知。

     

    在项目的AppDelegate中添加下面的方法来获取deviceToken:

    //APNS
    - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
        NSString *token = [NSString stringWithFormat:@"%@", deviceToken];
        NSLog(@"My token is:%@", token);
    }
    
    - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
        NSString *error_str = [NSString stringWithFormat: @"%@", error];
        NSLog(@"Failed to get token, error:%@", error_str);
    }
    
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    {
        
    }
    
    

    运行程序可以获取到device Token, 如图,


    获取到的deviceToken,我们可以提交给后台应用程序,


    后台程序实现有多种方式

    一. PushMeBaby

    我用的pushMeBaby,做的测试,https://github.com/stefanhafeneger/PushMeBaby

    1,将下载的aps_development.cer文件拷贝到PushMeBaby工程文件所在目录,

    2.打开工程,在工程文件上右击 add file to "PushMeBaby",

    3.run PushMeBaby  ,询问是否允许访问钥匙串,点允许,


    4,运行界面及日志如图所示表示连接成功,

    5, 将前面获取到的 device Token 粘贴到输入框中,点击push,日志如下,0表示成功,111表示发送的数据的长度,


    二. java, php

    发送通知的后台应用程序如果用php, java 实现,除了需要知道deviceToken之外,还需要一个与APNS连接的证书。

    这个证书可以通过我们前面生成的两个文件中得到。

     

    1、将aps_development.cer转换成aps_development.pem格式

     

    1. openssl x509 -in aps_development.cer -inform DER -out aps_development.pem -outform PEM  

     

    2、将p12格式的私钥转换成pem

     

    1. openssl pkcs12 -nocerts -out Push_Noenc.pem -in Push.p12  

     

    3、创建p12文件

     

    1. openssl pkcs12 -export -in aps_development.pem -inkey Push_Noenc.pem -certfile Push.certSigningRequest -name "aps_development" -out aps_development.p12  

     

    这样我们就得到了在.net或java等后台应用程序中使用的证书文件:aps_development.p12

     

    如果后台应用是php的话,那么可以按照 iOS消息推送机制中pem文件的生成这篇文章中的方法来生成php后台应用程序中使用的证书文件:ck.pem






    展开全文
  • 苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的...

    苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己应用服务器的需要被推送的信息,然后推送到指定的iOS设备上,然后由设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会返给我们一个device_token,拿到这个token后我们将这个token发给我们自己的应用服务器,当有需要被推送的消息时,我们的应用服务器会将消息按指定的格式打包,然后结合设备的device_token一并发给APNs服务器,由于我们的应用和APNs维持一个基于TCP的长连接,APNs将新消息推送到我们设备上,然后在屏幕上显示出新消息来。整个过程基本就这样,下面我们看一下设备注册APNs的流程图:

    上图完成了如下步骤:

    1、Device连接APNs服务器并携带设备序列号

    2、连接成功,APNs经过打包和处理产生device_token并返回给注册的Device

    3、Device携带获取的device_token向我们自己的应用服务器注册

     

    推送经过的流程参照下图:

    图中,Provider是指的是我们的应用服务器,APNS 是Apple Push Notification Service(苹果的推送服务器)。

    上图可以分为三个阶段。

    第一阶段:我们的应用服务器把要发送的消息、目的iPhone的标识打包,发给APNS。

    第二阶段:APNS在自身的已注册Push服务的iPhone列表中,查找有相应标识的iPhone,并把消息发到iPhone。

    第三阶段:iPhone把发来的消息传递给相应的应用程序, 并且按照设定弹出Push通知。

     

    从上图我们可以看到。

    1、首先是应用程序注册消息推送。

    2、 iOS跟APNS Server要deviceToken。应用程序接受deviceToken。

    3、应用程序将deviceToken发送给PUSH服务端程序。

    4、 服务端程序向APNS服务发送消息。

    5、APNS服务将消息发送给iPhone应用程序。

    展开全文
  • APNs工具App推荐,适合APNs开发使用 最近在调试APNs功能时,通过服务器下发消息太慢了,也很麻烦,之前都是自己从网上找APNs服务器代码,用过Java版、C#、Node.js、Python等等,过一段时间会出现项目运行不起来,...

    APNs工具App推荐,适合APNs开发使用

    最近在调试APNs功能时,通过服务器下发消息太慢了,也很麻烦,之前都是自己从网上找APNs服务器代码,用过Java版、C#、Node.js、Python等等,过一段时间会出现项目运行不起来,可能是环境被破坏了,或者就是项目被我清理删除了,每次想项目时都可能要从网上找代码。例如Java版,作为一个iOS开发,每次都要配置很长时间,还经常报错不知道怎么修复,就不能像iOS开发一样,下载一个Xcode就可以了么?

    吐槽也吐槽够了,下面我来介绍一下这块App:APNs工具,可以直接在AppStore上搜索到,下载到手机上就可以使用:

    APNs工具


    启动App,第一次启动,界面上证书都没有配置:


    这时,我们需要配置证书,这个App只支持p12推送证书,先导出p12证书,然后打开iTunes(Mac电脑好像直接就有,PC的话需要下载),打开 文件共享>APNs功能,将p12推送证书拖到页面中,这样就导入成功了,参考下图:

    导入证书方式


    进入App,点击“选择证书”区域,选择刚才导入的证书,添加密码、DeviceToken,选择环境和Voip推送开关,推送内容可以先不填,App默认就有,所有东西添加好如下图:

    这里写图片描述


    点击“发送APNs推送”,如果App是第一次安装,可能会因为没有网络权限,导致失败,在弹出来的网络权限弹框中点击“允许”,再次点击发送就可以了。

    正确的code是200,其他都是错误了,下面是苹果提供的一下错误码:

    HTTP status codes

    Status code Description
    200 Success.
    400 Bad request.
    403 There was an error with the certificate or with the provider’s authentication token.
    405 The request used an invalid :method value. Only POST requests are supported.
    410 The device token is no longer active for the topic.
    413 The notification payload was too large.
    429 The server received too many requests for the same device token.
    500 Internal server error.
    503 The server is shutting down and unavailable.



    Response error strings

    Status code Error string Description
    400 BadCollapseId The collapse identifier exceeds the maximum allowed size.
    400 BadDeviceToken The specified device token is invalid. Verify that the request contains a valid token and that the token matches the environment.
    400 BadExpirationDate The apns-expiration value is invalid.
    400 BadMessageId The apns-id value is invalid.
    400 BadPriority The apns-priority value is invalid.
    400 BadTopic The apns-topic is invalid.
    400 DeviceTokenNotForTopic The device token doesn’t match the specified topic.
    400 DuplicateHeaders One or more headers are repeated.
    400 IdleTimeout Idle time out.
    400 MissingDeviceToken The device token isn’t specified in the request :path. Verify that the :path header contains the device token.
    400 MissingTopic The apns-topic header of the request isn’t specified and is required. The apns-topic header is mandatory when the client is connected using a certificate that supports multiple topics.
    400 PayloadEmpty The message payload is empty.
    400 TopicDisallowed Pushing to this topic is not allowed.
    403 BadCertificate The certificate is invalid.
    403 BadCertificateEnvironment The client certificate is for the wrong environment.
    403 ExpiredProviderToken The provider token is stale and a new token should be generated.
    403 Forbidden The specified action is not allowed.
    403 InvalidProviderToken The provider token is not valid, or the token signature cannot be verified.
    403 MissingProviderToken No provider certificate was used to connect to APNs, and the authorization header is missing or no provider token is specified.
    404 BadPath The request contained an invalid :path value.
    405 MethodNotAllowed The specified :method isn’t POST.
    410 Unregistered The device token is inactive for the specified topic.
    413 PayloadTooLarge The message payload is too large. For information about the allowed payload size, see Create and Send a POST Request to APNs.
    429 TooManyProviderTokenUpdates The provider’s authentication token is being updated too often. Update the authentication token no more than once every 20 minutes.
    429 TooManyRequests Too many requests were made consecutively to the same device token.
    500 InternalServerError An internal server error occurred.
    503 ServiceUnavailable The service is unavailable.
    503 Shutdown The APNs server is shutting down.



    详细APNs资料可以苹果查看:
    Sending Notification Requests to APNs

    展开全文
  • APNS 那些事!

    2014-10-28 14:15:31
    之前在消息推送中间件APush里实现了对APNS的桥接,并利用业余时间阅读了官方指南Local and Push Notification Programming Guide,蛮有心得的。稍作总结,分享给大家,希望能够喜欢,欢迎留言讨论!

            之前在消息推送中间件APush里实现了对APNS的桥接,并利用业余时间阅读了官方指南Local and Push Notification Programming Guide,蛮有心得的。稍作总结,分享给大家,希望能够喜欢,欢迎留言讨论!

    1.  APNS 通道环境

          作为一个黑盒的消息推送服务,APNS为我们提供了开发和产品两套环境,这两套环境除了Host name不同外,授权证书也不近相同(证书里面会包含APP相关信息,如application bundle ID,在你创建不同的profile的时候,这些信息会自动添加进去),当然Device Token也不同。下面的英文也许能更好地描述这两套环境的不同:

    Development: Use the development environment for initial development and testing of the provider application. It provides the same set of services as the production environment, although with a smaller number of server units. The development environment also acts as a virtual device, enabling simulated end-to-end testing.You access the development environment atgateway.sandbox.push.apple.com , outbound TCP port 2195.


    Production: Use the production environment when building the production version of the provider application. Applications using the production environment must meet Apple’s reliability requirements.You access the production environment atgateway.push.apple.com , outbound TCP port 2195.

    2.  APNS 消息格式

         APNS 采用二进制消息协议,如下:



         看官方的解释,蛮清晰的:


         



         注意:

           如果APNS成功接收到你的消息,它将什么也不返回;

           如果你send a notification that is malformed or otherwise unintelligible, APNs returns an error-response packet and closes the connection. Any notifications that you sent after the malformed notification using the same connection are discarded, and must be resent. 这点要格外重要,所以很多开源工具包,比方说pushy提供了一个FailedConnectionListener,你可以通过实现该接口,方便的实现resent,代码片段如下:

    private class MyFailedConnectionListener implements FailedConnectionListener<SimpleApnsPushNotification> {
    
        public void handleFailedConnection(
                final PushManager<? extends SimpleApnsPushNotification> pushManager,
                final Throwable cause) {
    
            if (cause instanceof SSLHandshakeException) {
                // This is probably a permanent failure, and we should shut down
                // the PushManager.
            }
        }
    }
    
    // ...
    
    pushManager.registerFailedConnectionListener(new MyFailedConnectionListener());

             error-response packet 结构如下:


             Status code 解释如下:

         

          A status code of 10 indicates that the APNs server closed the connection (for example, to perform maintenance).The notification identifier in the error response indicates the last notification that was successfully sent. Any notifications you sent after it have been discarded and must be resent. When you receive this status code, stop using this connection and open a new connection.

    3.  APNS 消息接收

         An application must register with Apple Push Notification service for the operating systems on a device and on a computer to receive remote notifications sent by the application’s provider. Registration has three stages:

     3.1 The app registers for remote notifications.

     3.2 The system sets up remote notifications for the app and, if registration is successful, passes a device token to the app delegate.

     3.3 The app sends its device token to the push provider.

     3.4 Device tokens can change. Your app needs to reregister every time it is launched—in iOS by calling the registerForRemoteNotificationTypes: method of UIApplication, and in OS X by calling the registerForRemoteNotificationTypes: method of NSApplication.

     3.5 Because the only notification type supported for non-running applications is icon-badging,pass NSRemoteNotificationTypeBadge as the parameter of registerForRemoteNotificationTypes:.

     3.6 If registration is successful, APNs returns a device token to the device and iOS passes the token to the app delegate in the application:didRegisterForRemoteNotificationsWithDeviceToken: method. If there is a problem in obtaining the token, the operating system informs the delegate by calling the application:didFailToRegisterForRemoteNotificationsWithError: method.

          用一幅图来说明这个流程,如下:



    4.  APNS Qos

        Apple Push Notification service includes a default Quality of Service (QoS) component that performs a store-and-forward function.
        If APNs attempts to deliver a notification but the device is offline, the notification is stored for a limited period of time, and delivered to the device when it becomes available.
        Only one recent notification for a particular application is stored. If multiple notifications are sent while the device is offline, each new notification causes the prior notification to be discarded. This behavior of keeping only the newest notification is referred to as coalescing notifications.
        If the device remains offline for a long time, any notifications that were being stored for it are discarded.

        注意我字体标黑的这个APNS的特性!

    5.  APNS  FAQ

        a. APNS feedback 服务是用来做什么的?

          The Apple Push Notification Service includes a feedback service to give you information about failed push notifications. When a push notification cannot be delivered because the intended app does not exist on the device, the feedback service adds that device’s token to its list. Push notifications that expire before being delivered are not considered a failed delivery and don’t impact the feedback service. By using this information to stop sending push notifications that will fail to be delivered, you reduce unnecessary message overhead and improve overall system performance.
          Query the feedback service daily to get the list of device tokens. Use the timestamp to verify that the device tokens haven’t been reregistered since the feedback entry was generated. For each device that has not been reregistered, stop sending notifications. APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.    
          The feedback service maintains a separate list for each push topic. If you have multiple apps, you must connect to the feedback service once for each app, using the corresponding certificate, in order to receive all feedback.
          The feedback service has a binary interface similar to the interface used for sending push notifications. You access the production feedback service via feedback.push.apple.com on port 2196 and the development feedback service via feedback.sandbox.push.apple.com on port 2196. As with the binary interface for push notifications, use TLS (or SSL) to establish a secured communications channel. You use the same SSL certificate for connecting to the feedback service as you use for sending notifications. To establish a trusted provider identity, present this certificate to APNs at connection time using peer-to-peer authentication.

          Once you are connected, transmission begins immediately; you do not need to send any command to APNs.Read the stream from the feedback service until there is no more data to read.

          The feedback service’s list is cleared after you read it. Each time you connect to the feedback service, the information it returns lists only the failures that have happened since you last connected.

        b. Best Practices for Managing Connections

           You may establish multiple connections to the same gateway or to multiple gateway instances. If you need to send a large number of push notifications, spread them out over connections to several different gateways.This improves performance compared to using a single connection: it lets you send the push notifications faster, and it lets APNs deliver them faster.
           Keep your connections with APNs open across multiple notifications; don’t repeatedly open and close connections. APNs treats rapid connection and disconnection as a denial-of-service attack. You should leave a connection open unless you know it will be idle for an extended period of time—for example, if you only send notifications to your users once a day it is ok to use a new connection each day.

       c. 接收到APNS消息后,我一般怎么处理呢?

        If your app is frontmost, the application:didReceiveRemoteNotification: or application:didReceiveLocalNotification:method is called on its app delegate;
        If your app is not frontmost or not running, you handle the notifications by checking the options dictionary passed to the application:didFinishLaunchingWithOptions: of your app delegate for either the UIApplicationLaunchOptionsLocalNotificationKey or UIApplicationLaunchOptionsRemoteNotificationKey key.

        实例代码如下:

     (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
        // 取得 APNs 标准信息内容
        NSDictionary *aps = [userInfo valueForKey:@"aps"];
        NSString *content = [aps valueForKey:@"alert"]; //推送显示的内容
        NSInteger badge = [[aps valueForKey:@"badge"] integerValue]; //badge数量
        NSString *sound = [aps valueForKey:@"sound"]; //播放的声音
         
        // 取得自定义字段内容
        NSString *customizeField1 = [userInfo valueForKey:@"customizeField1"]; //自定义参数,key是自己定义的
        NSLog(@"content =[%@], badge=[%d], sound=[%@], customize field =[%@]",content,badge,sound,customizeField1);
         
        // Required
        [APService handleRemoteNotification:userInfo];
    }
    
    
    
    //iOS 7 Remote Notification
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
     
        NSLog(@"this is iOS7 Remote Notification");
         
        // Required
        [APService handleRemoteNotification:userInfo];
        completionHandler(UIBackgroundFetchResultNoData);
    }


    d.  消息发送过程中,突然出现Write failed: Broken pipe

       可能因为某种原因,如发送了错误的token,导致Apple强行关闭了SSL连接。跳过最后一次发送的信息,重新连接,继续发送错误信息之后的信息。

    e.  因为APNS发送成功没有任何返回,会不会出现是异常但是异常信息还没返回的现象

       有可能,这个时候可以采取批量发送,等候一段时间,比方说1秒,通过Feedback来检查发送状态。

    f.   一些你可能会忽视的细节:

          Each application on a device is limited to 64 scheduled local notifications. The system discards scheduled notifications in excess of this limit, keeping only the 64 notifications that will fire the soonest. Recurring notifications are treated as a single notification.

          Custom sounds must be under 30 seconds when played. If a custom sound is over that limit, the default system sound is played instead.

    参考文献:

         1. Local and Push Notification Programming Guide

         2. http://www.easyapns.com/apple-delegate

         3. http://blog.csdn.net/tlq1988/article/details/9612237

         4. http://www.cocoachina.com/bbs/read.php?tid=98797


    展开全文
  • APNs的学习和使用

    2018-04-10 16:29:15
    APNs(英文全称:Apple Push Notification service),中文翻译为:苹果推送通知服务。 该技术由苹果公司提供的APNs服务。苹果推送通知服务的传输和路由的通知从一个给定的供应商给定的设备。通知是由两个主要部分...
  • APNS工作原理

    2015-03-02 16:23:15
    首先,APNS会对用户进行物理连接认证,和设备令牌认证(简言之就是苹果的服务器检查设备里的证书以确定其为苹果设备); 然后,将服务器的信息接收并且保存在APNS当中,APNS从其中注册的列表中查找该IOS设备(设备...
  • apns

    2014-04-02 11:22:12
    在应用没有被运行的时候,只能通过 Apple Push Notification Service (APNs) 把数据发送到终端用户。对于互联网应用,正确高效的使用 APNs 显然非常重要。 JPush 为 iOS、Android 平台提供了一个统一推送消息的...
  • 前言:APNs 协议在近两年的 WWDC 上改过两次,2015年12月17日更是推出了革命性的新特性。但在国内传播的博客、面试题里关于APNs的答案全都是旧的、错的。正文:对 APNs 的吐槽APNs 是 Apple Push Notification ...
  • APNS苹果远程推送

    2019-01-02 14:15:20
    什么是APNS? 苹果推送通知服务(APNs)是推送通知的网关,iPhone ipad 对于应用程序在后台运行有诸多限制,考虑到手机电池电量,应用不允许在后台进行过多的操作。因此,当用户切换到其他程序后,原先的程序无法保持...
  • iOS远程推送--APNs详解

    2019-07-20 11:45:17
    iOS远程推送–APNs详解 iOS远程推送,远远不是配置两个证书,集成个SDK那么简单。 本文会从实践出发,结合苹果的官方文档,带你全面的了解苹果APNs服务。除了基础原理和集成方法外,还将详细介绍了APNs服务接口的...
  • 工作中需要用到APNs服务,所以打算将APNs的官方文档翻译出来,供有需求的朋友一起参考,水平有限,难免出错,还望不吝赐教。总览本地通知和远程通知本地通知和远程通知就是所谓的两种不同类型的通知,他们的区别在于...
  • apns.jar包用于apns推送

    2020-07-30 23:31:06
    apns.jar包,用于apns推送,直接导入就可以使用了
  • iOS推送流程(APNS

    2017-12-04 14:33:15
    iOS推送流程(APNS)一、APNS(Apple Push Notification Service)苹果推送通知服务(APNs)是推送通知的网关,iPhone ipad 对于应用程序在后台运行有诸多限制,考虑到手机电池电量,应用不允许在后台进行过多的操作。...
  • APNs入门学习和使用

    2016-12-04 01:13:23
    本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/12/01/APNs入门学习和使用/APNs入门学习和使用这篇文章费了我好多心血啊,这都是在我测试了一堆失败的代码,看了大量的博客之后...
  • 最近公司需要用苹果APNs实现类似微信视频接电话的效果,查看了苹果官网有VOIP这个东西,于是进行了研究开发。 首先总结一下接入流程: 在开发者中心申请对应的证书(推送证书,VOIP证书) IOS 注册推送到APNs(直接...
  • apns-expiration:过期 A UNIX epoch date expressed in seconds (UTC). This header identifies the date when the notification is no longer valid and can >be discarded. If this value is nonzero, APNs ...
  • APNs全名是Apple Push Notification Service。用iPhone的应该都习惯了,每次安装完一个新应用启动后,几乎都会弹出个警告框,“XXX应用”想要给您发送推送通知。这个警告框的权限申请就是为了APNs推送,用户授权后,...
  • 在开发向苹果Apns推送消息服务功能,我们需要根据Apns接受的数据格式进行推送。下面接受我在进行apns推送时候总结的一点apns服务接受的Json数据格式 示例 1: 以下负载包含哦一个简单的 aps 字典。它使用字符串而...
  • 参考的帖子如下:iOS集成极光推送遇到的几个问题iOS 远程推送通知APNS远程推送证书的申请和制作——详细解析对于APNs证书 以上 是你将要创建的四个证书1 ==> 这里,我只先记录一下 development 开发测试的 证书申请 ...
1 2 3 4 5 ... 20
收藏数 10,495
精华内容 4,198
关键字:

apns