PHP的APNS消息推送

最近在给公司的APP项目做后台,其中要实现的一个功能是给IOS设备推送通知,由于网上资料不是很健全,做这一块的时候走了蛮多的弯路的,记录下,有人看到的话,可以借鉴下,希望能帮你节省一些时间...


关于APNS相关的原理性东西,可以参考这里,上面的代码也可以走通的.


之后对大批量用户发送的时候,出现了有些用户可以收到而有些用户不能收到的问题,由于发送消息之后苹果服务器没有返回值,所以一下子也没办法知道是哪里出了问题..

经过查阅,得知还有一种加强版(enhanced)的消息格式存在,如果使用这种格式,苹果服务器会将错误的相关信息返回给你(APNs returns a packet that associates an error code with the identifier).

代码如下:

$msg = pack("C", 1) . pack("N", $i) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $devicetoken)) . pack("n", strlen($payload)) . $payload;

每个字段的含义可以参考这里;

这样,通过checkAppleErrorResponse函数(参考这里),就能知道结果是否发送成功了.



刚解决了一个问题,另外的又来了..当我模拟对5000个devicetoken发送消息的时候,它出现了报错: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry.  因为我对SSL操作不是很熟悉,就直接去谷歌上找解决办法了..发现原因很简单,当你在写的过程中发生错误时,你必须要使用相同的参数再次调用fwrite方法,不然(使用不同的参数,如没有进行重发,直接对下一个devicetoken推消息),则会导致1409F07F错误.. 参考这里...

注: 后来在IOS开发手册里发现,写的过程中发生错误有两个表现:

1.fwrite函数的返回值为0 - 表示连接关闭;

2.如果返回值非0,则需要通过checkAppleErrorResponse函数查看原因

(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.)



然后,我又发现,当发送完2000个左右以后,程序又会报错: fwrite() expects parameter1 to be resource, boolean given in xxx.  原因很简单,之前和apns的连接断开了.

查看了IOS开发文档,发现里面有记录: 如果发送数量超过500的时候,连接就可能断开, 而当发送大概1700个的时候,写操作会失败,因为管道满了(这里我不是很懂什么意思).(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).     参考这里

这里的解决办法: 每执行完100个推送之后重新连接一次苹果的消息推送服务器.



除此之外还碰到的一些错误基本上都在stackoverflow上能找到答案,如果还有问题,可以留言.

附上代码,用的是CI框架.

load->model('init_mongo');
		$this->mongo = $this->init_mongo->get_resouce('mongodb_default');   //连接数据库
	}
	


	function send(){
		$fp = $this->create_apns_link();
		
		$body['aps'] = array(
			'alert' => 'apns test~' , 
			'sound' => 'default',
			'badge' => 1
		);
		$apple_expiry = time()+86400;
		$payload = json_encode($body);
		
		$devicetoken = $this->mongo->devicetoken->find();
		$i = 1;
		
		//推送开始
		foreach ($devicetoken as $v){
			echo '
' . $i . 'begin:'.'
'; $msg = pack("C", 1) . pack("N", $i) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $v['_id'])) . pack("n", strlen($payload)) . $payload; if( !fwrite($fp, $msg) || $this->checkAppleErrorResponse($fp) ){ //如果失败,则进行重发,连续3次还是失败,则重新建立连接,并对下一个devicetoken进行推送 $times = 1; while($times <= 3){ if(!fwrite($fp, $msg) || $this->checkAppleErrorResponse($fp)){ $times ++ ; }else{ $fp = $this->create_apns_link(); break; } } } //每隔100次重新和APNS服务器建立连接. if( $i%100 == 0 ){ $fp = $this->create_apns_link(); } $i++; } usleep(500000); $this->checkAppleErrorResponse($fp); echo 'Completed'; fclose($fp); } private function create_apns_link(){ $passphrase = '12345678'; $filename = 'product_ck.pem'; $link = 'ssl://gateway.push.apple.com:2195'; $scc = stream_context_create(); stream_context_set_option($scc, 'ssl', 'local_cert', realpath($filename)); stream_context_set_option($scc, 'ssl', 'passphrase', $passphrase); $fp = stream_socket_client($link, $err,$errstr, 60, STREAM_CLIENT_CONNECT, $scc); $i = 0; while (gettype($fp) != 'resource'){ echo 'rebuild
'; if($i < 3){ usleep(500000); $fp = stream_socket_client($link, $err,$errstr, 60, STREAM_CLIENT_CONNECT, $scc); }else{ break; } } stream_set_blocking ($fp, 0); if($fp){ echo 'Connected to APNS for Push Notification' . '
'; } return $fp; } 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 '
+ + + + + + ERROR Response Command:' . $error_response['command'] . '   Identifier:' . $error_response['identifier'] . '   Status:' . $error_response['status_code'] . '
'; 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.
'; return true; } return false; } }


你可能感兴趣的:(PHP的APNS消息推送)