今天看啥
热点:

这段时间一直在测试IOS消息推送接口,其中发现了不少问题,归结起来还是对推送流程不是很明朗造成了,今天总结一下推送消息流程。

流程步骤:

1、获取device tokens信息。

这里获取device tokens,考虑到总体的数据量不大,也就几百条吧,所以一次性从数据库中把所有的token信息取出来,再对这些信息进行分组。

注意:其实一次性获取所有token信息后进行分组的做法不是很好,应该是直接分批从数据库中获取,而不是获取后再分组。

由于苹果服务器接收推送消息一次只可以接收7000 字节,每次发送消息又必须少于256 字节,这也是为什么要分批次推送的原因。

2、链接APPLE PUSH SSH

3、分配循环对每个device token进行消息推送

这里有几点需要注意一下:

1)、每次发送消息必须小于256字节。

2)、对每个设备进行fwrite操作时,成功返回写入内容的字节数,失败返回false。根据这个返回值,如果本次推送失败,则关闭ssh链接后重新链接ssh,再次对该device进行推送。

对于关闭ssh链接后再次链接ssh,这里可以适当sleep()一定时间后再次链接,避免频繁对苹果服务器进行链接操作被认为是恶意攻击。

3)、每一批推送完成后,断开链接,休眠N秒,重新链接,进行下一批推送。

……

4、是否通过feedback服务更新本地数据库中的device token信息?

通过feedback服务可以获取用户是否已经卸载APP信息,如果用户卸载了APP运用,返回该设备的token信息,反之为空。

这里需要注意的是,通过feedback获取信息前必须对该设备进行push一次,才能有信息返回,所以可以把这个更新本地device token信息的过程添加到每一次推送信息完成后,获取该token信息是否有效,也就是用户是否卸载APP运用。

以上就是自己给IOS设备推送消息的流程,懒得画流程图了。第一次弄这个ISO消息推送,遇到了不少问题,总算可以进行批量推送了…但问题还是有的,有待完善。

 

这段时间一直在总结IOS消息推送的东西,前面也写了些自己在处理这个需求时遇到的问题已经解决方法。



 

下面贴上自己的代码:

 

<?php
error_reporting(0);
set_time_limit(0);
 
class push
{
	private $message = '';
	private $article_id = '';
 
	private $group_size = 15;
 
	//证书
	private $certificate = '';
 
	//密码
	private $passphrase = '';
 
	//PUSH地址
	//private $push_url = 'ssl://gateway.push.apple.com:2195';
	private $push_url = 'ssl://gateway.sandbox.push.apple.com:2195';
 
	//feedback地址
	//private $feedback_url = 'ssl://feedback.push.apple.com:2196';
	private $feedback_url = 'ssl://feedback.sandbox.push.apple.com:2196';
 
	private $push_ssl = null;
	private $feedback_ssl = null;
 
	//是否获取feedback信息
	private $get_feedback_info = null;
 
	public function __construct($message, $article_id)
	{
		$this->message = $message;
		$this->article_id = $article_id;
 
		$day = date('d', time());
		if($day % 9 == 0)
		{
			$this->get_feedback_info = true;
		}
		else
		{
			$this->get_feedback_info = false;
		}
	}
 
	public function push_message($tokens)
	{
		$this->open_push_ssl();
 
		$payload = $this->create_payload();
 
		//对device tokens信息进行分组
		$group_tokens = array_chunk($tokens, $this->group_size, true);
		$group_num = count($group_tokens);
		$mark = 0;
 
		$success_tokens = array();
		$feedback_tokens = array();
 
		foreach($group_tokens as $token)
		{
			$mark++;
			foreach($token as $value)
			{
				$msg = chr(0) . pack('n', 32) . pack('H*', $value['device_token']) . pack('n', strlen($payload)) . $payload;
				$result = fwrite($this->push_ssl, $msg, strlen($msg));
 
				if(!$result)
				{
					$this->close_push_ssl();
					sleep(1);
					$this->open_push_ssl();
				}
				else
				{
					$success_tokens[] = $value['device_token'];
 
					if($this->get_feedback_info)
					{
						if($this->feedback_info())
						{
							$feedback_tokens[] = $this->feedback_info();
						}
					}
				}
			}
 
			if($mark < $group_num)
			{
				$this->close_push_ssl();
				sleep(5);
				$this->open_push_ssl();
			}
		}
 
		$this->close_feedback_ssl();
		$this->close_push_ssl();
	}
 
	//链接push ssl
	private function open_push_ssl()
	{
		$ctx = stream_context_create();
		stream_context_set_option($ctx, 'ssl', 'allow_self_signed', true);
		stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
		stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certificate);
		stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
 
		$this->push_ssl = stream_socket_client($this->push_url, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
 
		if(!$this->push_ssl)
		{
			echo "Failed to connect Apple Push Server {$err} {$errstr}! Please try again later.<br/>";
			exit();
		}
	}
 
	private function close_push_ssl()
	{
		fclose($this->push_ssl);
	}
 
	//根据实际情况,生成相应的推送信息,这里需要注意一下每条信息的长度最大为256字节
	private function create_payload($message, $article_id)
	{
		$body = array();
		$body['aps'] = array(
							'alert' => $this->message,
							'badge' => 1,
							'sound' => 'default',
							'activityId' => $this->article_id
		);
		return json_encode($body);
	}
 
	private function open_feedback_ssl()
	{
		$ctx = stream_context_create();
		stream_context_set_option($ctx, 'ssl', 'allow_self_signed', true);
		stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
		stream_context_set_option($ctx, 'ssl', 'local_cert', $this->certificate);
		stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
 
		$this->feedback_ssl = stream_socket_client($this->feedback_url, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
 
		if(!$this->feedback_ssl)
		{
			echo "Failed to connect Apple Feedback Server {$err} {$errstr}! Please try again later.<br/>";
			exit();
		}
	}
 
	private function close_feedback_ssl()
	{
		fclose($this->feedback_ssl);
	}
 
	private function feedback_info()
	{
		$this->open_feedback_ssl();
 
		while($devcon = fread($this->feedback_ssl, 38))
		{
			$arr = unpack("H*", $devcon);
			$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);
		}
 
		if(is_null($feedbackDeviceToken))
		{
			return $feedbackDeviceToken;
		}
		else
		{
			return false;
		}
	}
}


需要注意的几点:

1、推送消息的长度限制:每次发送消息又必须少于256 字节,苹果服务器接收推送消息一次只可以接收7000 字节。这里需要对推送的消息做些长度限制。

2、获取本地数据库device tokens信息:这里是一次性获取所有的device tokens信息,然后再进行分组推送,如果数据量大的话不建议这样做,建议直接分批次从数据库中获取数据。其实这个获取device tokens信息的方式,不知道是不是可以通过redis队列来完成,最近在看redis方面的东西,对redis还不是很了解。

3、feedback服务:要获取feedback信息,必须先进行push一次,才可以获取该设备的相关信息,如果用户已经删除APP运用,则返回设备token信息,否则为空。获取这些无用的device tokens信息后进行对本地数据库中的tokens进行更新,保证数据库存储的都是有用的数据。上面代码没有体现这个操作。
 

评论暂时关闭