首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 實作 iOS 推播服務端(APNs)完整教學

     編輯:PHP 相關知識

前置準備

  1. Device Token:iOS App 向 APNs 註冊後取得的唯一裝置識別碼(64 字元十六進位字串)
  2. 憑證檔:.pem 格式(含客戶端憑證 + 私鑰),由 Apple Developer 後台下載 .cer 再轉換
  3. Passphrase:匯出 .p12 時設定的密碼(若無則為空字串)

憑證轉換指令(macOS/Linux)

# 1. 下載 aps_development.cer (測試) 或 aps_production.cer (正式)
# 2. 雙擊安裝至鑰匙圈
# 3. 鑰匙圈 → 登入 → 憑證 → 右鍵匯出 .p12
# 4. 轉換為 PEM
openssl pkcs12 -in aps_development.p12 -out aps_development.pem -nodes -clcerts
# 輸入匯出密碼,產出 aps_development.pem

PHP 推播核心程式碼

<?php
/**
 * APNs 推播發送類
 */
class ApnsPush {
    private $pemPath;
    private $passphrase;
    private $gateway;
    private $fp;

    public function __construct($pemPath, $passphrase = '', $isSandbox = true) {
        $this->pemPath = $pemPath;
        $this->passphrase = $passphrase;
        $this->gateway = $isSandbox 
            ? 'ssl://gateway.sandbox.push.apple.com:2195' 
            : 'ssl://gateway.push.apple.com:2195';
    }

    public function connect() {
        $ctx = stream_context_create();
        stream_context_set_option($ctx, 'ssl', 'local_cert', $this->pemPath);
        stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
        stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
        stream_context_set_option($ctx, 'ssl', 'verify_peer_name', false);

        $this->fp = stream_socket_client(
            $this->gateway, $err, $errstr, 60, 
            STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx
        );

        if (!$this->fp) {
            throw new Exception("APNs 連線失敗: $err $errstr");
        }
        return true;
    }

    public function push($deviceToken, $alert, $badge = 0, $sound = 'default', $custom = []) {
        if (!$this->fp) $this->connect();

        // 建構 Payload
        $payload = ['aps' => ['alert' => $alert]];
        if ($badge) $payload['aps']['badge'] = (int)$badge;
        if ($sound) $payload['aps']['sound'] = $sound;
        if ($custom) $payload = array_merge($payload, $custom);

        $json = json_encode($payload);
        if (strlen($json) > 4096) {  // APNs 限制 4KB
            throw new Exception('Payload 過大 (> 4KB)');
        }

        // 封裝 Binary 格式 (APNs Protocol v0)
        // Command(1) + TokenLength(2) + Token(32) + PayloadLength(2) + Payload
        $token = pack('H*', str_replace(' ', '', $deviceToken));
        $msg = chr(0) . pack('n', 32) . $token . pack('n', strlen($json)) . $json;

        $written = fwrite($this->fp, $msg);
        if ($written === false) {
            throw new Exception('寫入失敗,連線可能已斷開');
        }
        return true;
    }

    public function close() {
        if ($this->fp) fclose($this->fp);
    }
}

// --- 使用範例 ---
$deviceToken = '6ad7b13fb05e6137a46a60ea421e50164b701671cc176f7033bb9ef438a8aef9';
$pemPath = __DIR__ . '/aps_development.pem';
$passphrase = 'your_passphrase';

try {
    $apns = new ApnsPush($pemPath, $passphrase, true);  // true = sandbox
    $apns->push($deviceToken, '測試推播訊息', 1, 'default', ['id' => 'msg_123']);
    echo "推播發送成功\n";
    $apns->close();
} catch (Exception $e) {
    echo "錯誤: " . $e->getMessage() . "\n";
}

重要更新:APNs HTTP/2 協定(強烈建議)

Apple 自 2016 年起支援 HTTP/2 API,優點:

  • 單一長連線多路複用,效能大幅提升
  • JSON 格式,無需 Binary 封裝
  • 即時回傳錯誤碼,易於除錯
  • 支援 JWT Token 認證(免憑證管理)

使用 HTTP/2 的 PHP 實作(需 PHP 7.0+ cURL 支援 HTTP/2)

<?php
class ApnsHttp2 {
    private $keyId, $teamId, $bundleId, $p8Path;
    private $endpoint;

    public function __construct($keyId, $teamId, $bundleId, $p8Path, $isSandbox = true) {
        $this->keyId = $keyId;
        $this->teamId = $teamId;
        $this->bundleId = $bundleId;
        $this->p8Path = $p8Path;
        $this->endpoint = $isSandbox 
            ? 'https://api.sandbox.push.apple.com' 
            : 'https://api.push.apple.com';
    }

    private function generateJwt() {
        $header = ['alg' => 'ES256', 'kid' => $this->keyId];
        $claims = ['iss' => $this->teamId, 'iat' => time()];
        $headerB64 = $this->base64urlEncode(json_encode($header));
        $claimsB64 = $this->base64urlEncode(json_encode($claims));
        $data = "$headerB64.$claimsB64";

        $pkey = openssl_pkey_get_private('file://' . $this->p8Path);
        openssl_sign($data, $signature, $pkey, 'sha256');
        return "$data." . $this->base64urlEncode($signature);
    }

    private function base64urlEncode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    public function push($deviceToken, $alert, $badge = 0, $sound = 'default', $custom = []) {
        $payload = ['aps' => ['alert' => $alert]];
        if ($badge) $payload['aps']['badge'] = (int)$badge;
        if ($sound) $payload['aps']['sound'] = $sound;
        if ($custom) $payload = array_merge($payload, $custom);

        $url = "$this->endpoint/3/device/$deviceToken";
        $jwt = $this->generateJwt();

        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_HTTPHEADER => [
                'authorization: bearer ' . $jwt,
                'apns-topic: ' . $this->bundleId,
                'content-type: application/json',
            ],
            CURLOPT_RETURNTRANSFER => true,
        ]);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return [$httpCode, $response];
    }
}

常見錯誤與排查

錯誤原因解決
SSL 連線失敗憑證路徑錯誤、Passphrase 錯誤、.pem 格式不正確檢查路徑、用 openssl 驗證 `openssl x509 -in cert.pem -text`
Invalid tokenDevice Token 格式錯誤(含空格、長度不對)移除空格,確認 64 字元十六進位
Payload too largeJSON 超過 4096 bytes精簡自訂欄位、縮短 alert 文字
Connection resetAPNs 伺服器斷開(錯誤 payload、過期憑證)檢查錯誤回應、更新憑證
Expired provider tokenJWT 簽發時間過舊確保伺服器時間同步、重新產生 JWT

生產環境建議

  • ✅ 使用 HTTP/2 + JWT Token(.p8 金鑰),避免憑證過期管理
  • ✅ 連線池化:重用 cURL handle / stream socket,減少 TLS 握手
  • ✅ 非同步發送:佇列(Redis/RabbitMQ)+ Worker 進程批次推送
  • ✅ 錯誤重試:指數退避,記錄失敗 Token 定期清理
  • ✅ 監控指標:成功率、延遲、APNs 回傳錯誤碼分佈
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved