Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending 5000 push notifications at same time, keep the connection to apple open?

Right now I'm using the following piece of code to send my push notifications

function applePush($deviceToken,$sound,$message,$object,$thread = 0)
{
$passphrase = 'Secret';


        $ctx = stream_context_create();
        stream_context_set_option($ctx, 'ssl', 'local_cert', 'secret.pem');
        stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);

        // Open a connection to the APNS server
        $fp = stream_socket_client(
            'ssl://gateway.push.apple.com:2195', $err,
            $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

        if (!$fp)
            exit("Failed to connect: $err $errstr" . PHP_EOL);

        //Customizable sounds
        if ($sound == 0) { $sound = 'default'; }
        else {$sound = '/' . $sound . '.wav'; }         

        // Create the payload body
        if ($thread > 0)
            {

                    $body['aps'] = array(
                        'alert' => $message,
                        'sound' => $sound,
                        'obj' => $object,
                        't' => $thread,
                        );

            }
        else
            {
                    $body['aps'] = array(
                        'alert' => $message,
                        'sound' => $sound,
                        'obj' => $object
                        );

            }   


        // Encode the payload as JSON
        $payload = json_encode($body);

        // Build the binary notification
        $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

        // Send it to the server
        $result = fwrite($fp, $msg, strlen($msg));


        // Close the connection to the server
        fclose($fp);

}

What I am in essence doing is: 1) Pulling 5000 (and growing) device token's from an SQL database.
2) Using a PHP loop, for each device token I run the above listed applePush function

Now this works just fine and everyone is getting the push from apple. Now the kicker is that, my own personal iphone was the first device in my SQL database, and I would get the push notification instantly. BUT, I just bought a new iphone and I am the last device in the database now, and I notice it takes me almost 30 minutes to get the notificaions now. I am thinking this has to do with the physical time to make all those connections to apple, but the time it gets to the 5000th device ID, 30 minutes has lapsed.

This got me reading and it seems that Apple actually suggests keeping a connection open to APNS instead of opening and closing connections constantly.

So I write to get everyones opinion on this, am I doing this all wrong? And if I am, how can I modify my code to get it inline with apple policy and make it faster/more efficient

Thank you!

like image 540
Mark Avatar asked Dec 24 '13 15:12

Mark


2 Answers

Do not open and close connection 5000 times this is very expensive and not acceptable.

The Problem is unlike Android , Apple Server does not take an array of device tokens along with the playload.

A better approach is to loop trough your database and return an array.

This array will then be used in a loop once connection is open.

So you will have something like this:

//Apple Push Notifcations
$apn = new Apple_Push_Notifications();

//Get Device tokens into an array
$deviceTokens = array();
$deviceTokens = $apn->GetDeviceTokens();

//Push the notifications
if(count($deviceTokens) > 0){
    $apn->applePush($deviceTokens,$sound,$message,$object,$thread);
}

//In applePush loop through all the tokens and submit
function applePush($deviceTokens,$sound,$message,$object,$thread = 0)
{
    ....
    // Open a connection to the APNS server
    $fp = stream_socket_client(
        'ssl://gateway.push.apple.com:2195', $err,
        $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
    ....
    ....
    // Encode the payload as JSON
    $payload = json_encode($body);

    //Send the Push to each token the close connection
    foreach ($deviceTokens as $deviceToken) {
        // Build the binary notification
        $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;

        // Send it to the server
        $result = fwrite($fp, $msg, strlen($msg));
    }

    // Close the connection to the server
    fclose($fp);
}
like image 198
meda Avatar answered Sep 20 '22 01:09

meda


You should keep the connection open as long as possible. Another way to improve performance is to have multiple threads (each with its own connection to APN server, which remains open) and divide the work of sending the push notifications between them.

Closing the connection after each message is not only bad for performance, it might also get your IP banned if Apple considers this as a DOS attack.

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.

like image 44
Eran Avatar answered Sep 22 '22 01:09

Eran