向APNS发送数千台设备PHP花费太多时间


Sending APNS thousand of devices php taking too much time

我正在循环向PHP中的多个设备发送APNS。

while($row = mysql_fetch_array($result))
  {
    $row['devicetoken'];
    $row['devcertificate'];
    $row['prodcertificate'];
    if($devprod=="dev"){
        $apnsserverurl="ssl://gateway.sandbox.push.apple.com:2195";
        $certificatename=$appname."".$row['devcertificate'];
    }
    elseif($devprod=="prod"){
        $apnsserverurl="ssl://gateway.push.apple.com:2195";
        $certificatename=$appname."".$row['prodcertificate'];
    }
    sendpush($row['devicetoken'],$certificatename,$apnsserverurl);
  }

这是发送推送功能:

function sendpush($deviceToken,$certificatename,$apnsserverurl){
// Get the parameters from http get or from command line
$message = utf8_encode(urldecode($_POST['message']));
$badge = 0;
$sound = "";
// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;
/* End of Configurable Items */

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $certificatename);
$fp = stream_socket_client($apnsserverurl, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
    //print "Failed to connect $err";
    return;
}
else {
    //print "Connection OK";
}
$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "n";
fwrite($fp, $msg);
fclose($fp);   
}

面临的问题是它花费了太多时间。如何优化代码?


我终于使用了 https://code.google.com/p/apns-php/,它只建立 1 个连接并将消息排队

APNs 最佳实践要求您保持与其服务器的连接处于打开状态:

您可以与同一网关或多个网关实例建立多个连接。如果您需要发送大量推送通知,请将它们分散到多个不同网关的连接中。与使用单个连接相比,这可以提高性能:它可以让您更快地发送推送通知,并让 APNs 更快地交付推送通知。

通过多个通知保持与 APNs 的连接处于打开状态;不要重复打开和关闭连接。APNs 将快速连接和断开连接视为拒绝服务攻击。您应该使连接保持打开状态,除非您知道它将在较长时间内处于空闲状态,例如,如果您每天只向用户发送一次通知,则可以每天使用新连接。

Apple 可能会将您的重复连接视为 DoS 攻击并限制处理。

hylander0 提出了一个有效的观点。否则,您的代码看起来正常。您遇到的唯一问题是您要为发送的每次推送重新创建 SSL 连接。即使Apple不阻止您,仅该过程也需要时间。

stream_context_create(( 代码移到 sendPush 函数之外,让它成为全局代码,或者通过引用传递给循环中的 sendPush 函数(基本上应该只使用 fwrite((。这将打开与Apple的一个连接,并在可能不到一秒钟的时间内发送所有推送通知。

请注意,如果

有效负载或推送令牌无效,则连接将被切断,因此,如果 fwrite 失败,请务必在继续循环之前重新创建连接。

是的,stream_socket_client(( 应该在循环的一侧。只保持 fwrite(( 在循环中。

一种使用 php 发送数千甚至数百万个 APNS 的简单方法(在 PHP 7 和 HTTP2 procotol 上测试(:

$data = array("aps" => $contents); // $contents if your data like badge, alert, etc
$payload = json_encode($data);
        
$header = ["alg" => "ES256", "kid" => "YOUAPIKEY"];
$header = base64_encode(json_encode($header));
$claim = ["iss" => $teamId, "iat" => time()];
$claim = base64_encode(json_encode($claim));
$token = $header.".".$claim;
$pkey  = file_get_contents("YOUKEY.p8");
$signature = "";
openssl_sign($token, $signature, $pkey, 'sha256');
$sign = base64_encode($signature);
$jws = $token.".".$sign;
    // headers
$headers = array(
        "apns-topic: "."YOUBUNDLEID",
        'Authorization: bearer ' . $jws,
        "apns-priority: 10",
        "apns-push-type: alert"
);
$devices = []; // YOUR array of tokens
$mh = curl_multi_init();
if (defined('CURLMOPT_MAX_HOST_CONNECTIONS')) {
   curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 5);
}
curl_multi_setopt($mh, CURLMOPT_PIPELINING, 2);
foreach($devices as $token)
{
    // open connection 
    $http2ch = curl_init();
    // cleanup device tokens
    $token = str_replace(' ', '', trim($token, '<> '));
    // url (endpoint)
    $url = $http2_server."/3/device/".$token;
    // other curl options
    curl_setopt_array($http2ch, array(
        CURLOPT_URL => $url,
        CURLOPT_PORT => 443,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POST => TRUE,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_RETURNTRANSFER => TRUE,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_HEADER => 1,
        CURLOPT_HTTP_VERSION => 3,
    ));
    curl_multi_add_handle($mh,$http2ch);
}
$still_running = true;
do {
    while (($curlCode = curl_multi_exec($mh, $still_running)) == CURLM_CALL_MULTI_PERFORM) {
      curl_multi_select($mh);
    }
      if ($curlCode != CURLM_OK) {
          break;
      }
      while ($res = curl_multi_info_read($mh)) {
          $handle = $done['handle'];
          $statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
          $datas = curl_getinfo($handle);
          // retrieve data like token to clean DB for example if error
          curl_multi_remove_handle($mh, $handle);
    }
} while ($still_running);
curl_multi_close($mh);