PHP实现Websocket异步客户端 (workerman方式)

1. 通过composer load workman

composer安装

php -r "readfile('https://getcomposer.org/installer');" | php
mv composer.phar /usr/local/bin/composer
mkdir /home/wsclient
cd /home/wsclient
composer require workerman/workerman

若出错,一般是 Composer 未配置正确的源,可以尝试切换Composer到官方源

composer config --global --unset repos.packagist
composer install

 

2. 创建workerman执行文件 /home/wsclient/wsclient.php

本例使用NowAPI提供的消息推送服务 Websocket消息推送服务

<?php
date_default_timezone_set('PRC');
require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\AsyncTcpConnection;

//配置
$setParmA['host']        = 'nms-aaa.nowapi.com';
$setParmA['port']        = '443';
$setParmA['ssl']         = '1';//是否ssl加密 0:否 1:是
$setParmA['instanceId']  = 'ins-10003';
$setParmA['accessKey']   = '8512547106565e12544125007942565e';//测试key
$setParmA['secretKey']   = '';
$setParmA['userId']      = 'php-'.rand(10000,99999);//指定客户端唯一ID,这里随机一个
$setParmA['channels']    = 'myChannel';//订阅通道(多个用逗号隔开)
$setParmA['onOpen']      = function(){};//连接成功触发
$setParmA['onMessage']   = function($msgData){};//收到消息触发
$setParmA['onClose']     = function(){};//与服务器断开
$setParmA['onFailed']    = function($msgFailed){};//异常消息触发
$setParmA['onTaskTimer'] = function($worker,$con){};//定时器

//接收到消息时触发
$setParmA['onMessage']   = function($msgData){
    $msgId      = $msgData['i'];
    $msgTime    = $msgData['t'];
    $msgChannel = $msgData['n'];
    $msgContent = $msgData['c'];

    echo "       msgId : ".$msgId."\r\n";
    echo "     msgTime : ".date('Y-m-d H:i:s',substr($msgTime,0,-3))." ".substr($msgTime,0,-3)."\r\n";
    echo "  msgChannel : ".$msgChannel."\r\n";
    echo "  msgContent : ".$msgContent."\r\n";
    echo "base64decode : ".base64_decode($msgContent)."\r\n";
    echo "----\r\n";
};

//定时器触发 (每10秒执行一次,推送源)
$setParmA['onTaskTimer'] = function($worker,$con){
    $phpOS = strtolower(PHP_OS);

    //数据采集提取
    $rspData = '';
    if($phpOS=='linux'){
        $rspData = shell_exec('ping 119.29.29.29 -c 1');
    }elseif($phpOS=='winnt'){
        $rspData = shell_exec('ping 119.29.29.29 -n 1');
    }
    //编码后传递
    $rspData = base64_encode($rspData);
    //
    $pushChannel = 'myChannel';//向该通道推送
    $con->send('["publish",{"channels":"'.$pushChannel.'","content":"'.$rspData.'"}]');
};

//异常时执行
$setParmA['onFailed'] = function($msgFailed){
    echo date('Y-m-d H:i:s')." ERR: ".$msgFailed."\r\n";
};

//与服务端连接断开执行
$setParmA['onClose'] = function(){
    echo date('Y-m-d H:i:s')." Connection closed and try to reconnect\r\n";
};

// 创建一个worker实例
$worker = new Worker();
$worker->onWorkerStart = function($worker) use($setParmA){
    //配置
    $host       = $setParmA['host'];
    $port       = $setParmA['port'];
    $ssl        = $setParmA['ssl'];//是否ssl加密 0:否 1:是
    $instanceId = $setParmA['instanceId'];
    $accessKey  = $setParmA['accessKey'];
    $secretKey  = $setParmA['secretKey'];
    $userId     = $setParmA['userId'];//指定客户端唯一ID
    $channels   = $setParmA['channels'];//订阅通道(多个用逗号隔开)

    //运行参数
    $worker->heartbeatPingInterval = 5000;//心跳包间隔
    $worker->heartbeatTimeId       = 0;//心跳定时器ID
    $worker->taskTimeId            = 0;//任务定时器ID

    //连接
    $con = new AsyncTcpConnection('ws://'.$host.':'.$port.'/?instanceId='.$instanceId);

    // 设置以ssl加密方式访问,使之成为wss
    if($ssl==1){
        $con->transport = 'ssl';
    }

    //连接
    $con->onWebSocketConnect = function(AsyncTcpConnection $con) use($accessKey,$secretKey,$userId) {
        $safeCode = '';
        if(!empty($secretKey)){
            $safeCode = openssl_encrypt(round(microtime(true)*1000).'___','AES-128-ECB',$secretKey,2,'');
        }
        $con->send('["authorize",{"accessKey":"'.$accessKey.'","userId":"'.$userId.'","safeCode":"'.$safeCode.'"}]');
    };
    //收到消息
    $con->onMessage = function(AsyncTcpConnection $con,$onData) use($worker,$setParmA){
        $channels  = $setParmA['channels'];
        $onMessage = $setParmA['onMessage'];
        try{
            //心跳
            if(trim($onData)==='2'){
                throw new Exception('INF_HEARTBEAT',1);
            }
            //数据
            $onDataA = json_decode($onData,true);
            if(!is_array($onDataA)){
                throw new Exception('ERR_DATA_FORMAT',-1);
            }
            $rcvType  = $onDataA[0];
            $rcvData  = $onDataA[1];

            //通用响应
            if(in_array($rcvType,array('subscribe','unsubscribe','publish'))){
                if($rcvData['resultCode']!='200'){
                    throw new Exception($onData,-1);
                }else{
                    throw new Exception($onData,1);
                }
            }elseif($rcvType=='message'){
                //消息确认
                if(!empty($rcvData['i'])){
                    $con->send('["messageack",{"i":"'.$rcvData['i'].'"}]');
                }else{
                    throw new Exception($onData,-1);
                }
            }

            //逻辑响应
            if($rcvType=='authorize'){
                if($rcvData['resultCode']=='200' && is_numeric($rcvData['resultContent']['pingInterval'])){
                    //启动定时器推送心跳包
                    $worker->heartbeatPingInterval = $rcvData['resultContent']['pingInterval'];
                    $worker->heartbeatTimeId = Timer::add($worker->heartbeatPingInterval/1000,function() use ($worker,$con){
                        //var_dump('ping '.date('Y-m-d H:i:s').' '.$worker->heartbeatPingInterval.' / '.$worker->heartbeatTimeId);
                        $con->send('1');
                    });
                    //清空订阅
                    $con->send('["unsubscribe",{"channels":[]}]');
                    //新订阅
                    $con->send('["subscribe",{"channels":"'.$channels.'"}]');
                }
                throw new Exception('INF_AUTHORIZE ['.$worker->heartbeatPingInterval.'/'.$worker->heartbeatTimeId.']',1);
            }
            //收到消息
            elseif($rcvType=='message'){
                if(is_object($onMessage)){
                    $onMessage($rcvData);
                }
            }
        }catch(Exception $ex){
            $errCode = $ex->getCode();
            if($errCode!==1){
                if(is_object($setParmA['onFailed'])){
                    $setParmA['onFailed']($ex->getMessage());
                }
            }
        }
    };

    //
    $con->onError = function(AsyncTcpConnection $con,$code,$msg) use($setParmA){
        if(is_object($setParmA['onFailed'])){
            $setParmA['onFailed']($msg);
        }
    };

    //
    $con->onClose = function($con) use($worker,$setParmA){
        // 清理timer
        Timer::del($worker->heartbeatTimeId);
        Timer::del($worker->taskTimeId);
        //
        if(is_object($setParmA['onClose'])){
            return $setParmA['onClose']();
        }
        // 如果连接断开,1秒后重连
        $con->reConnect(5);
    };

    //task
    $worker->taskTimeId = Timer::add(10,function() use ($worker,$con,$setParmA){
        if(is_object($setParmA['onTaskTimer'])){
            return $setParmA['onTaskTimer']($worker,$con);
        }
    });

    $con->connect();
};

Worker::runAll();

 

3. 运行

本例需要php_openssl支持,注意在php.ini中添加php_openssl ext

/usr/bin/php /home/wsclient/wsclient.php start 

如果不想配置PHP运行环境,也可以直接用swoole编译好的二进制版

/usr/bin/swoole-cli /home/wsclient/wsclient.php start

posted on 2024-04-02 11:01  jasonss  阅读(34)  评论(0编辑  收藏  举报