IOS消息推送之APNS

转自:http://blog.csdn.net/jiajiayouba/article/details/39926017

一、背景概述:

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

反馈服务:

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

发布接口:feedback.sandbox.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,左侧钥匙串选择“登录”,种类选择“所有项目”,在右侧窗口中,选中刚才导入的aps_development.cer证书和对应的专用密钥,并导出两项,命名为:ck.p12 ,存储时,会提示输入保护密码,这里为演示方便,就输入了123456。之后又会要求输入电脑登录密码,输入即可。

3,生成PEM文件

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

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

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


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

Development PP 文件制作不再赘述。

三、IOS 代码编写

首先,在AppDelegate.m 中:

1,注册通知

[objc]  view plain copy print ?
  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
  2.     // Override point for customization after application launch.  
  3.     ViewController *mainCtrl=[[ViewController alloc] init];  
  4.     self.window.rootViewController=mainCtrl;  
  5.       
  6.     //注册通知  
  7.     if ([UIDevice currentDevice].systemVersion.doubleValue<8.0) {  
  8.         [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];  
  9.     }  
  10.     else {  
  11.         [[UIApplication sharedApplication] registerForRemoteNotifications];  
  12.         [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert categories:nil]];  
  13.     }  
  14.       
  15.     //判断是否由远程消息通知触发应用程序启动  
  16.     if (launchOptions) {  
  17.         //获取应用程序消息通知标记数(即小红圈中的数字)  
  18.         NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;  
  19.         if (badge>0) {  
  20.             //如果应用程序消息通知标记数(即小红圈中的数字)大于0,清除标记。  
  21.             badge--;  
  22.             //清除标记。清除小红圈中数字,小红圈中数字为0,小红圈才会消除。  
  23.             [UIApplication sharedApplication].applicationIconBadgeNumber = badge;  
  24.             NSDictionary *pushInfo = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];  
  25.               
  26.             //获取推送详情  
  27.             NSString *pushString = [NSString stringWithFormat:@"%@",[pushInfo  objectForKey:@"aps"]];  
  28.             UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"finish Loaunch" message:pushString delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil];  
  29.             [alert show];  
  30.         }  
  31.     }  
  32.       
  33.     return YES;  
  34. }  

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

[objc]  view plain copy print ?
  1. - (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {  
  2.     NSString *token = [NSString stringWithFormat:@"%@", deviceToken];  
  3.     NSLog(@"My token is:%@", token);  
  4.     //这里应将device token发送到服务器端  
  5. }  
  6.   
  7. - (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {  
  8.     NSString *error_str = [NSString stringWithFormat@"%@", error];  
  9.     NSLog(@"Failed to get token, error:%@", error_str);  
  10. }  

3,接收推送通知

[objc]  view plain copy print ?
  1. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo  
  2. {  
  3.     [UIApplication sharedApplication].applicationIconBadgeNumber=0;  
  4.     for (id key in userInfo) {  
  5.         NSLog(@"key: %@, value: %@", key, [userInfo objectForKey:key]);  
  6.     }  
  7.     /* eg. 
  8.     key: aps, value: { 
  9.         alert = "\U8fd9\U662f\U4e00\U6761\U6d4b\U8bd5\U4fe1\U606f"; 
  10.         badge = 1; 
  11.         sound = default; 
  12.     } 
  13.      */  
  14.     UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"remote notification" message:userInfo[@"aps"][@"alert"] delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil nil];  
  15.     [alert show];  
  16. }  

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

四、服务器端代码编写

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

1,php 源码:

[php]  view plain copy print ?
  1.   
  2.   
  3.   
  4. "content-type" content="text/html;charset=utf-8">  
  5. APNS  
  6.   
  7.   
  8. /** 
  9. * @file apns.php 
  10. * @synopsis  apple APNS class 
  11. * @author Yee,  
  12. * @version 1.0 
  13. * @date 2012-09-17 11:27:59 
  14. */  
  15.     class APNS  
  16.     {  
  17.         const ENVIRONMENT_PRODUCTION = 0;  
  18.         const ENVIRONMENT_SANDBOX = 1;  
  19.         const DEVICE_BINARY_SIZE = 32;  
  20.         const CONNECT_RETRY_INTERVAL = 1000000;  
  21.         const SOCKET_SELECT_TIMEOUT = 1000000;  
  22.         const COMMAND_PUSH = 1;  
  23.         const STATUS_CODE_INTERNAL_ERROR = 999;  
  24.         const ERROR_RESPONSE_SIZE = 6;  
  25.         const ERROR_RESPONSE_COMMAND = 8;  
  26.         const PAYLOAD_MAXIMUM_SIZE = 256;  
  27.         const APPLE_RESERVED_NAMESPACE = 'aps';  
  28.         protected $_environment;  
  29.         protected $_providerCertificateFile;  
  30.         protected $_rootCertificationAuthorityFile;  
  31.         protected $_connectTimeout;  
  32.         protected $_connectRetryTimes = 3;  
  33.         protected $_connectRetryInterval;  
  34.         protected $_socketSelectTimeout;  
  35.         protected $_hSocket;  
  36.         protected $_deviceTokens = array();  
  37.         protected $_text;  
  38.         protected $_badge;  
  39.         protected $_sound;  
  40.         protected $_customProperties;  
  41.         protected $_expiryValue = 604800;  
  42.         protected $_customIdentifier;  
  43.         protected $_autoAdjustLongPayload = true;  
  44.         protected $asurls = array('ssl://gateway.push.apple.com:2195','ssl://gateway.sandbox.push.apple.com:2195');  
  45.         protected $_errorResponseMessages = array  
  46.                             (  
  47.                                 0   => 'No errors encountered',  
  48.                                 1 => 'Processing error',  
  49.                                 2 => 'Missing device token',  
  50.                                 3 => 'Missing topic',  
  51.                                 4 => 'Missing payload',  
  52.                                 5 => 'Invalid token size',  
  53.                                 6 => 'Invalid topic size',  
  54.                                 7 => 'Invalid payload size',  
  55.                                 8 => 'Invalid token',  
  56.                                 self::STATUS_CODE_INTERNAL_ERROR => 'Internal error'  
  57.                             );  
  58.           
  59.         function __construct($environment,$providerCertificateFile)  
  60.         {  
  61.             if($environment != self::ENVIRONMENT_PRODUCTION && $environment != self::ENVIRONMENT_SANDBOX)   
  62.             {  
  63.                 throw new Exception(  
  64.                     "Invalid environment '{$environment}'"  
  65.                 );  
  66.             }  
  67.             $this->_environment = $environment;  
  68.   
  69.             if(!is_readable($providerCertificateFile))   
  70.             {  
  71.                 throw new Exception(  
  72.                     "Unable to read certificate file '{$providerCertificateFile}'"  
  73.                 );  
  74.             }  
  75.             $this->_providerCertificateFile = $providerCertificateFile;  
  76.   
  77.             $this->_connectTimeout = @ini_get("default_socket_timeout");  
  78.             $this->_connectRetryInterval = self::CONNECT_RETRY_INTERVAL;  
  79.             $this->_socketSelectTimeout = self::SOCKET_SELECT_TIMEOUT;  
  80.         }  
  81.   
  82.         public function setRCA($rootCertificationAuthorityFile)  
  83.         {  
  84.             if(!is_readable($rootCertificationAuthorityFile))   
  85.             {  
  86.                 throw new Exception(  
  87.                     "Unable to read Certificate Authority file '{$rootCertificationAuthorityFile}'"  
  88.                 );  
  89.             }  
  90.             $this->_rootCertificationAuthorityFile = $rootCertificationAuthorityFile;  
  91.         }  
  92.   
  93.         public function getRCA()  
  94.         {  
  95.             return $this->_rootCertificationAuthorityFile;  
  96.         }  
  97.   
  98.         protected function _connect()  
  99.         {  
  100.             $sURL = $this->asurls[$this->_environment];  
  101.             $streamContext = stream_context_create(  
  102.                 array  
  103.                     (  
  104.                         'ssl' => array  
  105.                         (  
  106.                             'verify_peer' => isset($this->_rootCertificationAuthorityFile),  
  107.                             'cafile' => $this->_rootCertificationAuthorityFile,  
  108.                             'local_cert' => $this->_providerCertificateFile  
  109.                         )  
  110.                     )  
  111.                 );  
  112.   
  113.             $this->_hSocket = @stream_socket_client($sURL,$nError,$sError,$this->_connectTimeout,STREAM_CLIENT_CONNECT, $streamContext);  
  114.   
  115.             if (!$this->_hSocket)   
  116.             {  
  117.                 throw new Exception  
  118.                 (  
  119.                     "Unable to connect to '{$sURL}': {$sError} ({$nError})"  
  120.                 );  
  121.             }  
  122.             stream_set_blocking($this->_hSocket, 0);  
  123.             stream_set_write_buffer($this->_hSocket, 0);  
  124.             return true;  
  125.         }  
  126.   
  127.         public function connect()  
  128.         {  
  129.             $bConnected = false;  
  130.             $retry = 0;  
  131.             while(!$bConnected)   
  132.             {  
  133.                 try   
  134.                 {  
  135.                     $bConnected = $this->_connect();  
  136.                 }catch (Exception $e)   
  137.                 {  
  138.                     if ($nRetry >= $this->_connectRetryTimes)   
  139.                     {  
  140.                         throw $e;  
  141.                     }else   
  142.                     {  
  143.                         usleep($this->_nConnectRetryInterval);  
  144.                     }  
  145.                 }  
  146.                 $retry++;  
  147.             }  
  148.         }  
  149.   
  150.         public function disconnect()  
  151.         {  
  152.             if (is_resource($this->_hSocket))   
  153.             {  
  154.                 return fclose($this->_hSocket);  
  155.             }  
  156.             return false;  
  157.         }  
  158.   
  159.         protected function getBinaryNotification($deviceToken$payload$messageID = 0, $Expire = 604800)  
  160.         {  
  161.             $tokenLength = strlen($deviceToken);  
  162.             $payloadLength = strlen($payload);  
  163.   
  164.             $ret  = pack('CNNnH*', self::COMMAND_PUSH, $messageID$Expire > 0 ? time() + $Expire : 0, self::DEVICE_BINARY_SIZE, $deviceToken);  
  165.             $ret .= pack('n'$payloadLength);  
  166.             $ret .= $payload;  
  167.             return $ret;  
  168.         }  
  169.   
  170.         protected function readErrorMessage()  
  171.         {  
  172.             $errorResponse = @fread($this->_hSocket, self::ERROR_RESPONSE_SIZE);  
  173.             if ($errorResponse === false || strlen($errorResponse) != self::ERROR_RESPONSE_SIZE)   
  174.             {  
  175.                 return;  
  176.             }  
  177.             $errorResponse = $this->parseErrorMessage($errorResponse);  
  178.             if (!is_array($errorResponse) || empty($errorResponse))   
  179.             {  
  180.                 return;  
  181.             }  
  182.             if (!isset($errorResponse['command'], $errorResponse['statusCode'], $errorResponse['identifier']))   
  183.             {  
  184.                 return;  
  185.             }  
  186.             if ($errorResponse['command'] != self::ERROR_RESPONSE_COMMAND)   
  187.             {  
  188.                 return;  
  189.             }  
  190.             $errorResponse['timeline'] = time();  
  191.             $errorResponse['statusMessage'] = 'None (unknown)';  
  192.             if (isset($this->_aErrorResponseMessages[$errorResponse['statusCode']]))   
  193.             {  
  194.                 $errorResponse['statusMessage'] = $this->_errorResponseMessages[$errorResponse['statusCode']];  
  195.             }  
  196.             return $errorResponse;  
  197.         }  
  198.   
  199.         protected function parseErrorMessage($errorMessage)  
  200.         {  
  201.             return unpack('Ccommand/CstatusCode/Nidentifier'$errorMessage);  
  202.         }  
  203.   
  204.         public function send()  
  205.         {  
  206.             if (!$this->_hSocket)   
  207.             {  
  208.                 throw new Exception  
  209.                 (  
  210.                     'Not connected to Push Notification Service'  
  211.                 );  
  212.             }  
  213.             $sendCount = $this->getDTNumber();  
  214.             $messagePayload = $this->getPayload();  
  215.             foreach($this->_deviceTokens AS $key => $value)  
  216.             {  
  217.                 $apnsMessage = $this->getBinaryNotification($value$messagePayload$messageID = 0, $Expire = 604800);  
  218.                 $nLen = strlen($apnsMessage);  
  219.                 $aErrorMessage = null;  
  220.                 if ($nLen !== ($nWritten = (int)@fwrite($this->_hSocket, $apnsMessage)))   
  221.                 {  
  222.                     $aErrorMessage = array  
  223.                     (  
  224.                         'identifier' => $key,  
  225.                         'statusCode' => self::STATUS_CODE_INTERNAL_ERROR,  
  226.                         'statusMessage' => sprintf('%s (%d bytes written instead of %d bytes)',$this->_errorResponseMessages[self::STATUS_CODE_INTERNAL_ERROR], $nWritten$nLen)  
  227.                     );  
  228.                 }  
  229.             }  
  230.         }  
  231.   
  232.   
  233.         public function addDT($deviceToken)  
  234.         {  
  235.             if (!preg_match('~^[a-f0-9]{64}$~i'$deviceToken))   
  236.             {  
  237.                 throw new Exception  
  238.                 (  
  239.                     "Invalid device token '{$deviceToken}'"  
  240.                 );  
  241.             }  
  242.             $this->_deviceTokens[] = $deviceToken;  
  243.         }         
  244.           
  245.         public function getDTNumber()  
  246.         {  
  247.             return count($this->_deviceTokens);  
  248.         }  
  249.   
  250.         public function setText($text)  
  251.         {  
  252.             $this->_text = $text;  
  253.         }  
  254.   
  255.         public function getText()  
  256.         {  
  257.             return $this->_text;  
  258.         }  
  259.   
  260.         public function setBadge($badge)  
  261.         {  
  262.             if (!is_int($badge))   
  263.             {  
  264.                 throw new Exception  
  265.                 (  
  266.                     "Invalid badge number '{$badge}'"  
  267.                 );  
  268.             }  
  269.             $this->_badge = $badge;  
  270.         }  
  271.   
  272.         public function getBadge()  
  273.         {  
  274.             return $this->_badge;  
  275.         }  
  276.   
  277.         public function setSound($sound = 'default')  
  278.         {  
  279.             $this->_sound = $sound;  
  280.         }  
  281.   
  282.         public function getSound()  
  283.         {  
  284.             return $this->_sound;  
  285.         }  
  286.   
  287.         public function setCP($name$value)  
  288.         {  
  289.             if ($name == self::APPLE_RESERVED_NAMESPACE)   
  290.             {  
  291.                 throw new Exception  
  292.                 (  
  293.                     "Property name '" . self::APPLE_RESERVED_NAMESPACE . "' can not be used for custom property."  
  294.                 );  
  295.             }  
  296.             $this->_customProperties[trim($name)] = $value;  
  297.         }  
  298.   
  299.         protected function _getPayload()  
  300.         {  
  301.             $aPayload[self::APPLE_RESERVED_NAMESPACE] = array();  
  302.   
  303.             if (isset($this->_text))   
  304.             {  
  305.                 $aPayload[self::APPLE_RESERVED_NAMESPACE]['alert'] = (string)$this->_text;  
  306.             }  
  307.             if (isset($this->_badge) && $this->_badge > 0)   
  308.             {  
  309.                 $aPayload[self::APPLE_RESERVED_NAMESPACE]['badge'] = (int)$this->_badge;  
  310.             }  
  311.             if (isset($this->_sound))   
  312.             {  
  313.                 $aPayload[self::APPLE_RESERVED_NAMESPACE]['sound'] = (string)$this->_sound;  
  314.             }  
  315.   
  316.             if (is_array($this->_customProperties))   
  317.             {  
  318.                 foreach($this->_customProperties as $propertyName => $propertyValue)   
  319.                 {  
  320.                     $aPayload[$propertyName] = $propertyValue;  
  321.                 }  
  322.             }  
  323.             return $aPayload;  
  324.         }  
  325.   
  326.         public function setExpiry($expiryValue)  
  327.         {  
  328.             if (!is_int($expiryValue))   
  329.             {  
  330.                 throw new Exception  
  331.                 (  
  332.                     "Invalid seconds number '{$expiryValue}'"  
  333.                 );  
  334.             }  
  335.             $this->_expiryValue = $expiryValue;  
  336.         }  
  337.   
  338.         public function getExpiry()  
  339.         {  
  340.             return $this->_expiryValue;  
  341.         }  
  342.   
  343.         public function setCustomIdentifier($customIdentifier)  
  344.         {  
  345.             $this->_customIdentifier = $customIdentifier;  
  346.         }  
  347.   
  348.         public function getCustomIdentifier()  
  349.         {  
  350.             return $this->_customIdentifier;  
  351.         }         
  352.   
  353.         public function getPayload()  
  354.         {  
  355.             $sJSONPayload = str_replace  
  356.             (  
  357.                 '"' . self::APPLE_RESERVED_NAMESPACE . '":[]',  
  358.                 '"' . self::APPLE_RESERVED_NAMESPACE . '":{}',  
  359.                 json_encode($this->_getPayload())  
  360.             );  
  361.             $nJSONPayloadLen = strlen($sJSONPayload);  
  362.   
  363.             if ($nJSONPayloadLen > self::PAYLOAD_MAXIMUM_SIZE)  
  364.             {  
  365.                 if ($this->_autoAdjustLongPayload)   
  366.                 {  
  367.                     $maxTextLen = $textLen = strlen($this->_text) - ($nJSONPayloadLen - self::PAYLOAD_MAXIMUM_SIZE);  
  368.                     if ($nMaxTextLen > 0)  
  369.                     {  
  370.                         while (strlen($this->_text = mb_substr($this->_text, 0, --$textLen'UTF-8')) > $maxTextLen);  
  371.                         return $this->getPayload();  
  372.                     }else  
  373.                     {  
  374.                         throw new Exception  
  375.                         (  
  376.                             "JSON Payload is too long: {$nJSONPayloadLen} bytes. Maximum size is " .  
  377.                             self::PAYLOAD_MAXIMUM_SIZE . " bytes. The message text can not be auto-adjusted."  
  378.                         );  
  379.                     }  
  380.                 }else  
  381.                 {  
  382.                     throw new Exception  
  383.                     (  
  384.                         "JSON Payload is too long: {$nJSONPayloadLen} bytes. Maximum size is " .  
  385.                         self::PAYLOAD_MAXIMUM_SIZE . " bytes"  
  386.                     );  
  387.                 }  
  388.             }  
  389.             return $sJSONPayload;  
  390.         }     
  391.     }  
  392.   
  393. ?>  
  394. date_default_timezone_set('PRC');  
  395. echo "we are young,test apns.  -".date('Y-m-d h:i:s',time());  
  396.   
  397. $rootpath = 'entrust_root_certification_authority.pem';  //ROOT证书地址  
  398. $cp = 'ck.pem';  //provider证书地址  
  399. $apns = new APNS(1,$cp);  
  400. try  
  401. {  
  402.     //$apns->setRCA($rootpath);  //设置ROOT证书  
  403.     $apns->connect(); //连接  
  404.     $apns->addDT('acc5150a4df26507a84f19ba145ca3c1be5842a6177511ce7c43d01badb1bd96');  //加入deviceToken  
  405.     $apns->setText('这是一条测试信息');  //发送内容  
  406.     $apns->setBadge(1);  //设置图标数  
  407.     $apns->setSound();  //设置声音  
  408.     $apns->setExpiry(3600);  //过期时间  
  409.     $apns->setCP('custom operation',array('type' => '1','url' => 'http://www.google.com.hk'));  //自定义操作  
  410.     $apns->send();  //发送  
  411.     echo ' sent ok';  
  412. }catch(Exception $e)  
  413. {  
  414.     echo $e;  
  415. }  
  416. ?>  
  417.   
  418.   
  419.   


2,启动 Apache 

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

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

输入 sudo apachectl start,这样Apache就启动了。打开Safari浏览器地址栏输入 “http://localhost”,可以看到内容为“It works!”的页面。其位

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

3,如何调试

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

你可能感兴趣的:(IOS消息推送之APNS)