I'm sending Apple Push Notifications (APN) from my server with a PHP script. First step is to populate a database table with the device tokens and the message to send. Then my script iterates over these table rows and sends the notifications one by one. The script is invoked by a cronjob every minute and if there are messages in my table it sends at most 50 of them and then it exits. The most important parts of my script are shown below.
I can use this script to send messages to five or ten test devices and all the messages get delivered successfully. But if I use the very same script to send the message to all 300 registered devices, only 50% get delivered. The same device sometimes gets the notifications and sometimes it doesn't. So, I know my script works and I know my devices can receive the notifications, but if I want to send a message to all devices only about a half of the messages are received.
I know that APN does not guarantee to successfully deliver the messages, but a success rate of 50% seems very low to me. Thus, these are my questions:
What is the typical success rate of messages sent via APN?
Should I better send all 300 push notifications at once?
Are there any ways to get error messages from the APN servers?
Here's the code from my script:
// create stream context
$ctx = stream_context_create();
stream_context_set_option(
$ctx,
'ssl',
'local_cert',
'/path/to/push.pem'
);
stream_context_set_option(
$ctx,
'ssl',
'passphrase',
$passphrase
);
// connect with Apple server
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195',
$err,
$errstr,
60,
STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT,
$ctx
);
// get next 50 rows from database table
// ...
// for every row in my table
foreach( $rows as $row ) {
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// send a message
$result = fwrite($fp, $msg, strlen($msg));
if( $result ) {
// delete row from database
} else {
// log error
break;
}
}
Related
I am working on push notifications and I want to send chat push notifications to IOS using php. But when I send 5 push notifications to apns (Apple Push Notification Server) then apns discard the old push notification and send only the most recent push notification to device when device get online.
I search the for solution on internet and one solution that I found is to set the notification expiry time. So I implemented this solution by my actual problem not solved.
Is there any way that solve my problem. Suggest any usefull solution or reference site.
IOS push notification with PHP
Below is my sample code
public function sendIOSNotification($tokens, $data, $envoirement = 'production') {
try {
$payload = json_encode($this->setIosNotificationDataParameters($data));
$deviceTokens = str_replace(array(' ', '<', '>'), '', $tokens['ios']);
// FUNCTION NOTIFICATIONS
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', config('push-notification.appNameIOS.certificate_' . $envoirement));
stream_context_set_option($ctx, 'ssl', 'passphrase', 'push');
//send notification
$fp = stream_socket_client(
config('push-notification.appNameIOS.ios_push_notification_' . $envoirement), $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx
);
$res = [];
foreach ($deviceTokens as $deviceToken) {
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken)
. pack('n', strlen($payload)) . $payload
.pack('N', time()).pack('N', time() + 86400);
$res = json_encode($result);
}
fclose($fp);
\Log::info("=== IOS Notification Send Successfully ===");
return true;
} catch (\Exception $ex) {
$messages = $ex->getMessage() . '::' . $ex->getFile() . '( ' . $ex->getLine() . ' )';
\Log::ifno("===Push Notificaion Exception===");
\Log::ifno($messages);
return true;
}
}
You cannot do what you intend according to the documentation. It is the documented behavior:
Quality of Service, Store-and-Forward, and Coalesced Notifications
Apple Push Notification service includes a Quality of Service (QoS)
component that performs a store-and-forward function. If APNs attempts
to deliver a notification and the destination device is offline, APNs
stores the notification for a limited period of time and delivers it
when the device becomes available again. This component stores only
the most recent notification per device and per app. If a device is
offline, sending a notification request targeting that device causes
the previous request to be discarded. If a device remains offline for
a long time, all its stored notifications in APNs are discarded.
source: apple push notifications documentation
This means just an offline notification per user per app.
You should architecture your application in a different way. First of all, for a chat application you cannot expect sending thousand push notifications when the device is back online. You will have to implement some extra mechanism for your app to retrieve the old messages if you want to show them in your app
I know this question has been asked several times but I'm looking for another answer to my problem.
Basically, I have a MYSQL database with over 10000 records of iPhone's device ID for push notifications in it.
Now, I need to run a PHP file that will connect to the Apple APNS server to send push notification to these 10000 records in my MYSQL database.
When I run my php page, after a while i get a timed out error...
in my research I came across a few solutions BUT most require to edit some stuff in etc folder on the server.
My question is:
is there any way to select lets say 30 records from MYSQL database, then stop and then select another 30 and then stop and then another 30 and so on and so forth until the who records have been sent the PN's?
This is my current code:
$message = "Welcome";
$sqld = "SELECT * FROM pushUsers ORDER BY id DESC";
$queryd = mysqli_query($db_conx, $sqld);
$productCountd = mysqli_num_rows($queryd); // count the output amount
if ($productCountd > 0) {
//while($rowd = mysqli_fetch_array($queryd, MYSQLI_ASSOC)){
while($rowd = mysqli_fetch_assoc($queryd)){
$deviceID = $rowd["deviceNo"];
$deviceType = $rowd["deviceType"];
if($deviceType == 'iPhone' || $deviceType == 'iPad'){
///////////////SEND PUSH NOTU8FOCATION FOR iOS//////////////////////
// Put your device token here (without spaces):
$deviceToken = ''. $deviceID.'';
// Put your private key's passphrase here:
$passphrase = '123456';
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apple_push_notification_production.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
//****IMPORTANT**** LIVE APNS URL:
//ssl://gateway.sandbox.push.apple.com:2195
$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);
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => ''.$message.'',
'sound' => 'default',
'link_url' => 'http://xxxx.xom',
'category' => 'APP',
'badge' => '1',
);
// 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));
if (!$result)
echo 'Message not delivered' . PHP_EOL;
else
echo 'Message successfully delivered' . PHP_EOL;
// Close the connection to the server
fclose($fp);
///////////////END SEND PUSH NOTU8FOCATION FOR iOS//////////////////////
}
}
} else {
}
ANY HELP WOULD BE APPRECIATED.
Thanks in advance.
There are a few ways to solve your problem.
I can't help you writing code, instead I'll write some conceptual tips.
Change maximum execution time in PHP
Use set_time_limit() to increase execution time limit.
But, if your PHP is running in safe_mode, this function has no effect.
Split your job
The way to do it depends on how you call this PHP file, if it's a cron job, you can persist the last index that you pushed the notification, then call the cron job multiple times.
Improve the performance
The push service should have a bulk method, where you send many notifications in a single http request. The HTTP request is the bottleneck here, not the database.
Every request have it own delay, if you have 10k rows in database, there will be 10k requests. If the request takes 10ms (a very low time), you will take 100 seconds to execute your script.
If you send your notifications in a bulk method that accepts 100 notifications per request and take 50ms to run, you will need only 100 requests, and will take only 5 seconds to execute it.
Doing this way, you can split your mysql query with LIMIT and OFFSET, so you only load in the memory what you need before sending the request.
Yes, you need to send push notification in 100-100 or 500-500 at once because may be your server not too much strong for 10,000 record process at once and other reason is that may be your execution time is 30 sec or something like that so a script run for particular time and after that its give timeout.
$message = "Welcome";
for($i=10000 ;$i>0;$i-$limit){
//your code here
sleep(2);
}
I am trying to send push notification on IOS using PHP there are 300,000 device tokens
I have written a small routine, on each execution the routine send notification to 100 devices, and after 1 second delay routine is executed again and notification is sent to next 100 devices, routine is as follows
$body['aps'] = array('alert' => 'Energy is full',
'sound' => 'default',
'badge' => '0'
);
$url = 'ssl://gateway.push.apple.com:2195';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ProductionCertificate.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', 'my pass phrase');
stream_context_set_option($ctx, 'ssl', 'cafile', 'trusted_ca.cer');
$fp = stream_socket_client($url, $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp) { return -1;}
foreach($OneHundredTokens as $deviceToken) {
$payload = json_encode($body, 256);
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
$result = fwrite($fp, $msg, strlen($msg));
}
fclose($fp);
Some time this routine works perfectly fine, and i have seen sent notifications on devices.
However some times it gives following error
Warning: fwrite(): SSL: An established connection was aborted by the
software in your host machine.
My Questions are:
Is it a correct way to send bulk notification?
I open a connection once and write data(notification) for 100 devices, and then close the connection, or is it better to open connection for each device token and then close?
What is causing above error?
Findings
Sent approx 90,000 push notifications (100 in single connection open and next 100 after 1 second delay)
Approx 60,000 failed (fwrite failed) with above error and approx 30,000 push were successfully sent (fwrite returned positive integers indicating number of bytes written).
How to implpement on below code for send 1000 push notification at same time server to ios deivce using php.
function push_iphone($deviceTokenid,$message){
$passphrase = '12345';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp)
return false;
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'message' => $message,
'sound' => 'default',
'flag' => '0'
);
// Encode the payload as JSON
$payload = json_encode($body);
//echo $device_token_id;
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceTokenid) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if(!$result){
echo "tst";
return true;
}else{ echo "test ";
return false;
}
fclose($fp);
}
Can any body help me about it.
Apple has an answer to this in their documentation:
You may establish multiple connections to the same gateway or to
multiple gateway instances. If you need to send a large number of
remote notifications, spread them out over connections to several
different gateways. This improves performance compared to using a
single connection: it lets you send the remote 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.
By threading your php application you can achieve this at (almost) the same time. Take a look at Patterns for PHP multi processes? for a direction which to take to setup forks.
I'm trying to send notifications for multiple devices. So I get tokens to an array, open connection, send notifications in a loop, close connection.
However, After 9-10 devices, it stops sending. I believe Apple somehow drops the connection .
Here's my code:
$message = 'Push';
$passphrase = 'mypass';
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'MyPemFile.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);
echo 'Connected to Apple service. ' . PHP_EOL;
// Encode the payload as JSON
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$payload = json_encode($body);
$result = 'Start'.PHP_EOL;
$tokenArray = array('mytoken');
foreach ($tokenArray as $item)
{
// Build the binary notification
$msg = chr(0).pack('n', 32).pack('H*', $item).pack('n', strlen($payload)).$payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if (!$result)
echo 'Failed message'.PHP_EOL;
else
echo 'Successful message'.PHP_EOL;
}
// Close the connection to the server
fclose($fp);
Is there something wrong with my code? I think I need to open connection once, send notifications then close. Should I be doing fwrite() with multiple tokens? I don't know how though. Any ideas or solutions are accepted.
By the way the answer is like:
Successful message
Successful message
Successful message
Successful message
Successful message
Successful message
Successful message
Successful message
Successful message
Successful message
Failed message
Failed message
Failed message
Failed message
Failed message
...
Failed message
P.S. I had character issue with same code but it is solved in another question, this is another problem, not a duplicate.
The first failed message likely has something wrong with it, at which point Apple will close the connection to signal to you that there was an issue. If you're using the enhanced format, you have an opportunity to get some feedback before Apple closes the connection to see what was wrong with the notification you sent. After this happens you must re-establish a connection to send more messages.
There are a number of reasons it might fail. You might have sent an invalid device token, your payload might be invalid or of the wrong length, etc.
Best check out the documentation for APNS: http://developer.apple.com/library/mac/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html