注:本博文来作者的其他博客GeekerProbe,但是均为原创,要获得最佳阅读效果请移步至GeekerProbe



最近再做一个推送项目,需要搭建APNS服务器,再将PHP代码部署到Server上时遇到了如下错误:


Warning: stream_socket_client() [function.stream-socket-client]:
Unable to set local cert chain file `ck.pem';
Check that your cafile/capath settings include details of your certificate
and its issuer in D:\AppServ\www\push1\push.php on line 24
Warning: stream_socket_client() [function.stream-socket-client]:
failed to create an SSL handle in D:\AppServ\www\push1\push.php on line 24
Warning: stream_socket_client() [function.stream-socket-client]:
Failed to enable crypto in D:\AppServ\www\push1\push.php on line 24
Warning: stream_socket_client() [function.stream-socket-client]:
unable to connect to ssl://gateway.sandbox.push.apple.com:2195
(Unknown error) in D:\AppServ\www\push1\push.php on line 24
Failed to connect: 0


上网Google之,发现很多人遇到此问题,给出的解决方案照做后错误依然存在。最终还是自己解决,从问题本身出发吧,自己当初调试时Server是部署在Mac os上的,而现在却要部署在Windows Server 2008上。所以很可能是两边的配置出了问题,而代码的第24行为:


<?php
$fp = stream_socket_client(
        'ssl://gateway.sandbox.push.apple.com:2195', $err,
        $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
?>



PHP给出的错误大概是说,没有找到证书,无法建立ssl连接。

首先.pem证书已经制作,并且可用。还有就是证书的路径需要放正确


<?php
stream_context_set_option($ctx, 'ssl', 'local_cert',$this->localcert);
?>


确保该函数的最后一个参数所指的路径能正确找到证书。经过测试在Mac OS下这样就可以了,但是在Windows下要改成这样:


<?php
stream_context_set_option($ctx, 'ssl', 'local_cert',dirname(__FILE__) . '\\' .$this->localcert);
?>


但是做了这些,问题仍然不能解决,剩下的问题就是Apache需要开启ssl模块,通过查看Apache官方文档得知,使用ssl需要Apache开启三个支持模块分别是:

  1. mod_include

  2. mod_cgi

  3. mod_expires

接下来打开Apache的配置文件httpd.conf大概50-100行之间模块加载部分,放开这三个模块加载前的注释:


LoadModule reqtimeout_module libexec/apache2/mod_reqtimeout.so
LoadModule ext_filter_module libexec/apache2/mod_ext_filter.so
LoadModule include_module libexec/apache2/mod_include.so          #注释放开
LoadModule filter_module libexec/apache2/mod_filter.so
LoadModule substitute_module libexec/apache2/mod_substitute.so
LoadModule deflate_module libexec/apache2/mod_deflate.so
LoadModule log_config_module libexec/apache2/mod_log_config.so
LoadModule log_forensic_module libexec/apache2/mod_log_forensic.so
LoadModule logio_module libexec/apache2/mod_logio.so
LoadModule env_module libexec/apache2/mod_env.so
LoadModule mime_magic_module libexec/apache2/mod_mime_magic.so
LoadModule cern_meta_module libexec/apache2/mod_cern_meta.so
LoadModule expires_module libexec/apache2/mod_expires.so         #注释放开
LoadModule headers_module libexec/apache2/mod_headers.so
LoadModule ident_module libexec/apache2/mod_ident.so



保存,重启Apache,再试,问题已解决。

最后附上推送部分的PHP代码:PHP代码下载地址


<?php
class Push {
    public $deviceToken;//需要在构造时候设置
    //本地证书和密码
    public $localcert ='';
    public $passphrase = '';
    /*
     * 功能:构造函数,设置deviceToken
    */
    function Push($deviceToken)
    {
        $this->deviceToken = $deviceToken;
    }
    /*
    功能:生成发送内容并且转化为json格式
    */
    private function createPayload($message,$type,$sound)
    {
        // Create the payload body
        $body['aps'] = array(
            'alert' => $message,
            'sound' => $sound,
            'type' =>$type
        );
        // Encode the payload as JSON
        $payload = json_encode($body);
        return $payload;
    }
    // Put your private key's passphrase here:
   public function  pushData($message,$type,$sound)
    {
        $ctx = stream_context_create();
        stream_context_set_option($ctx, 'ssl', 'local_cert',$this->localcert);
        stream_context_set_option($ctx, 'ssl', 'passphrase', $this->passphrase);
        // Open a connection to the APNS server
        //这个为正是的发布地址
         //$fp = stream_socket_client(“ssl://gateway.push.apple.com:2195“, $err, $errstr, 60, //STREAM_CLIENT_CONNECT, $ctx);
        //这个是沙盒测试地址,发布到appstore后记得修改哦
        $fp = stream_socket_client(
        'ssl://gateway.sandbox.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 =$this->createPayload($message,$type,$sound);
        // Build the binary notification
        $msg = chr(0) . pack('n', 32) . pack('H*', $this ->deviceToken) . pack('n', strlen($payload)) . $payload;
        // Send it to the server
        $result = fwrite($fp, $msg, strlen($msg));
        if (!$result)
        {
            echo 'Message not delivered' . PHP_EOL;
        }
        else
        {
            echo 'Message successfully delivered' . PHP_EOL;
        }
        // Close the connection to the server
        fclose($fp);
    }
   }
?>