Tuesday, June 10, 2014

PHP Apple Enhanced Push Notification read error response

PHP Apple Enhanced Push Notification read error response

In PHP, how do you use fread() to check if there is an error response when sending enhanced push notifications?
I have read the Apple docs, a couple vague posts thru Google, and a couple questions/answers here on SO but this was still very confusing.
Here is what I looked at:http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.htmlhttp://stackoverflow.com/questions/6665921/reading-error-from-apple-enhanced-push-notification-with-php iPhone Push Notification - Error response problem
I am going to answer my own question below, based on the fact that: (1) I found this a very confusing topic, and (2) I had to piece the information together with lots of trial and error to get it to work, and (3) this blog post that says it is encouraged: http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/
When you send a push notification, there are several problems:
  • If there is a problem, Apple will disconnect you but you don't know about it. When you use basic notifications there is no way to know if they were all sent or not. SOLUTION: This is the whole point of using an enhanced notification and then checking for an error response. Note that we will use "ORDER BY id" in database query and then use the id as the identifier that we send in notification. This way, if there is a problem, we know exactly which row in the db caused the problem (and therefore we know when Apple disconnected us and stopped sending the notifications). We can then continue sending Push notifications to all the rows after the row that caused the problem, without having to resend to the ones we already sent to.
  • Apple does NOT send any response back if everything is ok, so this can cause your script to pause and wait forever while fread() is waiting for data that is not coming. SOLUTION: Need to set stream_set_blocking to 0 so that fread always returns right away. Note that this causes another minor issue that fread can return before it receives an error response, but see the workaround in the code, which is just to pause for 1/2 a second AFTER all your sending is done and then check fread one more time.
  • You can send multiple push notifications much faster than it takes an error response to get back to you. SOLUTION: Again this is the same workaround mentioned above... pause for 1/2 a second AFTER all your sending is done and then check fread one more time.
Here is my solution using PHP, which addresses all my problems that I encountered. Its pretty basic but gets the job done. I have tested it with sending a few notifications at a time as well as sending out 120,000 at one time.

<?php /* * Read Error Response when sending Apple Enhanced Push Notification * * This assumes your iOS devices have the proper code to add their device tokens * to the db and also the proper code to receive push notifications when sent. * */ //database $host = "localhost"; $user = "my_db_username"; $pass = "my_db_password"; $dbname = "my_db_name"; $con = mysql_connect($host,$user,$pass); if (!$con) { die('Could not connect to database: ' . mysql_error()); } else { mysql_select_db($dbname, $con); } // IMPORTANT: make sure you ORDER BY id column $result = mysql_query("SELECT id,token FROM `device_tokens` ORDER BY id"); //Setup notification message $body = array(); $body['aps'] = array('alert' => 'My push notification message!'); $body['aps']['notifurl'] = 'http://www.mydomain.com'; $body['aps']['badge'] = 1; //Setup stream (connect to Apple Push Server) $ctx = stream_context_create(); stream_context_set_option($ctx, 'ssl', 'passphrase', 'password_for_apns.pem_file'); stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns.pem'); $fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_
Hi. I implemented you're code in my (working) pusher class. Strangely I dont't ever get a response from apple. The $apple_error_response is always false. But some of the push messages ARE delivered, and some just fail. Have you any idea why I don't get any response? –  Michael Kunst Sep 16 '13 at 12:54
I posted this almost a year and a half ago and stopped using push notifications not long after so I don't really remember all the issues I dealt with. For debugging create list of tokens that you know work then copy tokens & modify so they will fail (so 5 known good & 5 known bad). Then also once you have accepted push notification on your device and verified that you receive it, disable the push notifications on that device and see what happens when you try to send to that token again. Also check Apple's documentation to make sure they did not change something on how their server responds. –  jsherk Sep 16 '13 at 17:05
Thanks for your answer. I had to increase the usleep time to one second, then it worked (mostly). But I still wasnt sure if the answer would be there in this time. So I made a loop which checks for an answer every 50 miliseconds, and if it didnt come at all after 5 seconds it returns. –  Michael Kunst Sep 17 '13 at 7:59

Am not sure the content of you code but you should try ApnsPHP it is well tested works perfectly fine and it able to handle all possible exception and error for you.
Other Alternatives
https://github.com/sebastianborggrewe/PHP-Apple-Push-Notification-Serverhttps://github.com/bortuzar/PHP-Mysql---Apple-Push-Notification-Server
Have tested 2 out of the 3 i examples and did not have issue of implementation and error management.
Thanks
:)



Xcode透過Ad-Hoc發佈App給其它裝置測試使用

This summary is not available. Please click here to view the post.

Check entitlements of your provision file

Check entitlements of your provision file

security cms -D -i testSecurity.mobileprovision | grep -A12 "Entitlements"

Line Protocol

http://media.line.me/howto/en/

Tuesday, August 13, 2013

网络编程专题总结(重要)

网络编程专题总结(重要)   

一:确认网络环境3G/WIFI 二:使用NSConnection下载数据 三:使用NSXMLParser解析xml文件
一:确认网络环境3G/WIFI

    1. 添加源
文件和framework
    开发Web等网络应用程序的时候,需要确认网络环境,连接情况等信息。如果没有处理它们,是不会通过
Apple的审(我们的)查的。
    Apple 的 例程 Reachability 中介绍了取得/检测网络状态的方法。要在应用程序程序中使用Reachability,首先要完成如下两部:
    1.1. 添加源文件:
    在你的程序中
使用 Reachability 只须将该例程中的 Reachability.h 和 Reachability.m 拷贝到你的工程中。如下图:
    1.2.添加framework:
    将SystemConfiguration.framework 添加进工程。如下图:
    2. 网络状态
    Reachability.h中定义了三种网络状态:
    typedef enum {
        NotReachable = 0,            //无连接
        ReachableViaWiFi,            //使用3G/GPRS网络
        ReachableViaWWAN            //使用WiFi网络
    } NetworkStatus;
    因此可以这样检查网络状态:
    Reachability *r = [Reachability reachabilityWithHostName:@“
www.apple.com”];
    switch ([r currentReachabilityStatus]) {
            case NotReachable:
                    // 没有网络连接
                    break;
            case ReachableViaWWAN:
                    // 使用3G网络
                    break;
            case ReachableViaWiFi:
                    // 使用WiFi网络
                    break;
    }
    3.检查当前网络环境
    程序启动时,如果想检测可用的网络环境,可以像这样
    // 是否wifi
    + (BOOL) IsEnableWIFI {
        return ([[Reachability reachabilityForLocalWiFi] currentReachabilityStatus] != NotReachable);
    }
    // 是否3G
    + (BOOL) IsEnable3G {
        return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] != NotReachable);
    }
    例子:
    - (void)viewWillAppear:(BOOL)animated {    
    if (([Reachability reachabilityForInternetConnection].currentReachabilityStatus == NotReachable) && 
            ([Reachability reachabilityForLocalWiFi].currentReachabilityStatus == NotReachable)) {
            self.navigationItem.hidesBackButton = YES;
            [self.navigationItem setLeftBarButtonItem:nil animated:NO];
        }
    }
    4. 链接状态的实时
通知
    网络连接状态的实时检查,通知在网络应用中也是十分必要的。接续状态发生变化时,需要及时地通知用户:
    Reachability 1.5版本
    // My.AppDelegate.h
    #import "Reachability.h"
    @interface MyAppDelegate : NSObject <UIApplicationDelegate> {
        NetworkStatus remoteHostStatus;
    }
    @property NetworkStatus remoteHostStatus;
    @end
    // My.AppDelegate.m
    #import "MyAppDelegate.h"
    @implementation MyAppDelegate
    @synthesize remoteHostStatus;
    // 更新网络状态
    - (void)updateStatus {
        self.remoteHostStatus = [[Reachability sharedReachability] remoteHostStatus];
    }
    // 通知网络状态
    - (void)reachabilityChanged:(NSNotification *)note {
        [self updateStatus];
        if (self.remoteHostStatus == NotReachable) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"AppName", nil)
                         message:NSLocalizedString (@"NotReachable", nil)
                        delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
            [alert show];
            [alert release];
        }
    }
    // 程序启动器,启动网络监视
    - (void)applicationDidFinishLaunching:(UIApplication *)application {
        // 设置网络检测的站点
        [[Reachability sharedReachability] setHostName:@"www.apple.com"];
        [[Reachability sharedReachability] setNetworkStatusNotificationsEnabled:YES];
        // 设置网络状态变化时的通知函数
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:)
                                                 name:@"kNetworkReachabilityChangedNotification" object:nil];
        [self updateStatus];
    }
    - (void)dealloc {
        // 删除通知对象
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        [window release];
        [super dealloc];
    } 
    
    Reachability 2.0版本
    // MyAppDelegate.h
    @class Reachability;

        @interface MyAppDelegate : NSObject <UIApplicationDelegate> {
            Reachability  *hostReach;
        }

    @end

    // MyAppDelegate.m
    - (void)reachabilityChanged:(NSNotification *)note {
        Reachability* curReach = [note object];
        NSParameterAssert([curReach isKindOfClass: [Reachability class]]);
        NetworkStatus status = [curReach currentReachabilityStatus];
    
        if (status == NotReachable) {
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"AppName""
                              message:@"NotReachable"
                              delegate:nil
                              cancelButtonTitle:@"YES" otherButtonTitles:nil];
                              [alert show];
                              [alert release];
        }
    }
                              
    - (void)applicationDidFinishLaunching:(UIApplication *)application {
        // ...
                  
        // 监测网络情况
        [[NSNotificationCenter defaultCenter] addObserver:self
                              selector:@selector(reachabilityChanged:)
                              name: kReachabilityChangedNotification
                              object: nil];
        hostReach = [[Reachability reachabilityWithHostName:@"www.google.com"] retain];
        hostReach startNotifer];
        // ...
    }
二:使用NSConnection下载数据
    1.创建NSConnection对象,设置委托对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[self urlString]]];
    [NSURLConnection connectionWithRequest:request delegate:self];
    2. NSURLConnection delegate委托方法
        - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;  
        - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;  
        - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;  
        - (void)connectionDidFinishLoading:(NSURLConnection *)connection;  

    3. 实现委托方法
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        [self.receivedData setLength:0]; //通常在这里先清空接受数据的缓存
    }
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [self.receivedData appendData:data]; //可能多次收到数据,把新的数据添加在现有数据最后
    }
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        // 
错误处理
    }
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        // disconnect
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
        NSString *returnString = [[NSString alloc] initWithData:self.receivedData encoding:NSUTF8StringEncoding];
        NSLog(returnString);
        [self urlLoaded:[self urlString] data:self.receivedData];
        firstTimeDownloaded = YES;
    }
三:使用NSXMLParser解析xml文件
  1. 设置委托对象,开始解析
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];   //或者也可以使用initWithContentsOfURL直接下载文件,但是有一个原因不这么做:
    // It's also possible to have NSXMLParser download the data, by passing it a URL, but this is not desirable
    // because it gives less control over the network, particularly in responding to connection errors.
    [parser setDelegate:self];
    [parser parse];

    2. 常用的委托方法
    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
                                namespaceURI:(NSString *)namespaceURI
                                qualifiedName:(NSString *)qName 
                                attributes:(NSDictionary *)attributeDict;
    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
                                namespaceURI:(NSString *)namespaceURI 
                                qualifiedName:(NSString *)qName;
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
    - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError;

    static NSString *feedURLString = @"http://www.yifeiyang.net/test/test.xml";

    3.  应用举例
    - (void)parseXMLFileAtURL:(NSURL *)URL parseError:(NSError **)error
    {
        NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:URL];
        [parser setDelegate:self];
        [parser setShouldProcessNamespaces:NO];
        [parser setShouldReportNamespacePrefixes:NO];
        [parser setShouldResolveExternalEntities:NO];
        [parser parse];
        NSError *parseError = [parser parserError];
        if (parseError && error) {
            *error = parseError;
        }
        [parser release];
    }

    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI 
                                        qualifiedName:(NSString*)qName attributes:(NSDictionary *)attributeDict{
        // 元素开始句柄
        if (qName) {
            elementName = qName;
        }
        if ([elementName isEqualToString:@"user"]) {
            // 输出属性值
            NSLog(@"Name is %@ , Age is %@", [attributeDict objectForKey:@"name"], [attributeDict objectForKey:@"age"]);
        }
    }

    - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI 
                                        qualifiedName:(NSString *)qName
    {
        // 元素终了句柄
        if (qName) {
               elementName = qName;
        }
    }
    - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
    {
        // 取得元素的
text
    }
    NSError *parseError = nil;
    [self parseXMLFileAtURL:[NSURL URLWithString:feedURLString] parseError:&parseError];