首先说明一下,本文只是介绍一些容易被开发者忽视,而导致性能低下问题。并不是介绍如何向苹果设备成功发送一条消息,这里假设所有阅读者已经能够向苹果服务器发送消息,并且成功接收,只是发送效率比较低,并且丢失率很高。如果你不是此类情况,那么绕道吧。PS:伸手党可以直接看标红部分(结论)
最近参与并且完成了公司1000W级的消息推送服务平台重建。此次重构级别解决了消息丢失,并且大幅度提升了推送效率。有些东西我想很多开发者也会碰到,并且难以被开发者所意识到。
先先扫下盲哈。如果你发送消息是一次连接发送一条,那么请你先改成长连接发送--一次连接发送多条数据。粘下PHP代码吧:)
$pass = ''; // $pass是你在建立证书的时候输入的密码
$ctx = stream_context_create();
// apns.pem就是你的证书的路径了,最好写绝对路径
stream_context_set_option($ctx, 'ssl', 'local_cert', 'apns.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if(!$fp) {
print "Failed to connect $err $errstr";
exit();
} else {
print "Connection OK\n";
}
$body = array('aps' => array('badge' => 1));
for($i = 0; $i <= 10000; $i++) {
$deviceToken = md5(time() . rand(0, 9999999)) . md5(time() . rand(0, 9999999)); // 模拟一个Device Token
$body['aps']['alert'] = md5(time() . rand(0, 9999999)); // 随便模拟点数据
$payload = json_encode($body);
// 这里是简单的消息结构,如果想多发几个但是不要返回错误,可以用这个
/*
$msg = chr(0) . pack("n", 32)
. pack('H*', str_replace(' ', '', $deviceToken))
. pack("n", strlen($payload)) . $payload;
*/
// 这个是增强型消息格式,$i就是Identifier,864000就是Expiry了
$msg = pack('CNNnH*', self::COMMAND_PUSH, $i, 864000, 32, $deviceToken)
. pack('n', strlen($payload))
. $payload;
print "sending message :" . $payload . "\n";
fwrite($fp, $msg);
// 这里是读取错误信息,不要没发一条就读取一次,这样苹果会认为攻击而终止连接
//fread($fp, 6);
}
要往下面说我先解释一下这个东东--Broken Pipe,如果你有过大量的数据推送,并且看下你的错误日志那么Writen Broken Pipe你一定不陌生。这个错误产生的原因通常是当管道读端没有在读,而管道的写端继续有线程在写,就会造成管道中断。可以简单的理解为你在向一个已经关闭的连接写数据就会抛出这个错误。
由于Broken Pipe的关系,我们不得不重新和苹果服务器建立连接,这个连接耗时在国内.....(你们懂的3sec+),这个应该是我们推送速度最大的瓶颈了。有很多开发者也许会认为这个是由于国内的网络环境导致,因为他们习惯的“traceroute gateway.push.apple.com”一下,然后发现30+的路由跳转然后就会说这个断开是无法避免的。如果你这么想那么你就错了。
我们用大量(10W左右)能保证基本正确的Device Token来做测试,平均一次连接的能写入3W左右的数据,好的情况下能一次写完这10W数据!!!这个测试也就证明平凡出现Broken Pipe不是由于网络原因。既然不是由于网络原因,那么我做个大胆的假设:这个连接是由APNs主动断开的。
那么假设这个猜想是正确的,那苹果什么时候会断开连接了?解释这个问题,我们又做了一个测试:往这10W的Device Token里面均匀插入1000个错误的Device Token。神奇的事情发生了,发送期间平均断开连接900次+。这个实验正好验证了我之前的猜测:产生Broken Pipe是因为APNs服务器主动断开了连接,并且是由于错误的Device Token引起的(或者其他的错误)。苹果的错误类型和代码编号: