PHP技术查询APNs反馈服务器

30

有人可以澄清一下APNs(苹果推送通知)在查询方面的要求吗?

文档中说,它在连接建立后立即开始发送。这是否意味着我不需要对其进行fread()操作?

这是我当前尝试读取它的代码。由于我不知道哪个响应表示“没有更多记录可读取”,也不想让我的服务器进入无限循环,因此我没有将fread()放入循环中。

<?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);
?>

目前我得到的只是一个空回复。没有错误,所以已经连接上了。

我不知道空回复是否意味着没有数据,或者我的fread()方法不正确。

谢谢


即使我有类似的代码,我也从fread得到了null。不确定我的代码是否真正有效,APNS是否发送了一个NULL回复或者只是缺少了一些东西。我确信我的连接到APNS是成功的。我已经在5-10个设备上安装了我的应用程序,并发送了几个警报,然后卸载了其中的一些,并重新开始发送警报,以便APNS反馈服务器会让我们知道它未能在某些设备上交付。除了空白的fread()之外,我什么都没有收到 :-( 如果您做了一些不同的事情并且使其工作,请告诉我。 - Anish
你用过"pushutil"吗?一旦你搞清楚了,它是检查反馈服务器的更快速的方法。这是一个Mac OS X实用程序,您需要编译它,然后从Unix命令行运行。搜索Erica Sadun - 它在她的Push页面下面。 - Johnny
即使我发送了76个推送通知,仍然无法从反馈服务器获得任何响应 - 其中一些必须是不成功的。当我使用Erica Sadun的“pushutil”命令行实用程序时,同样的事情发生 - 推送正常进行,然后我删除应用并再次推送,但仍然没有任何反馈出现在反馈服务器上。而且这是使用Erica的应用程序,所以我知道它必须有效。我认为问题可能是证书。我正在使用与推送相同的推送生产证书。难道没有单独的反馈证书吗? - Johnny
今天我发现fread()无法工作,因为APNs反馈会发送垃圾数据直到实际反馈。这会导致fread失败,因为它正在读取空数据。正如下面gw1921所提到的,你必须在feof()上循环直到strlen(fread($apns, 38))。从那时起,你将拥有有效的反馈。然后,按照Nick B的建议解包数据。(另一个令人讨厌的问题:未安装的应用程序不会生成反馈,除非您在设备上有另一个具有相同aps-environment的应用程序。因此,您需要在设备上拥有2个启用推送的沙盒应用程序才能使未安装的应用程序填充反馈。) - brack
5个回答

69

当我第一次尝试连接时,有一个很大的陷阱使我感到困惑:APNS反馈服务器仅返回自你上次反馈请求以来"过期"的设备令牌。这意味着大多数情况下,除非你已经处理了大量用户使用你的应用程序,否则你将得到一个NULL响应。

因此,请确保将过期的设备令牌存储到磁盘或数据库中,因为在反馈查询之后它们将永久消失。这至少让测试变得痛苦!

下面是一个完整的函数,用于从APNS反馈服务器获取设备令牌(非常感谢上面的答案帮助我将其全部组合起来):

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;
}

如果一切正常,这个函数的返回值将类似于以下内容(通过print_r()输出):

Array
(
    Array
    (
        [timestamp] => 1266604759
        [length] => 32
        [devtoken] => abc1234..............etcetc
    ),
    Array
    (
        [timestamp] => 1266604922
        [length] => 32
        [devtoken] => def56789..............etcetc
    ),
)

1
在什么情况下我可能不会从反馈服务器收到响应?我的通知正常传递(从日志中可以看出),但由于某种原因我从未在设备上收到任何通知! - lostInTransit
1
APNS反馈服务器仅返回自上次反馈请求以来已“过期”的设备标记。哎呀,我想我应该阅读文档;( - Daniel Magnusson
我能否不检查时间戳,而是检查所有返回的令牌,然后从我的数据库中删除它们? - Andy Ibanez
当然。这就是我们在实现中所做的 - 时间戳数据对我们没有价值,因此我们忽略它。 - Nick Baicoianu
是的,我们仍然在我们的生产应用程序中使用这段代码。它作为每小时的 cron 作业运行,与我们的主要 APNS 连接分开。 - Nick Baicoianu
显示剩余5条评论

2

那段代码看起来没问题,但你需要循环并检查流的结束以便读取所有设备码。

 while (!feof($apns)) {
        $devcon = fread($apns, 38);
 }

然而,我的问题在于实际的数据解包。有人知道如何解包二进制数据以获取实际设备ID(作为字符串)以及时间戳等吗?


1
我认为这是正确的想法 -$array = unpack("NnH32", $result); $feedbackTime = $row[0]; $feedbackLen = $row[1]; $feedbackUDID = $row[2];这将解包反馈服务器发送的38个字节。然而,32位日期值采用网络顺序或大端序。如果有人能提供一个PHP函数来翻转这4个字节以符合Intel(小端序)顺序,我认为我们就有了解决方案。注意:实际的UDID是一个字符字符串,不需要翻转其顺序。 - Johnny
这个怎么样? /* 将 HostOrder 中的浮点数转换为 Network Order */ function FToN( $val ) { $a = unpack("I",pack( "f",$val )); return pack("N",$a[1] ); }/* 将 Network Order 中的浮点数转换为 HostOrder */ function NToF($val ) { $a = unpack("N",$val); $b = unpack("f",pack( "I",$a[1])); return $b[1]; } - strange

1

我从苹果论坛上找到了解决方案,这是用于开发的。也可以尝试用于生产环境。

"好吧,听起来很蠢,但我找到了一个解决方案:

在程序门户中创建一个虚拟应用程序ID,并为其启用开发推送通知 创建并下载相关联的配置文件 创建一个新的Xcode项目,并在启动时调用registerForRemoteNotificationTypes方法。 将虚拟应用程序安装到您的设备上。此时,您的设备应该运行两个开发应用程序:原始应用程序和虚拟应用程序。两者都应注册接收推送通知。 卸载原始应用程序,并尝试向该应用程序发送推送通知。 调用反馈服务,你应该会收到数据。"


0

这最终对我起作用了。

$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);

然后,您只需根据时间戳检查设备令牌即可!

这个很好用,gw1921。目前我正在将$feedbackDate存储在SQL列中。这个列应该是什么数据类型?我设置它为整数,但这给了我“2009”。另外两个列,长度和标记,运行得非常好!!谢谢。 - Johnny

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接