EDIT - Using the enhanced binary format
Turns out I wasn't using the enhanced binary format so I changed my code.
<?php
$message = $_POST['message'];
$passphrase = $_POST['pass'];
//Connect to db
if ($db_found) {
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem');
stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext);
stream_set_blocking ($fp, 0);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS for Push Notification' . PHP_EOL;
// Keep push alive (waiting for delivery) for 90 days
$apple_expiry = time() + (90 * 24 * 60 * 60);
$tokenResult = //SQL QUERY TO GET TOKENS
while($row = mysql_fetch_array($tokenResult)) {
$apple_identifier = $row["id"];
$deviceToken = $row['device_id'];
$payload = json_encode($body);
// Enhanced Notification
$msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload;
// SEND PUSH
fwrite($fp, $msg);
// We can check if an error has been returned while we are sending, but we also need to
// check once more after we are done sending in case there was a delay with error response.
checkAppleErrorResponse($fp);
}
// Workaround to check if there were any errors during the last seconds of sending.
// Pause for half a second.
// Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved
usleep(500000);
checkAppleErrorResponse($fp);
echo 'Completed';
fclose($fp);
// SIMPLE BINARY FORMAT
/*for($i = 0; $i<count($deviceToken); $i++) {
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken[$i]) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
$bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].'';
if (!$result) {
$errCounter = $errCounter + 1;
echo 'Message not delivered' . PHP_EOL;
}
else
echo 'Message successfully delivered' . PHP_EOL;
}*/
// Close the connection to the server
//fclose($fp);
//Insert message into database
mysql_close($db_handle);
}
else {
print "Database niet gevonden ";
mysql_close($db_handle);
}
// FUNCTION to check if there is an error response from Apple
// Returns TRUE if there was and FALSE if there was not
function checkAppleErrorResponse($fp) {
//byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID).
// Should return nothing if OK.
//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait
// forever when there is no response to be sent.
$apple_error_response = fread($fp, 6);
if ($apple_error_response) {
// unpack the error response (first byte 'command" should always be 8)
$error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response);
if ($error_response['status_code'] == '0') {
$error_response['status_code'] = '0-No errors encountered';
} else if ($error_response['status_code'] == '1') {
$error_response['status_code'] = '1-Processing error';
} else if ($error_response['status_code'] == '2') {
$error_response['status_code'] = '2-Missing device token';
} else if ($error_response['status_code'] == '3') {
$error_response['status_code'] = '3-Missing topic';
} else if ($error_response['status_code'] == '4') {
$error_response['status_code'] = '4-Missing payload';
} else if ($error_response['status_code'] == '5') {
$error_response['status_code'] = '5-Invalid token size';
} else if ($error_response['status_code'] == '6') {
$error_response['status_code'] = '6-Invalid topic size';
} else if ($error_response['status_code'] == '7') {
$error_response['status_code'] = '7-Invalid payload size';
} else if ($error_response['status_code'] == '8') {
$error_response['status_code'] = '8-Invalid token';
} else if ($error_response['status_code'] == '255') {
$error_response['status_code'] = '255-None (unknown)';
} else {
$error_response['status_code'] = $error_response['status_code'].'-Not listed';
}
echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b> Identifier:<b>' . $error_response['identifier'] . '</b> Status:<b>' . $error_response['status_code'] . '</b><br>';
echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>';
return true;
}
return false;
}
?>
While using this new code I still can't send more than 300+ messages because of this error:
Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in PATH_TO_SCRIPT.php on line NUMBER
this code works fine when sending just a few push messages.
OLD QUESTION with simple binary format So I integrated Push Notifications a long time ago and it was working fine for messages sent to less than 500 people. Now I'm trying to send a push notification to more than 1000 people but then i get the broken error
Warning: fwrite() [function.fwrite]: SSL: Broken pipe in PATH_TO.PHP on line x
I've read the apple docs and I know that invalid tokens can cause the socket to disconnect. Some solutions online recommend on detecting disconnections and reconnect like this one:
Your server needs to detect disconnections and reconnect if necessary. Nothing is
"instant" when networking is involved; there's always some latency and code needs to take
that into account. Also, consider using the enhanced binary interface so you can check the
return response and know why the connection was dropped. The connection can also be
dropped as a result of TCP keep-alive, which is outside of Apple's control.
I'm also running a Feedback Service which detects Invalid tokens (Users who wanted Push Notifications but deleted the application) and that just works fine. That php script echos the deleted ID's and I can confirm that those tokens are deleted from our MySQL database.
How can I be able to detect a disconnect or broken pipe and react to that so my push notifications can reach more than 1000 people?
Currently I'm using this simple push.php script.
<?php
$message = $_POST['message'];
$passphrase = $_POST['pass'];
//Connect to database stuff
if ($db_found) {
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem');
stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext);
if (!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
echo 'Connected to APNS for Push Notification' . PHP_EOL;
$deviceToken[] = //GET ALL TOKENS FROM DATABASE AND STORE IN ARRAY
for($i = 0; $i<count($deviceToken); $i++) {
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken[$i]) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
$bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].'';
if (!$result) {
$errCounter = $errCounter + 1;
echo 'Message not delivered' . PHP_EOL;
}
else
echo 'Message successfully delivered' . PHP_EOL;
}
echo $bodyError;
// Close the connection to the server
fclose($fp);
//CODE TO SAVE MESSAGE TO DATABSE HERE
if (!mysql_query($SQL,$db_handle)) {
die('Error: ' . mysql_error());
}
}
else {
print "Database niet gevonden ";
mysql_close($db_handle);
}
?>
Also fwrite returns 0 written bytes when the SLL Broken Pipe error occurs.
I must also mention that I'm no PHP or web developer but an app developer so my php skills aren't that good.
checkAppleErrorResponse($fp);
function? I see that you call it inside the loop, but you don't check it's return value. If that function prints an error, you should manage it and do the fwrite again, before going to the next item – Cantaloupe