Sending APNS thousand of devices php taking too much time
Asked Answered
D

4

5

I am sending a APNS to multiple devices in PHP in a loop.

while($row = mysql_fetch_array($result))
  {
    $row['devicetoken'];
    $row['devcertificate'];
    $row['prodcertificate'];

    if($devprod=="dev"){
        $apnsserverurl="ssl://gateway.sandbox.push.apple.com:2195";
        $certificatename=$appname."".$row['devcertificate'];
    }
    elseif($devprod=="prod"){
        $apnsserverurl="ssl://gateway.push.apple.com:2195";
        $certificatename=$appname."".$row['prodcertificate'];
    }
    sendpush($row['devicetoken'],$certificatename,$apnsserverurl);
  }

Here is the send push function :

function sendpush($deviceToken,$certificatename,$apnsserverurl){
// Get the parameters from http get or from command line
$message = utf8_encode(urldecode($_POST['message']));
$badge = 0;
$sound = "";
// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;
/* End of Configurable Items */



$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $certificatename);

$fp = stream_socket_client($apnsserverurl, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
    //print "Failed to connect $err";
    return;
}
else {
    //print "Connection OK";
}
$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "n";
fwrite($fp, $msg);
fclose($fp);   
}

The problem that am facing is that its taking too much time. How can I optimise the code?


I am finally using https://code.google.com/p/apns-php/, which make 1 connection only and queues messages

Dap answered 1/10, 2013 at 12:15 Comment(4)
You might be able to parallelise your streams in PHP, see here.Grider
hello there ok thanks. Is there any other work arounds?Dap
That's not a workaround. If you can get it working (and I don't know if it is possible in a stream context) then you can initiate a large number of connections at a time, and it will take the length of time of the slowest one. That's an extremely powerful way to speed this up. But, also: are you doing this in a web or a console? If this is in a web request, move it to an offline process.Grider
Hello ok thanks for the tip, i am on web.Dap
V
6

The APNs Best Practices requires you to keep your connection to their servers open:

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.

Source

Apple is probably treating your repeated connections as DoS attacks and throttling the processing.

Vilayet answered 1/10, 2013 at 17:4 Comment(0)
L
5

hylander0 makes a valid point. Otherwise, your code looks OK. The only problem you have is that you're re-creating the SSL connection for every push you send. Even if Apple does not block you, that process alone takes time.

Move the stream_context_create() code outside the sendPush function and either make it global or pass it by reference to the sendPush function in the loop (which should basically only use the fwrite(). This will open one connection with Apple and send all the push notifications in probably less than a second.

Note that the connection will be severed if the payload or pushtoken is invalid so it's important to re-create the connection if the fwrite fails before continuing to loop.

Lupita answered 2/10, 2013 at 8:44 Comment(1)
As this post is nearly three years old, it's probably because Apple updated APNS since, but I just tried sending multiple notifications using the method shown in here, with the same connexion that remains open while I loop through a token array, containing one fake token, and though this sending is obviously not working, every other devices received their notification as intended, so it looks like the connexion isn't closed in case of failure, well, not anymore.Danny
V
2

Yes, stream_socket_client() should be out side of loop. Keep fwrite() in loop only.

Verret answered 5/5, 2015 at 10:37 Comment(0)
B
0

A simple way to send thousands or even millions of APNS with php (tested on PHP 7 & HTTP2 procotol):

$data = array("aps" => $contents); // $contents if your data like badge, alert, etc
$payload = json_encode($data);
        
$header = ["alg" => "ES256", "kid" => "YOUAPIKEY"];
$header = base64_encode(json_encode($header));

$claim = ["iss" => $teamId, "iat" => time()];
$claim = base64_encode(json_encode($claim));

$token = $header.".".$claim;

$pkey  = file_get_contents("YOUKEY.p8");
$signature = "";
openssl_sign($token, $signature, $pkey, 'sha256');
$sign = base64_encode($signature);

$jws = $token.".".$sign;

    // headers
$headers = array(
        "apns-topic: "."YOUBUNDLEID",
        'Authorization: bearer ' . $jws,
        "apns-priority: 10",
        "apns-push-type: alert"
);

$devices = []; // YOUR array of tokens
$mh = curl_multi_init();

if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
   curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 5);
}
curl_multi_setopt($mh, CURLMOPT_PIPELINING, 2);

foreach($devices as $token)
{
    // open connection 
    $http2ch = curl_init();

    // cleanup device tokens
    $token = str_replace(' ', '', trim($token, '<> '));

    // url (endpoint)
    $url = $http2_server."/3/device/".$token;

    // other curl options
    curl_setopt_array($http2ch, array(
        CURLOPT_URL => $url,
        CURLOPT_PORT => 443,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_HEADER => 1,
        CURLOPT_HTTP_VERSION => 3,
    ));
    curl_multi_add_handle($mh,$http2ch);
}

$still_running = true;
do {
    while (($curlCode = curl_multi_exec($mh, $still_running)) == CURLM_CALL_MULTI_PERFORM) {
      curl_multi_select($mh);
    }

      if ($curlCode != CURLM_OK) {
          break;
      }

      while ($res = curl_multi_info_read($mh)) {
          $handle = $done['handle'];
          $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
          $datas = curl_getinfo($handle);
          // retrieve data like token to clean DB for example if error

          curl_multi_remove_handle($mh, $handle);
    }
} while ($still_running);

curl_multi_close($mh);
Bewhiskered answered 6/7, 2023 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.