为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用PHP主动推送信息给客户端

标签:
PHP

一、需求分析

开发一个项目微信小程序享寄存,软硬件结合,软硬件通信以前没有做过,网上的资料也很少,踩了不少的坑,特此记录一下。首先硬件和服务器建立一个socket长链接,前端微信小程序给服务器,发出命令给服务器,开锁,服务器如何通知客户端呢?

二、解决方案

在硬件客户端和服务器建立链接时,同时开启一个内部端口,进行监听

三、代码实现

start.php代码

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2017/12/30
 * Time: 23:30
 */

namespace app\push\controller;

use app\api\model\User;
use app\common\model\Box;
use app\common\model\Chest;
use GatewayWorker\Gateway;
use My\RedisPackage;
use think\Cache;
use think\Db;
use think\Log;
use think\worker\Server;

class Worker extends Server
{

    /**
     * 每个进程启动
     * @param $worker
     */
    public function onWorkerStart($worker)
    {
        // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
        $inner_text_worker = new \Workerman\Worker('text://0.0.0.0:5678');
        $inner_text_worker->onMessage = function($connection, $buffer)
        {
            // $data数组格式,里面有uid,表示向那个uid的页面推送数据
            $data = json_decode($buffer, true);
            $uid = $data['uid'];
            $openArr = config('lock.open');
            $index = $data['index'];
            // 通过workerman,向uid的页面推送数据
            $ret = $this->sendMessageByUid($uid, self::hexToString($openArr[$index]));
            // 返回推送结果
            $connection->send($ret ? 'ok' : 'fail');
        };
        // ## 执行监听 ##
        $inner_text_worker->listen();
    }

    /**
     * 收到信息
     * @param $connection
     * @param $data
     */
    public function onMessage($connection, $data)
    {
       $receiveData  = explode(',',$data);
       //判断是否是心跳包
       if(!empty($receiveData) && $receiveData[0] == 'NBES') {
         $send = explode('=',$receiveData[1]);
        if(isset($connection->id) && !array_key_exists($connection->id,self::$worker->uidConnections)) {

            self::$worker->uidConnections[$connection->id] = $connection;
        }
        //对应起来数据库里面的柜子
        $chest = Chest::getChestByGsm($send[1]);
        if($chest->server_id != $connection->id) {
            Chest::update(array('server_id'=>$connection->id,'simstatus'=>config('sim.status_normal')),array('bdz'=>$send[1]));
        }
        $connection->send($send[1]);
     }else {
           if(!empty($data)) {
               $backSuccess = config('lock.backSuccess');
               $backError = config('lock.backError');
               $receive = self::stringToHexArray($data);
               $boxOpenStatus = config('box.open');
               $boxTrouble = config('box.trouble');
               $closeSuccess = config('lock.closeSuccess');
               $closeError = config('lock.closeError');
               if($receive[0] == '0x8a') {
                  //读锁返回状态
                   $this->openReturnStatus($receive,$backError,$backSuccess,$boxOpenStatus,$boxTrouble,$connection);
               }else if($receive[0] == '0x82'){
                   //关锁状态反馈
                   $this->closeReturnStatus($receive,$closeSuccess,$closeError,$boxTrouble,$boxOpenStatus,$connection);
               }
           }
       }
    }

    /**
     * 当连接建立时触发的回调函数
     * @param $connection
     */
    public function onConnect($connection)
    {
        echo 'connect ok -'.$connection->id;
    }

    /**
     * 当连接断开时触发的回调函数
     * @param $connection
     */
    public function onClose($connection)
    {
        if(isset($connection->uid))
        {
            // 连接断开时删除映射
            unset(self::$worker->uidConnections[$connection->uid]);
        }
    }

    /**
     * @param $uid
     * @param $message
     * @return bool
     * 发送信息到客户端
     */
    public function sendMessageByUid($uid, $message)
    {
        if(isset(self::$worker->uidConnections[$uid]))
        {
            $connection = self::$worker->uidConnections[$uid];
            $connection->send($message);
            return true;
        }
        return false;
    }

    /**
     * 当客户端的连接上发生错误时触发
     * @param $connection
     * @param $code
     * @param $msg
     */
    public function onError($connection, $code, $msg)
    {
        echo "error $code $msg\n";
    }

    /**
     * 说明:字符串转十六进制
     * 参数:字符串,例如:$string = "cfg_power";
     * 返回:十六进制,返回0x63,0x66,0x67,0x5f,0x70,0x6f,0x77,0x65,0x72,0x62,0x79
     */
    public static function stringToHexArray($string)
    {
        $arr1 = str_split($string, 1);
        foreach($arr1 as $akey=>$aval){
            $arr1[$akey]="0x".bin2hex($aval);
        }
        return($arr1);
    }

    /**
     * 说明:十六进制转字符串
     * 参数:十六进制,例如:$hex = array(0x63,0x66,0x67,0x5f,0x70,0x6f,0x77,0x65,0x72,0x62,0x79);
     * 返回:字符串 ,例如: $myStr = "cfg_power"
     */
    public static function hexToString($hex)
    {
        $myStr="";
        for($i=0;isset($hex[$i]);$i++)
        {
            $myStr.= chr($hex[$i]);
        }
        return($myStr);
    }
    /**
     * @param $receive
     * @param $closeSuccess
     * @param $boxOpenStatus
     * @param $connection
     * 手动关闭箱子返回状态处理
     */
    public function closeReturnStatus($receive,$closeSuccess,$closeError,$boxTrouble,$boxOpenStatus,$connection) {
        if(in_array($receive,$closeSuccess)) {
            $keys = array_keys($closeSuccess,$receive);
            $key = $keys[0]+1;
            $open = $boxOpenStatus['status_normal'];
            $chest = Chest::get(array('server_id'=>$connection->id));
            if(!empty($chest)) {
                Box::update(array('open'=>$open),array('xsxh'=>$key,'cid'=>$chest->id));
            }
        }elseif (in_array($receive,$closeError)) {
            $keys = array_keys($closeError,$receive);
            $key = $keys[0]+1;
            $status = $boxTrouble['status_padding'];
            $chest = Chest::get(array('server_id'=>$connection->id));
            if(!empty($chest)) {
                Box::update(array('status'=>$status),array('xsxh'=>$key,'cid'=>$chest->id));
            }
        }
    }

    /**
     * @param $receive
     * @param $backError
     * @param $backSuccess
     * @param $boxOpenStatus
     * @param $boxTrouble
     * @param $connection
     * 处理开门命令反馈的数据
     */
    private function openReturnStatus($receive,$backError,$backSuccess,$boxOpenStatus,$boxTrouble,$connection) {
        //开门返回状态
        $open = 1;
        $trouble = 0;
        $key = 1;
        if(in_array($receive,$backSuccess)) {
            $keys = array_keys($backSuccess,$receive);
            $key = $keys[0]+1;
            $open = $boxOpenStatus['status_padding'];
            $trouble = $boxTrouble['status_normal'];
        }else if(in_array($receive,$backError)) {
            $keys = array_keys($backError,$receive);
            $key = $keys[0]+1;
            $open = $boxOpenStatus['status_normal'];
            $trouble = $boxTrouble['status_delete'];
        }
        $chest = Chest::get(array('server_id'=>$connection->id));
        if(!empty($chest)) {
            Box::update(array('open'=>$open,'status'=>$trouble),array('xsxh'=>$key,'cid'=>$chest->id));
        }
    }

}

Socket代码,发送指令到内部端口

 /**
     * @param $id 柜子的ID
     * @param $index 箱子的ID 显示箱号
     * @return bool|string
     *
     */
    public static function openLock($id,$index) {
        // 建立socket连接到内部推送端口
        $client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
        // 推送的数据,包含uid字段,表示是给这个uid推送
        $data = array('uid' => $id, 'index' => $index);
        // 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
        fwrite($client, json_encode($data) . "\n");
        // 读取推送结果
        return fread($client, 8192);
    }

四、流程总结

首先硬件客户端和服务器建立一个socket链接,建立时开启一个内部端口进行监听,软件客户端(微信小程序或者app)发送http请求给服务器,服务器接受到请求,通过socket发送数据到内部端口,内部端口接受到数据发送指令给硬件客户端。

点击查看更多内容
9人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
PHP开发工程师
手记
粉丝
75
获赞与收藏
659

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消