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
你可以使用 Java 的 WebSocketClient 类来实现 WebSocket 客户端。下面是一个简单的示例代码: ```java import java.net.URI; import java.net.http.HttpClient; import java.net.http.WebSocket; import java.util.concurrent.CompletionStage;import java.util.concurrent.CompletableFuture; public class WebSocketClient { public static void main(String[] args) { HttpClient client = HttpClient.newHttpClient(); WebSocket webSocket = client.newWebSocketBuilder() .buildAsync(URI.create("ws://localhost:8080/ws"), new WebSocketListener()) .join(); // 发送消息到服务器 webSocket.sendText("Hello, Server!", true); // 关闭 WebSocket 连接 webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "Client is closing connection"); } } class WebSocketListener implements WebSocket.Listener { @Override public void onOpen(WebSocket webSocket) { System.out.println("Connected to the server"); } @Override public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) { System.out.println("Received message from server: " + data); return CompletableFuture.completedFuture(null); } @Override public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) { System.out.println("Disconnected from the server: " + reason); return CompletableFuture.completedFuture(null); } @Override public void onError(WebSocket webSocket, Throwable error) { System.err.println("An error occurred: " + error.getMessage()); } } ``` 在上面的示例中,我们使用 Java 11 引入的 `java.net.http` 包中的 `HttpClient` 和 `WebSocket` 类。首先,我们创建一个 `HttpClient` 对象,并使用 `newWebSocketBuilder()` 方法创建一个 WebSocket 客户端。通过传入服务器的 URI 和一个实现了 `WebSocket.Listener` 接口的自定义监听器对象,我们可以异步地建立 WebSocket 连接。然后,我们可以使用 `sendText()` 方法向服务器发送文本消息,并使用 `sendClose()` 方法关闭 WebSocket 连接。 在自定义监听器中,我们实现了几个回调方法:`onOpen()` 在连接建立时调用,`onText()` 在接收到服务器发送的文本消息时调用,`onClose()` 在连接关闭时调用,`onError()` 在发生错误时调用。在这些方法中,我们可以编写自己的逻辑来处理不同的事件。 请注意,以上代码仅为一个基本示例,实际应用中可能需要根据具体需求进行修改和完善。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值