I am implementing a Push Notification server for our iPhone application.
I am using Symfony web framework to build my backend system for my iPhone application.
The way I have built the push notification server is:
1) Make a socket stream connection to Apple's PNS.
2) Start an infinite while loop
3) Inside the while loop, look for any new notifications in the SQL database
4) If there are new push notification entities in my SQL database, get them all and send them out
Below is my PHP code:
// ----------------------------------------------------------
// Opens a connection to Apple's Push Notification server
// ----------------------------------------------------------
public function pnsConnect()
{
// push notification pem certificate file
//$this->apnscert_dev = $this->container->getParameter('apnscert_dev');
//$this->apnshost_dev = $this->container->getParameter('apnshost_dev');
$this->apnscert_dev = 'cert_and_key_dev.pem';
$this->apnshost_dev = 'gateway.sandbox.push.apple.com';
$this->apnsport = 2195; //$this->container->getParameter('apnsport');
$pempath_dev = __DIR__."/../Resources/config/".$this->apnscert_dev;
echo 'pem path = '.$pempath_dev.'<br />';
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $pempath_dev);
// push notification server connection object
$this->apns = stream_socket_client('ssl://' . $this->apnshost_dev . ':' . $this->apnsport, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
error_log(date('Y-m-d H:i:s')." - Successfully connected to APNS", 3, 'PushLog.log'); // log for successful connection to help with debugging
}
// ---------------------------------------------------------
// Sends a push notification to multiple targeted tokens
// i.e. a group of users
//
// pnsSendToTokenGroup() only sends to a group, not a list
// of tokens specifically selected
// ---------------------------------------------------------
public function pnsSendToMultiple($paramArrTokens, $paramMessage)
{
if(!$paramMessage || !$paramArrTokens)
{
return new Response('Missing input parameters');
}
$badge = 1;
$sound = 'default';
$development = true;
$payload = array();
$payload['aps'] = array('alert' => $paramMessage, 'badge' => intval($badge), 'sound' => $sound);
$payload = json_encode($payload);
//echo 'message = '.$paramMessage.'<br />';
//echo '<br />Received '.count($paramArrTokens).' tokens<br />';
foreach($paramArrTokens as $paramToken)
{
//echo 'current token = '.$paramToken.'<br />';
$apns_message = chr(0).chr(0).chr(32).pack('H*', str_replace(' ', '', $paramToken)).chr(0).chr(strlen($payload)).$payload;
fwrite($this->apns, $apns_message);
$apns_message = null;
//$paramToken = null;
}
$badge = null;
$sound = null;
$development = null;
$payload = null;
//$paramArrTokens = null;
//$paramMessage = null;
}
// ---------------------------------------------------------
// Keeps this PNS server alive to maintain the connection
// to Apple's PNS server. This process will continually
// check the database for any new notification and push
// the notificaiton to who ever we need to.
// ---------------------------------------------------------
public function pnsKeepAlive()
{
// prevents time out
ini_set('max_input_time', 0);
ini_set('max_execution_time', 0);
$this->pnsDisconnect();
$this->pnsConnect();
// circular reference collector
gc_enable();
// start infinite loop to keep monitoring for new notifications
while(true)
{
echo 'checking database for new notifications ...<br />';
set_time_limit(0);
gc_collect_cycles();
$today = new DateTime('today');
$now = new DateTime();
$query = $this->em->createQuery('SELECT n FROM MyAppWebServiceBundle:Notification n WHERE n.sent = :paramSent and n.notificationdate >= :paramDate and n.notificationdate <= :paramNow')
->setParameter('paramSent', false)
->setParameter('paramDate', $today)
->setParameter('paramNow', $now);
$notifications = $query->getResult();
$today = null;
$now = null;
//echo 'number of unsent notifications found for today = '.count($notifications).'<br />';
// for each notification, combine all tokens into a single array
// to be looped through and sent into notification processing queue
foreach($notifications as $notification)
{
$this->pushNotification($notification);
}
$this->em->detach($query);
$notifications = null;
$query = null;
}
$this->pnsDisconnect();
}
// ---------------------------------------------------------
// Finds all tokens attached to Notification
// and construct an array of all unique tokens to be
// sent out to all receivers of the notification
// ---------------------------------------------------------
public function pushNotification($notification)
{
// initialise an empty array
$arrAllTokens = array();
// add all raw tokens to final array first
foreach($notification->getTokens() as $token)
{
// only add active tokens
if($token->getActive() == true)
{
$arrAllTokens[] = $token->getToken();
}
}
// for each token group add all
// tokens in each group to final array
foreach($notification->getTokenGroups() as $tokenGroup)
{
foreach($tokenGroup->getTokens() as $token)
{
// only add active tokens
if($token->getActive() == true)
{
$arrAllTokens[] = $token->getToken();
}
}
}
$arrAllTokens = array_unique($arrAllTokens);
$this->pnsSendToMultiple($arrAllTokens, $notification->getMessage());
$notification->setSent(true);
$this->em->flush();
$this->em->detach($notification);
$arrTokens = null;
$arrAllTokens = null;
$notification = null;
}
Apple have stated that a Push Provider should maintain connection to Apple's Push Server and not frequently disconnection and reconnect, otherwise they treat it as a denial of service attack and block us.
That is why I have an infinite while loop so the script doesn't end to allow me to maintain the constant connection to Apple's push notification socket stream.
At the moment, my push notification server works on my local machine (my pnsKeepAlive() infinite while loop goes on forever) but when I deploy my code to the production server, my pnsKeepAlive() (see above code) does not go on forever.
My production server is a shared hosting on Linode. It is a LAMP server running Apache and Debian.
I have heard PHP isn't designed to be doing these kind of jobs.
So my questions whether there are other language that is designed for these kind of thing (maintaining a persistent connection for a push notification server).
I have looked into Gearman but was also told by others Gearman isn't really what I need.