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.
When you do:
fwrite($fp, $msg);
you are trying to write to the socket. If something goes wrong, fwrite will return false or 0 (depending on the php version) as the return value. When it happens, you must manage it.
You have two possibilities:
discard the entire operation
try again the last write operation
if you choose the second option, you have to do a new fwrite($fp, $msg) with THE SAME $fp and $msg of the failed fwrite() operation.
If you change the parameters, a 1409F07F:SSL error is returned
Moreover, there are situations where the fwrite fails at writing only "some bytes", you should manage even this situation, comparing the returned value with the lenght of $msg.
In this case, you should send the remaining part of the message, but in some situations you have to send the whole message again (according to this link).
Have a look at fwrite reference and the comments: Link
I can't give you actual PHP code, since I don't know PHP, but here's the logic you should use (according to Apple) :
Push Notification Throughput and Error Checking
If you're seeing throughput lower than 9,000 notifications per second, your server might benefit from improved error handling logic.
Here's how to check for errors when using the enhanced binary interface. Keep writing until a write fails. If the stream is ready for writing again, resend the notification and keep going. If the stream isn't ready for writing, see if the stream is available for reading.
If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.
Once everything has been sent, do one last check for an error response.
It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again.
Now, here's where the tradeoffs get interesting. You can check for an error response after every write, and you'll catch the error right away. But this causes a huge increase in the time it takes to send a batch of notifications.
Device tokens should almost all be valid if you've captured them correctly and you're sending them to the correct environment. So it makes sense to optimize assuming failures will be rare. You'll get way better performance if you wait for write to fail or the batch to complete before checking for an error response, even counting the time to send the dropped notifications again.
None of this is really specific to APNs, it applies to most socket-level programming.
If your development tool of choice supports multiple threads or interprocess communication, you could have a thread or process waiting for an error response all the time and let the main sending thread or process know when it should give up and retry.
This is taken from Apple's Tech Note: Troubleshooting Push Notifications.
EDIT
I don't know how you detect in PHP that the write failed, but when it does, you should attempt to write the failed notification once again, and if it fails again, try to read the error response and close the connection.
If you manage to read the error response, you will know which notification failed and you'll know the error type (the most likely error is 8 - invalid device token). If after writing 100 messages you get an error response for the 80th message, you must resend messages 81 to 100, since Apple never received them. In my case (Java server), I don't always manage to read the error response (sometimes I get an error when trying to read the response from the socket). In that case I can only move on an send the next notifications (and have no way of knowing which notifications were actually received by Apple). That's why it's important to keep your database clean of invalid tokens.
Anyway, you shouldn't be stuck in an infinite loop, since when getting an error after sending N notifications, you are not going to resend these N notifications. Unless you manage to read an error response from Apple (in which case you know exactly what to resend), you'll only resend the last notification, and even if that notification happens to be the one with the invalid token, you'll probably get the next error after sending more notifications (which is unfortunate, since it would have been much easier to detect the invalid tokens if you would get the failures immediately).
If you keep your database clean (i.e. store in it only device tokens that were sent to your App by Apple, and all of them belong to the same push environment - either sandbox or production), you shouldn't encounter any invalid device tokens.
The device tokens returned by the feedback service are not invalid tokens. They are valid tokens of devices that uninstalled your app. Invalid tokens have never been valid for the current push environment, and never will. The only way to identify invalid tokens is to read the error responses from Apple.
EDIT2:
I forgot to mention it before. I encountered a similar problem to yours when implementing the push notification server side in Java. I couldn't reliably get all the error responses returned by Apple.
I found that in Java there's a way to disable the TCP Nagle's algorithm, which causes the buffering of multiple messages before sending them in a batch to Apple. Though Apple encourages us to use Nagle's algorithm (for performance reasons), I found that when I disable it and then try to read the response from Apple after each message I send to them, I manage to receive 100% of the error responses (I verified it by writing a process that simulated the APNS server).
By disabling Nagle's algorithm and sending the notifications one by one, slowly, and atempting to read the error response after each message, you can locate all the invalid tokens in your DB and remove them. Once you know your DB is clean you can enable Nagle's algorithm and resume sending notifications quickly without bothering to read the error responses from Apple. Then, whenever you get an error while writing a message to the socket, you can simply create a new socket and retry sending only the last message.
My solution (to the now semi-old question) was that I had some development-environment APN tokens in my database trying to send to a production-environment. Once I got rid of them from my database the rest worked fine. Unfortunately, out of 7000+ APNs, I wasn't sure which tokens were bad so I had to erase them all in the hope that fresh tokens would be created when the user re-opened the app. So far so good.
Apple will halt all immediate attempts at sending a push notification if it comes across an erroneous APN token.
I had the exact same message appear which I had never seen before (below) on various apps so I'm glad I was able to resolve it.
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]
The solution is:
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
try {
$result = fwrite($fp, $msg, strlen($msg));
} catch (Exception $ex) {
sleep(1); //sleep for 5 seconds
$result = fwrite($fp, $msg, strlen($msg));
}
Googling I found some interest things
http://rt.openssl.org/Ticket/Display.html?id=598&user=guest&pass=guest
As the patch comment says
first check if there is a SSL3_BUFFER still being written
out. This will happen with non blocking IO
Answer of Why am I getting "error:1409F07F:SSL routines:SSL3_WRITE_PENDING: bad write retry" error while attempting an SSL_write? says:
SSL_Write returns with SSL_ERROR_WANT_WRITE or SSL_ERROR_WANT_READ, you have to repeat the call to SSL_write with the same parameters again, after the condition is satisfied.
Maybe the ssl buffer is still writing when you try to write, you can check if buffer is not writing, retry, or limiting the buffer could enough.
Duplicates:
Apple Push Notification - PHP - SSL operation failed with code 1
In PHP: OpenSSL Error messages: error: 1409F07F: SSL routines: SSL3_WRITE_PENDING: bad write retry
Additional (edit)
Above I try to say that you need to figure out a way to determine if socket is not writing when you try to write again and then write.
If not have a way to do it, try:
Disabling the non-blocking block
Rerty the write
while(!fwrite($fp, $msg)) {
usleep(400000); //400 msec
}
if is successful, just disable the erors via error_reporting never use # operator.
Setting stream_set_write_buffer() to 0
Related
My iOS app should support push notifications through APN and I have uninstalled and re-installed my app into my iPhone 6S three times which means that I have three different couples <device-id, device-token> in an apposite MySQL table of which only the last (the maximum id) is the most recent and thus the valid one. Then, I wrote this PHP script which I found here in StackOverflow which should return the invalid tokens to be deleted from the database:
<?php
$cert_file = './superbar-development.pem';
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', $cert_file);
$apns_connection = stream_socket_client('feedback.sandbox.push.apple.com:2196', $error_code, $error_message, 60, STREAM_CLIENT_CONNECT, $stream_context);
if($apns_connection === false) {
apns_close_connection($apns_connection);
error_log ("APNS Feedback Request Error: $error_code - $error_message", 0);
}
$feedback_tokens = array();
while(!feof($apns_connection)) {
$data = fread($apns_connection, 38);
if(strlen($data)) {
$feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
}
}
fclose($apns_connection);
print_r($feedback_tokens);
?>
But I always get and empty array which means that the first two couples <device-id, device-token> have not been detected as invalid yet. How much time does it take to do that? I am asking this because I have seen that connecting to and reading from feedback.sandbox.push.apple.com:2196 is very very slow so I wouldn't like to cron to do it every time it has to send push notifications. Do you know anything about this? Thank you.
I have created a client-server solution for APNS in PHP.
The "server" is a continously running script that waits for connections and copies the content to APNS. This means it keeps the connection to APNS open all the time. I use fwrite and stream context as such. This is the part that accepts incoming connections:
if ($client = #stream_socket_accept($this->socket, -1)) {
$this->writeLine("Received connection.");
$this->inputBuffer = stream_get_contents($client);
}
It runs in a while-loop, so that next iteration copies the contents of inputBuffer to APNS as such:
$writtenBytes = #fwrite($this->connectionAPNS, $this->inputBuffer, strlen($this->inputBuffer));
I then check for bytes written like:
if ($writtenBytes > 0) {
$this->writeLine("Copied $writtenBytes bytes to APNS.");
return true;
}
Now, the problem arises when the connection has been sitting idle for some time. The first push notification WILL return the number of bytes written to APNS - such as "Copied 157 bytes to APNS.", indicating that the fwrite works, but the message is not delivered.
The next notification then returns 0 bytes written, reconnects automatically (part of my script) and writes the bytes that failed - then it works.
Why do I get bytes written returned if the connection actually failed to write or if APNS did not accept them?
I use the enhanced notification format and I check for errors returned, which seem to be none.
I use this code for that, which I found on a blog, where I pass the connection handle to the function after writing to APNS:
function checkAppleErrorResponse($fp)
{
$apple_error_response = fread($fp, 6);
if ($apple_error_response) {
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 {
// Here is some fancy if/else error handling, but I never end in this else , so I excluded it.
}
}
}
The 'client' part that keeps the connection APNS open looks like this, and is called whenever writtenBytes returns 0 (error) or when the script first loads:
function connectToAPNS()
{
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certpath);
stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
// Open a connection to the APNS server
$error = "";
$errno = 0;
$this->connectionAPNS = stream_socket_client(
'ssl://gateway.push.apple.com:2195',
$errno,
$error,
60,
STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT,
$ctx
);
if ($this->connectionAPNS) {
stream_set_blocking($this->connectionAPNS, 0);
$this->writeLine("Connected to APNS!");
return true;
}
}
Is your app a release or a debug version? In each case you have to communicate with a different web-service and use different certificates.
In sandbox/debug mode communicate with ssl://gateway.sandbox.push.apple.com:2195
In production/relase mode communicate with ssl://gateway.push.apple.com:2195
The notification will be silently dropped, if you communicate with the wrong service.
I'm trying to send a huge amount of data using SSL/TLS connection in PHP. It works pretty well if the data chunk isn't very big or if I don't use TLS, but what I need (near 2MiB), the fwrite function shows the warning:
Warning: fwrite(): SSL operation failed with code 1. OpenSSL Error messages: error: 1409F07F: SSL routines: SSL3_WRITE_PENDING: bad write retry
The relevant code I'm using to connect clients:
$cntxt = stream_context_create(array('ssl' => array('local_cert' => 'certificate.pem')));
$server = stream_socket_server('tls://127.0.0.1:8080', $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $cntxt);
// Wait for client connection //
$client = stream_socket_accept($server);
// Use non-blocking socket to allow answering many clients at a time
stream_set_blocking($client, 0);
$clients[] = $client;
When sending data, it's append to a buffer and, from time to time, this function is called for each client and linked buffer:
function trySend($client, &$buffer) {
if (strlen($buffer)) {
$len = fwrite($client, $buffer);
$buffer = substr($buffer, $len);
}
}
As I said, my code works for small ammount of data or for normal (non-TLS) connections. I've searched for this error and found http://www.openssl.org/docs/ssl/SSL_write.html:
SSL_write() will only return with success, when the complete contents of buf of length num has been written. This default behaviour can be changed with the SSL_MODE_ENABLE_PARTIAL_WRITE option of SSL_CTX_set_mode(3). When this flag is set, SSL_write() will also return with success, when a partial write has been successfully completed. In this case the SSL_write() operation is considered completed. The bytes are sent and a new SSL_write() operation with a new buffer (with the already sent bytes removed) must be started. A partial write is performed with the size of a message block, which is 16kB for SSLv3/TLSv1.
But how can I do this in PHP?
Any help appreciated :)
I have found I can get around this problem by restricting the length of the string passed to fwrite() to 8192, which prevents fwrite() warning.
So for the code in your example I would try changing the substr() call to:
$buffer = substr($buffer, $len, 8192);
The solution is:
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
try {
$result = fwrite($fp, $msg, strlen($msg));
}
catch (Exception $ex) {
sleep(1); //sleep for 5 seconds
$result = fwrite($fp, $msg, strlen($msg));
}
I am working on a little project of mine and have built a UDP scraper that uses sockets to return data about a specific sha1 hash.
It works but is incredibly slow and wondered if any one knows how I could speed it up or improve the existing code.
The code is below;
// SCRAPE UDP
private function scrapeUDP($tracker, $hash) {
// GET TRACKER DETAILS
preg_match('%udp://([^:/]*)(?::([0-9]*))?(?:/)?%i', $tracker, $info);
// GENERATE TRANSACTION ID
$transID = mt_rand(0, 65535);
// PACKED TRANSACTION ID
$packedTransID = pack('N', $transID);
// ATTEMPT TO CREATE A SOCKET
if(!$socket = #fsockopen('udp://' . $info[1], $info[2], $errno, $errstr, 2)) {
return;
}
// SET STREAM TIMEOUT
stream_set_timeout($socket, 2);
// CONNECTION ID
$connID = "\x00\x00\x04\x17\x27\x10\x19\x80";
// BUILD CONNECTION REQUEST PACKET
$packet = $connID . pack('N', 0) . $packedTransID;
// SEND PACKET
fwrite($socket, $packet);
// CONNECTION RESPONSE
$response = fread($socket, 16);
// CHECK CONNECTION RESPONSE LENGTH
if(strlen($response) < 16) {
return;
}
// UNPACK CONNECTION RESPONSE
$returnData = unpack('Naction/NtransID', $response);
// CHECK CONNECTION RESPONSE DATA
if($returnData['action'] != 0 || $returnData['transID'] != $transID) {
return;
}
// GET CONNECTION ID
$connID = substr($response, 8, 8);
// BUILD SCRAPE PACKET
$packet = $connID . pack('N', 2) . $packedTransID . $hash;
// SEND SCRAPE PACKET
fwrite($socket, $packet);
// SCRAPE RESPONSE
$response = fread($socket, 20);
// CHECK SCRAPE RESPONSE LENGTH
if(strlen($response) < 20) {
return;
}
// UNPACK SCRAPE RESPONSE
$returnData = unpack('Naction/NtransID', $response);
// CHECK SCRAPE RESPONSE DATA
if($returnData['action'] != 2 || $returnData['transID'] != $transID) {
return;
}
// UNPACK SCRAPE INFORMATION
$returnData = unpack('Nseeders/Ncompleted/Nleechers', substr($response, 8, 12));
// RETURN TRACKER INFORMATION
return array('seeders' => $returnData['seeders'], 'leechers' => $returnData['leechers'],);
}
It is my first time I have ever created anything to do with sockets or UDP so forgive me if it is a mess!
Thanks...
You have to make parallel request using socket_select() and non-blocking sockets or forks, because you are spending a lot of time in waiting for the response. Additionally, it may be better to use low-level functions like socket_read() or similar to control connection and data transmission better.
Can someone clarify what the APNs (Apple Push Notification) wants as far as how you query it?
The docs say it starts sending as soon as the connection is made. Does this mean that I don't do an fread() on it?
Here's my current code to try and read it. I did NOT put the fread() in a loop as I do not know what response indicates "no more records to read" and I didn't want an infinite loop on my server.
<?php
$apnsCert = 'HOHRO-prod.pem';
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
stream_context_set_option($streamContext, 'ssl', 'verify_peer', false);
$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
echo 'error=' . $error;
echo 'errorString=' . $errorString;
$result = fread($apns, 38);
echo 'result=' . $result;
fclose($apns);
?>
So far all I am getting is a null reply. There are no errors so it is connecting.
I don't know if the null reply means no data is there, or my fread() is the wrong way to do it.
Thanks
Here's a big gotcha which confused me when I first tried connecting: the APNS feedback servers only return the device tokens that have "expired" since your last feedback request. Which means most of the time you'll get a NULL response unless you're already dealing with a high volume of users of your app.
So make sure you store the expired device tokens to disk or db, because after your feedback query they're gone for good. This makes testing a pain to say the least!
Here's a complete function to fetch the device tokens from the APNS feedback servers (many thanks to the answers above for helping me put it all together):
function send_feedback_request() {
//connect to the APNS feedback servers
//make sure you're using the right dev/production server & cert combo!
$stream_context = stream_context_create();
stream_context_set_option($stream_context, 'ssl', 'local_cert', '/path/to/my/cert.pem');
$apns = stream_socket_client('ssl://feedback.push.apple.com:2196', $errcode, $errstr, 60, STREAM_CLIENT_CONNECT, $stream_context);
if(!$apns) {
echo "ERROR $errcode: $errstr\n";
return;
}
$feedback_tokens = array();
//and read the data on the connection:
while(!feof($apns)) {
$data = fread($apns, 38);
if(strlen($data)) {
$feedback_tokens[] = unpack("N1timestamp/n1length/H*devtoken", $data);
}
}
fclose($apns);
return $feedback_tokens;
}
If all is well, the return values from this function will look something like this (via print_r()):
Array
(
Array
(
[timestamp] => 1266604759
[length] => 32
[devtoken] => abc1234..............etcetc
),
Array
(
[timestamp] => 1266604922
[length] => 32
[devtoken] => def56789..............etcetc
),
)
That code looks right however you need to loop and check for end of stream in order to read all the device codes.
while (!feof($apns)) {
$devcon = fread($apns, 38);
}
However my problem is the actual unpacking of the data. Does anyone know how to unpack the binary data which you've just read to get the actual device ID (as string) along with the timestamp etc?
I got the solution from apple forum and it is for development. Try this for production also.
"Well, as dumb as it sounds, I found a solution:
Create a dummy app id in the program portal, enable development push notifications on it
Create and download the associated provisioning profile
Create a new xcode project, and invoke the registerForRemoteNotificationTypes method on start.
Install the dummy app on your device. At this point, you should have two DEVELOPMENT apps running on your device: the original app and the dummy app. Both should be registered to receive push notifications.
Uninstall the original app, and try to send a push notification to that app.
Invoke the feedback service, and you should receive data back."
This finally worked for me.
$arr = unpack("H*", $devconts);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
And then you simply check for the device token against the timestamp!
Just started using this library - works great for me!
https://github.com/mac-cain13/notificato