PHP Thinkphp框架Fastadmin实现MQTT发布与订阅功能

1.首先在Linux中正常安装PHP、MQTT。

2.在服务器上安装MQTT服务端,有2中方式:(1)MQTT 服务器搭建之Apollo,(2) MQTT 服务器搭建之Mosquitto,本文用的第二种,具体的安装随便找个教程都可以安装。

3.MQTT安装好后测试,推荐使用Eclipse Paho MQTT Utility工具测试,如出现以下情况,恭喜你安装成功了!

4.在ThinkPHP框架里面实现发布与订阅。

4.1首先写一个自动订阅的脚本,让它一直执行在服务器上,这样写的好处是实时获取前台的一些状态。

<?php
$c = new Mosquitto\Client;
$c->setCredentials('MQTT用户','密码');
$c->connect('连接地址',1883, 50);
$c->subscribe('订阅主题', 0);
$c->onMessage(function($m) {
      $payload = json_decode($m->payload, true);
      if(is_array($payload)){
          @post_url('http://ip地址/mqtt/api/index/rec', $payload);//这个地址是接收客户端发过来的数据。
      }
      var_dump($m);
});
$c->loopForever();
function post_url($url, $post = '', $files = '', $host = '', $referrer = '', $cookie = '', $proxy = '', $useragent = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1)'){
    if(empty($post) && empty($files) && empty($host) && empty($referrer) && empty($cookie) && empty($proxy) && empty($useragent))return @file_get_contents($url);
    $method = 'POST';//empty($post) && empty($files) ? 'GET' : 'POST';
    if($post && is_array($post)){
      if(count($post) != count($post, 1))$post = http_build_query($post);
    }

    $ch = @curl_init();
    @curl_setopt($ch, CURLOPT_URL, $url);
    if($proxy)@curl_setopt($ch, CURLOPT_PROXY, 'http://'.$proxy);
    if($referrer)@curl_setopt($ch, CURLOPT_REFERER, $referrer);
    @curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
    if($cookie){
      @curl_setopt($ch, CURLOPT_COOKIE, $cookie); 
      //@curl_setopt($ch, CURLOPT_COOKIEJAR, COOKIE_FILE_PATH);
      //@curl_setopt($ch, CURLOPT_COOKIEFILE, COOKIE_FILE_PATH);
    }
    @curl_setopt($ch, CURLOPT_HEADER, 0);
    @curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    @curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    @curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

    if ($method == 'POST') {
      @curl_setopt($ch, CURLOPT_POST, 1);     
      //处理文件上传
      if($files){
        if(!$post)$post = array();
        foreach($files as $k => $v){
          if (class_exists('CURLFile')) {
            $post[$k] = new CURLFile(realpath($v));
          } else {
            $post[$k] = '@'.realpath($v);
          }
        }
      }
      @curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    }

    $result = @curl_exec($ch);
    @curl_close($ch);
    return $result;
}
 

4.2接受数据的函数

mqtt/api/index/rec /*需要在这样一个目录下面,文件名为index,rec是函数名字*/,这个是根据上面@post_url而来,你想怎么写都行的,只要它能找到就OK。

public function rec(){

$postInfo = json_encode($_POST,JSON_UNESCAPED_UNICODE);//获取前台传过来的数据

}

特别说明:下面订阅与发布是封装后的函数,如果需要发布,直接调用就可以了。

调用例子

publish("mqtt",json_encode($data),0);//第一个是发布的主题,第二个是数据,第三个是...mqtt有三种模式0、1、2具体代表什么意思自己去看哈。

5.写订阅函数

<?php
namespace app\admin\v1\mqtt\model;

use think\Model;
use think\Db;
class Subscribe extends Model
{
   protected $server = "";     // 服务代理地址(mqtt服务端地址)
   
   protected $port = 1883;                // 通信端口
   
   protected $username = '';          // 用户名(如果需要)
   
   protected $password = '';       // 密码(如果需要)
   
   protected $url = 'http://127.0.0.1/message/sendmessage';  //webSocket请求地址
   
   public function subscribe($client_id = "box_001"){
      set_time_limit(0);
      // 订阅信息,接收一个信息后退出
      $server = $this->server;     
      $port = $this->port;                     
      $username = $this->username;                
      $password = $this->password;                 
      $client_id = $client_id;   
      $mqtt = new Mqtt($server, $port, $client_id);
     //链接不成功再重复执行监听连接
      if(!$mqtt->connect(true, NULL, $username, $password)) {
         exit(1);
      }
      $topics['xls001/box_001/event'] = array("qos" => 0, "function" => "procmsg");
      // 订阅主题为 testlink qos为0
      $mqtt->subscribe($topics, 0);
      while($mqtt->proc()){
      }
      //死循环监听
      $mqtt->close();
      function procmsg($topic, $msg){ //信息回调函数 打印信息
         echo "Msg Recieved: " . date("r") . "\n";
         echo "Topic: {$topic}\n\n";
         echo "\t$msg\n\n";
         $xxx = json_decode($msg);
         var_dump($xxx->aa);
         die;
      }
   }
   
   
}

6.发布主题

<?php
namespace app\admin\v1\mqtt\model;
use fast\Addon;
use think\Model;

use think\Db;
/* phpMQTT */

class Publish extends Model
{
   protected $server = "";     // 服务代理地址(mqtt服务端地址)
   
   protected $port = ;                // 通信端口
   
   protected $username = '';          // 用户名(如果需要)
   
   protected $password = '';       // 密码(如果需要)
   
   protected $url = 'http://127.0.0.1/message/sendmessage';  //webSocket请求地址
   
   /**
     * Mqtt 发布主题
    * @param string  $topic        发布的主题
    * @param json    $data         要发送的内容
    * @param int     $qos          模式 0.发送不保证收到    1.最少收到1次  2.收到并且只收到1次
    * @param string  $client_id    唯一
    * @return [true] [false]
     */
   public function publish($topic = "index",$data = '',$qos = 0,$client_id = 0){
      // 发送给订阅号信息,创建socket,无sam队列
      $server = $this->server;     
      $port = $this->port;                     
      $username = $this->username;                
      $password = $this->password;                 
      $mqtt = new Mqtt($server, $port, $client_id); //实例化MQTT类
      if ($mqtt->connect(true, NULL, $username, $password)) {
         //如果创建链接成功
         $re = $mqtt->publish($topic,$data,$qos); //发送主题
         // 发送到 xxx3809293670ctr 的主题 一个信息 内容为 setr=3xxxxxxxxx Qos 为 0 
         $mqtt->close();    //发送后关闭链接
         return true;//成功返回true
      } else {
         return "Time out!\n";//失败返回false
      }
   }

}

7.php想要实现mqtt需要使用到php中的socket函数;

代码如下:(直接新建一个Mqtt.php文件,注意大小写,将下面的代码粘贴进去就可以了,不知道是哪位大牛写的哈,在此向这位大牛致敬,敬礼!)。

/* phpMQTT */
class Mqtt {

    private $socket;             /* holds the socket    */
    private $msgid = 1;            /* counter for message id */
    public $keepalive = 10;        /* default keepalive timmer */
    public $timesinceping;        /* host unix time, used to detect disconects */
    public $topics = array();     /* used to store currently subscribed topics */
    public $debug = false;        /* should output debug messages */
    public $address;            /* broker address */
    public $port;                /* broker port */
    public $clientid;            /* client id sent to brocker */
    public $will;                /* stores the will of the client */
    private $username;            /* stores username */
    private $password;            /* stores password */

    public $cafile;

    function __construct($address, $port, $clientid, $cafile = NULL){
        $this->broker($address, $port, $clientid, $cafile);
    }

    /* sets the broker details */
    function broker($address, $port, $clientid, $cafile = NULL){
        $this->address = $address;
        $this->port = $port;
        $this->clientid = $clientid;
        $this->cafile = $cafile;
    }

    function connect_auto($clean = true, $will = NULL, $username = NULL, $password = NULL){
        while($this->connect($clean, $will, $username, $password)==false){
            sleep(10);
        }
        return true;
    }

    /* connects to the broker
        inputs: $clean: should the client send a clean session flag */
    function connect($clean = true, $will = NULL, $username = NULL, $password = NULL){

        if($will) $this->will = $will;
        if($username) $this->username = $username;
        if($password) $this->password = $password;


        if ($this->cafile) {
            $socketContext = stream_context_create(["ssl" => [
                "verify_peer_name" => true,
                "cafile" => $this->cafile
            ]]);
            $this->socket = stream_socket_client("tls://" . $this->address . ":" . $this->port, $errno, $errstr, 60, STREAM_CLIENT_CONNECT, $socketContext);
        } else {
            $this->socket = stream_socket_client("tcp://" . $this->address . ":" . $this->port, $errno, $errstr, 60, STREAM_CLIENT_CONNECT);
        }

        if (!$this->socket ) {
            if($this->debug) error_log("stream_socket_create() $errno, $errstr \n");
            return false;
        }

        stream_set_timeout($this->socket, 5);
        stream_set_blocking($this->socket, 0);

        $i = 0;
        $buffer = "";

        $buffer .= chr(0x00); $i++;
        $buffer .= chr(0x06); $i++;
        $buffer .= chr(0x4d); $i++;
        $buffer .= chr(0x51); $i++;
        $buffer .= chr(0x49); $i++;
        $buffer .= chr(0x73); $i++;
        $buffer .= chr(0x64); $i++;
        $buffer .= chr(0x70); $i++;
        $buffer .= chr(0x03); $i++;

        //No Will
        $var = 0;
        if($clean) $var+=2;

        //Add will info to header
        if($this->will != NULL){
            $var += 4; // Set will flag
            $var += ($this->will['qos'] << 3); //Set will qos
            if($this->will['retain'])    $var += 32; //Set will retain
        }

        if($this->username != NULL) $var += 128;    //Add username to header
        if($this->password != NULL) $var += 64;    //Add password to header

        $buffer .= chr($var); $i++;

        //Keep alive
        $buffer .= chr($this->keepalive >> 8); $i++;
        $buffer .= chr($this->keepalive & 0xff); $i++;

        $buffer .= $this->strwritestring($this->clientid,$i);

        //Adding will to payload
        if($this->will != NULL){
            $buffer .= $this->strwritestring($this->will['topic'],$i);
            $buffer .= $this->strwritestring($this->will['content'],$i);
        }

        if($this->username) $buffer .= $this->strwritestring($this->username,$i);
        if($this->password) $buffer .= $this->strwritestring($this->password,$i);

        $head = "  ";
        $head{0} = chr(0x10);
        $head{1} = chr($i);

        fwrite($this->socket, $head, 2);
        fwrite($this->socket,  $buffer);

        $string = $this->read(4);

        if(ord($string{0})>>4 == 2 && $string{3} == chr(0)){
            if($this->debug) echo "Connected to Broker\n";
        }else{
            error_log(sprintf("Connection failed! (Error: 0x%02x 0x%02x)\n",
                ord($string{0}),ord($string{3})));
            return false;
        }

        $this->timesinceping = time();

        return true;
    }

    /* read: reads in so many bytes */
    function read($int = 8192, $nb = false){

        //    print_r(socket_get_status($this->socket));

        $string="";
        $togo = $int;

        if($nb){
            return fread($this->socket, $togo);
        }

        while (!feof($this->socket) && $togo>0) {
            $fread = fread($this->socket, $togo);
            $string .= $fread;
            $togo = $int - strlen($string);
        }


        return $string;
    }

    /* subscribe: subscribes to topics */
    function subscribe($topics, $qos = 0){
        $i = 0;
        $buffer = "";
        $id = $this->msgid;
        $buffer .= chr($id >> 8);  $i++;
        $buffer .= chr($id % 256);  $i++;

        foreach($topics as $key => $topic){
            $buffer .= $this->strwritestring($key,$i);
            $buffer .= chr($topic["qos"]);  $i++;
            $this->topics[$key] = $topic;
        }

        $cmd = 0x80;
        //$qos
        $cmd +=    ($qos << 1);


        $head = chr($cmd);
        $head .= chr($i);

        fwrite($this->socket, $head, 2);
        fwrite($this->socket, $buffer, $i);
        $string = $this->read(2);

        $bytes = ord(substr($string,1,1));
        $string = $this->read($bytes);
    }

    /* ping: sends a keep alive ping */
    function ping(){
        $head = " ";
        $head = chr(0xc0);
        $head .= chr(0x00);
        fwrite($this->socket, $head, 2);
        if($this->debug) echo "ping sent\n";
    }

    /* disconnect: sends a proper disconect cmd */
    function disconnect(){
        $head = " ";
        $head{0} = chr(0xe0);
        $head{1} = chr(0x00);
        fwrite($this->socket, $head, 2);
    }

    /* close: sends a proper disconect, then closes the socket */
    function close(){
        $this->disconnect();
        stream_socket_shutdown($this->socket, STREAM_SHUT_WR);
    }

    /* publish: publishes $content on a $topic */
    function publish($topic, $content, $qos = 0, $retain = 0){

        $i = 0;
        $buffer = "";

        $buffer .= $this->strwritestring($topic,$i);

        //$buffer .= $this->strwritestring($content,$i);

        if($qos){
            $id = $this->msgid++;
            $buffer .= chr($id >> 8);  $i++;
            $buffer .= chr($id % 256);  $i++;
        }

        $buffer .= $content;
        $i+=strlen($content);


        $head = " ";
        $cmd = 0x30;
        if($qos) $cmd += $qos << 1;
        if($retain) $cmd += 1;

        $head{0} = chr($cmd);
        $head .= $this->setmsglength($i);

        fwrite($this->socket, $head, strlen($head));
        fwrite($this->socket, $buffer, $i);

    }

    /* message: processes a recieved topic */
    function message($msg){
        $tlen = (ord($msg{0})<<8) + ord($msg{1});
        $topic = substr($msg,2,$tlen);
        $msg = substr($msg,($tlen+2));
        $found = 0;
        foreach($this->topics as $key=>$top){
            if( preg_match("/^".str_replace("#",".*",
                    str_replace("+","[^\/]*",
                        str_replace("/","\/",
                            str_replace("$",'\$',
                                $key))))."$/",$topic) ){
                if(is_callable($top['function'])){
                    call_user_func($top['function'],$topic,$msg);
                    $found = 1;
                }
            }
        }

        if($this->debug && !$found) echo "msg recieved but no match in subscriptions\n";
    }

    /* proc: the processing loop for an "allways on" client
        set true when you are doing other stuff in the loop good for watching something else at the same time */
    function proc( $loop = true){

        if(1){
            $sockets = array($this->socket);
            $w = $e = NULL;
            $cmd = 0;

            //$byte = fgetc($this->socket);
            if(feof($this->socket)){
                if($this->debug) echo "eof receive going to reconnect for good measure\n";
                fclose($this->socket);
                $this->connect_auto(false);
                if(count($this->topics))
                    $this->subscribe($this->topics);
            }

            $byte = $this->read(1, true);

            if(!strlen($byte)){
                if($loop){
                    usleep(100000);
                }

            }else{

                $cmd = (int)(ord($byte)/16);
                if($this->debug) echo "Recevid: $cmd\n";

                $multiplier = 1;
                $value = 0;
                do{
                    $digit = ord($this->read(1));
                    $value += ($digit & 127) * $multiplier;
                    $multiplier *= 128;
                }while (($digit & 128) != 0);

                if($this->debug) echo "Fetching: $value\n";

                if($value)
                    $string = $this->read($value);

                if($cmd){
                    switch($cmd){
                        case 3:
                            $this->message($string);
                            break;
                    }

                    $this->timesinceping = time();
                }
            }

            if($this->timesinceping < (time() - $this->keepalive )){
                if($this->debug) echo "not found something so ping\n";
                $this->ping();
            }


            if($this->timesinceping<(time()-($this->keepalive*2))){
                if($this->debug) echo "not seen a package in a while, disconnecting\n";
                fclose($this->socket);
                $this->connect_auto(false);
                if(count($this->topics))
                    $this->subscribe($this->topics);
            }

        }
        return 1;
    }

    /* getmsglength: */
    function getmsglength(&$msg, &$i){

        $multiplier = 1;
        $value = 0 ;
        do{
            $digit = ord($msg{$i});
            $value += ($digit & 127) * $multiplier;
            $multiplier *= 128;
            $i++;
        }while (($digit & 128) != 0);

        return $value;
    }


    /* setmsglength: */
    function setmsglength($len){
        $string = "";
        do{
            $digit = $len % 128;
            $len = $len >> 7;
            // if there are more digits to encode, set the top bit of this digit
            if ( $len > 0 )
                $digit = ($digit | 0x80);
            $string .= chr($digit);
        }while ( $len > 0 );
        return $string;
    }

    /* strwritestring: writes a string to a buffer */
    function strwritestring($str, &$i){
        $ret = " ";
        $len = strlen($str);
        $msb = $len >> 8;
        $lsb = $len % 256;
        $ret = chr($msb);
        $ret .= chr($lsb);
        $ret .= $str;
        $i += ($len+2);
        return $ret;
    }

    function printstr($string){
        $strlen = strlen($string);
        for($j=0;$j<$strlen;$j++){
            $num = ord($string{$j});
            if($num > 31)
                $chr = $string{$j}; else $chr = " ";
            printf("%4d: %08b : 0x%02x : %s \n",$j,$num,$num,$chr);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值