PHP调用APNS发送iOS推送失败,返回Result:114如何排查
APNS推送问题排查:Result:114含义与代码修正
一、Result:114的含义
fwrite返回的114是成功写入到Socket连接的字节总数,这仅代表你的推送请求已成功发送至APNS服务器,但不代表苹果会将推送给递到目标设备。推送未送达的问题出在APNS处理环节或设备端,而非请求发送失败。
二、你的代码存在的核心问题
使用已废弃的旧版二进制格式
你当前用chr(0)开头的是APNS早期的简单二进制格式(格式0),苹果已明确推荐使用增强二进制格式(格式1)或HTTP/2 API。旧格式无法接收APNS返回的错误响应,导致你无法得知推送失败的具体原因。未利用传入的参数构建Payload
函数接收了$title和$message参数,但构建$body['aps']时硬编码了标题和内容,完全未使用传入的变量,这会导致推送内容固定,不符合业务预期。环境不匹配风险
你连接的是生产环境地址gateway.push.apple.com:2195,如果你的测试设备使用的是开发证书打包的App,必须切换到沙箱环境地址gateway.sandbox.push.apple.com:2195,否则推送会被APNS拒绝。未读取APNS的错误反馈
即使使用旧格式,APNS在推送失败时会返回错误数据包,但你的代码发送后立即关闭连接,完全忽略了这一关键反馈渠道。
三、获取APNS错误信息的解决方案
方案1:切换到增强二进制格式(兼容旧版,但能获取错误)
修改消息构建和响应读取逻辑,这样可以获取APNS返回的错误状态码:
function sendPushAPN($to,$title,$message) { $to=str_replace(" ","",$to); $deviceToken=$to; $passphrase = 'abc'; $ctx = stream_context_create(); stream_context_set_option($ctx, 'ssl', 'local_cert', 'aps.pem'); stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase); // 根据环境选择地址:生产用gateway.push.apple.com,沙箱用gateway.sandbox.push.apple.com $fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx); if (!$fp) exit("Failed to connect: $err $errstr" . PHP_EOL); echo 'Connected to APNS' . PHP_EOL; // 正确使用传入的参数构建Payload $body['aps']=array( 'alert' => array( 'title' => $title, 'body' => $message['text'] ), 'sound' => 'default' ); // 自定义字段放在aps数组外部 $body['id'] = $message['id']; $payload = json_encode($body); // 构建增强二进制格式消息 $identifier = time(); // 唯一消息ID,用于定位错误 $expiry = time() + 3600; // 推送过期时间(1小时后) $msg = pack('C', 1) . pack('N', $identifier) . pack('N', $expiry) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; $result = fwrite($fp, $msg, strlen($msg)); echo 'Result:' . $result . PHP_EOL; // 读取APNS错误响应 stream_set_blocking($fp, 0); usleep(500000); // 等待500ms让APNS返回响应 $response = fread($fp, 6); if ($response) { $error = unpack('Ccommand/Cstatus/Nidentifier', $response); echo "APNS错误状态码: {$error['status']}" . PHP_EOL; echo "对应消息ID: {$error['identifier']}" . PHP_EOL; // 状态码对应含义:0=无错,8=无效令牌,10=连接关闭,255=未知错误等 } fclose($fp); }
方案2:改用APNS HTTP/2 API(官方推荐)
HTTP/2 API的错误反馈更直观,支持批量推送,无需维护长连接,是当前苹果推荐的方式。示例代码(依赖Guzzle HTTP客户端):
require 'vendor/autoload.php'; use GuzzleHttp\Client; function sendPushAPN($to,$title,$message) { $deviceToken = str_replace(" ","",$to); $passphrase = 'abc'; $certPath = 'aps.pem'; $bundleId = '你的App Bundle ID'; // 替换为你的App的Bundle ID // 环境选择:生产用api.push.apple.com,沙箱用api.development.push.apple.com $client = new Client([ 'base_uri' => 'https://api.push.apple.com', 'cert' => $certPath, 'ssl_key' => $certPath, 'passphrase' => $passphrase, 'verify' => true // 生产环境建议开启SSL验证 ]); $payload = json_encode([ 'aps' => [ 'alert' => [ 'title' => $title, 'body' => $message['text'] ], 'sound' => 'default' ], 'id' => $message['id'] ]); try { $response = $client->post("/3/device/{$deviceToken}", [ 'headers' => [ 'apns-topic' => $bundleId, 'apns-push-type' => 'alert' ], 'body' => $payload ]); echo "推送成功,HTTP状态码: {$response->getStatusCode()}" . PHP_EOL; } catch (\GuzzleHttp\Exception\ClientException $e) { $errorBody = json_decode($e->getResponse()->getBody()->getContents(), true); echo "推送失败: " . json_encode($errorBody) . PHP_EOL; } }
四、额外排查要点
- 设备令牌有效性:确保设备令牌是App启动时最新获取的,用户卸载重装后令牌会变更;检查令牌格式是否正确(无空格,大小写不敏感但建议与设备返回一致)。
- 证书权限:确认
aps.pem包含完整的证书和私钥,passphrase与生成证书时设置的一致;生产证书对应生产环境,开发证书对应沙箱环境,不可混用。 - App权限:确保App已向用户请求推送通知权限,且用户已允许。
- 网络环境:设备需正常联网,国内环境下可能需要特殊网络访问APNS服务器。
内容的提问来源于stack exchange,提问作者dembo
相关产品推荐
相关产品推荐

