php开发-hj212环保协议2007

基础

1、基于TCP/IP


2、数据包示例

    ##0069QN=20230812235921004;ST=91;CN=9021;PW=123456;MN=000202;Flag=4;CP=&&&&1540
    
    ##0106QN=20230812233003569;ST=32;CN=3020;PW=123456;MN=000102;Flag=5;CP=&&DataTime=20230812233003;i12001-Info=4&&7D00
    
    ##0580QN=20230914000010688;ST=32;CN=2061;PW=123456;MN=3207xuweixinqu0000000233;Flag=4;CP=&&DataTime=20230913230000;w21001-Min=23.592,w21001-Max=23.592,w21001-Avg=23.592,w21001-Cou=0.000,w21001-Flag=N;w21011-Min=0.476,w21011-Max=0.476,w21011-Avg=0.476,w21011-Cou=0.000,w21011-Flag=N;w01018-Min=181.574,w01018-Max=181.574,w01018-Avg=181.574,w01018-Cou=0.000,w01018-Flag=N;w21003-Min=5.036,w21003-Max=5.036,w21003-Avg=5.036,w21003-Cou=0.000,w21003-Flag=N;w01001-Min=6.856,w01001-Max=6.874,w01001-Avg=6.864,w01001-Flag=N;w01010-Min=23.284,w01010-Max=23.411,w01010-Avg=23.349,w01010-Flag=N&&F6C1

    ##0620QN=20230813000015114;ST=32;CN=2051;PW=123456;MN=000101;Flag=5;CP=&&DataTime=20230812235000;w01018-Cou=0.0,w01018-Min=3547.8,w01018-Avg=3547.8,w01018-Max=3547.8,w01018-Flag=N;w21003-Cou=0.00,w21003-Min=129.63,w21003-Avg=129.63,w21003-Max=129.63,w21003-Flag=N;w21011-Cou=0.00,w21011-Min=0.43,w21011-Avg=0.43,w21011-Max=0.43,w21011-Flag=N;w21001-Cou=0.00,w21001-Min=67.35,w21001-Avg=67.35,w21001-Max=67.35,w21001-Flag=N;w01001-Min=6.65,w01001-Avg=6.65,w01001-Max=6.65,w01001-Flag=N;w01010-Min=26.2,w01010-Avg=26.5,w01010-Max=26.7,w01010-Flag=N;w00000-Cou=0.00,w00000-Min=0.00,w00000-Avg=0.00,w00000-Max=0.00,w00000-Flag=N&&2BC0

hj212解析

<?php
namespace app\hj212;

use app\hj212\feature\ParserFeature;
use app\hj212\model\verify\PacketElement;

/**
 * T212通信包解析器
 * Class T212Parser
 * @package App\core
 */
class T212Parser {

    public static $HEADER = '##';
    //需要双引号("")
    public static $FOOTER = "\r\n";

    protected $reader;
    //位置
    protected $readerIndex = 0;

    private $parserFeature;

    //TEMP
    private $count;
//    private PacketElement token;

    function __construct($reader = ''){
        $this->reader = $reader;

    }

    public function setReader($reader) {
        $this->reader = $reader;
        $this->readerIndex = 0;
    }

    /**
     * 设置解析特性
     * @param parserFeature 解析特性
     */
    function setParserFeature(int $parserFeature) {
        $this->parserFeature = $parserFeature;
    }

    /**
     * 读取 包头
     * @return mixed
     * @throws IOException
     */
    public function readHeader() {

//        $header = new char[2];
//        $count = $reader.read($header);

        $header = substr($this->reader,$this->readerIndex,PacketElement::HEADER);
        $this->readerIndex = $this->readerIndex + PacketElement::HEADER;
        $count = strlen($header);
        VerifyUtil::verifyEqualLen($count, PacketElement::HEADER, PacketElement::getElementVar());
        VerifyUtil::verifyChar($header, self::$HEADER, PacketElement::getElementVar());

        return $header;
    }

    /**
     * 读取 数据段长度
     * @see PacketElement#DATA_LEN
     * @return chars
     * @throws T212FormatException
     * @throws IOException
     */
    function readDataLen(){
        $len = substr($this->reader,$this->readerIndex,PacketElement::DATA_LEN);
        $this->readerIndex = $this->readerIndex + PacketElement::DATA_LEN;
        $count = strlen($len);
        VerifyUtil::verifyEqualLen($count,PacketElement::DATA_LEN, PacketElement::getElementVar());
        return $len;
    }

    /**
     * 读取 数据段
     * @see PacketElement#DATA
     * @return chars
     * @throws IOException
     */
    public function readData(int $segmentLen) {
        $segment = substr($this->reader,$this->readerIndex, $segmentLen);
        $this->readerIndex = $this->readerIndex + $segmentLen;
        $count = strlen($segment);
        VerifyUtil::verifyEqualLen($count,$segmentLen, PacketElement::getElementVar());
        return $segment;
    }

    /**
     * 读取 DATA_CRC 校验
     * @see PacketElement#DATA_CRC
     * @return header chars
     * @throws IOException
     */
    function readCrc() {
        $crc = substr($this->reader,$this->readerIndex, PacketElement::DATA_CRC);
        $this->readerIndex = $this->readerIndex + PacketElement::DATA_CRC;
        $count = strlen($crc);
        VerifyUtil::verifyEqualLen($count,PacketElement::DATA_CRC, PacketElement::getElementVar());
        return $crc;
    }

    /**
     * 读取 包尾
     * @see PacketElement
     * @return chars
     * @throws IOException
     */
    function readFooter() {
        $footer = substr($this->reader,$this->readerIndex,PacketElement::FOOTER);
        $this->readerIndex = $this->readerIndex + PacketElement::FOOTER;
        $count = strlen($footer);

        VerifyUtil::verifyEqualLen($count, PacketElement::FOOTER, PacketElement::getElementVar());
        VerifyUtil::verifyChar($footer, self::$FOOTER, PacketElement::getElementVar());

        return $footer;
    }

    /**
     * 读取 4字节Integer
     * @param radix 进制
     * @return Integer
     * @throws IOException
     */
    public function readCrcInt16() {
        $crc = substr($this->reader,$this->readerIndex, PacketElement::DATA_CRC);
//        $this->readerIndex = $this->readerIndex + PacketElement::DATA_CRC;
        $count = strlen($crc);

        if($count != 4){
            return -1;
        }
        //本身为十六进制字符串
        return $crc;
    }

    /**
     * 读取 data + 校验
     * @see PacketElement
     * @return chars
\     * @throws IOException
     */
    function readDataAndCrc(int $dataLen) {
        $data = substr($this->reader,$this->readerIndex, $dataLen);
        $this->readerIndex = $this->readerIndex + $dataLen;
        $count = strlen($data);
        VerifyUtil::verifyEqualLen($count,$dataLen, PacketElement::getElementVar());

        $crc = $this->readCrcInt16();
        $check_crc = self::crc16Checkout($data,$dataLen);

        if($crc != -1 && $check_crc == $crc){
            return $data;
        }
        return null;
    }

    /**
     * CRC校验
     * @param msg 消息
     * @param length 长度
     * @return DATA_CRC 校验码
     */
    public static function crc16Checkout($msg, int $length) {
        $crc_reg = 0xFFFF;
        for($i=0; $i<$length; $i++) {
            $crc_reg = ($crc_reg>>8) ^ ord($msg[$i]);
//            echo $crc_reg . "\r\n";
            for($j=0;$j<8;$j++) {
                $check = $crc_reg & 0x0001;
                $crc_reg >>= 1;
                if ($check == 0x0001) {
                    $crc_reg ^= 0xA001;
                }
            }
        }
        $res = strtoupper(dechex($crc_reg));
        $res = str_pad($res, 4, "0", STR_PAD_LEFT);
        return $res;
    }



}

Swoole接收处理

<?php
/**
 * Created by PhpStorm
 * USER: Zhaoys
 * Date: 2022/2/8 19:09
 */

namespace app\hj212\command;

use app\hj212\model\Data;
use app\hj212\model\Pollution;
use app\hj212\segment\converter\DataConverter;
use app\hj212\VerifyUtil;
use Swoole\Server as SwooleServer;
use app\hj212\T212Parser;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Exception;
use think\Db;

class Hj212Server extends Command
{
    // Server 实例
    protected $server;

    protected $t212Parser;

    protected $buffer;

    protected function configure()
    {
        $this->setName('hj212:start')->setDescription('Start hj212 Server!');
    }

    protected function execute(Input $input, Output $output)
    {
//        // 监听所有地址,监听端口f
        $this->server = new SwooleServer('0.0.0.0', 65212);

        $this->server->set(array(
            'worker_num' => 10,
//            'task_worker_num' => 2,   //必须设置 on task
            'max_request' => 10000,
//            'open_length_check' => true,
//            'package_length_func' => '',
            'daemonize' => true,
            'log_file' => "runtime/log/swoole.log",
            'dispatch_mode' => 2,
        ));
        $this->t212Parser = new T212Parser();
        $this->server->on('start', array($this, 'onStart'));

        $this->server->on('connect', array($this, 'onConnect'));
        $this->server->on('receive', array($this, 'onReceive'));

        $this->server->on('close', array($this, 'onClose'));
        $this->server->start();

    }


    public function onStart($serv)
    {
        echo "TCP Server is started at tcp://\n";
    }


    public function onConnect($serv, $fd)
    {
        echo "connection open: {$fd}\n";
        file_put_contents('runtime/log/hj212connect_' . date("Ymd") . '.log', date('Y-m-d H:i:s') ."ip".$this->getIP()."open: {$fd}");
    }

    public function onReceive($serv, $fd, $reactor_id, $data)
    {
        file_put_contents('runtime/log/hj212receive_' . date("Ymd") . '.log', date('Y-m-d H:i:s') . "receive:  " . $data . PHP_EOL, FILE_APPEND);

        $tempdata = rtrim($data, "\r\n");
        $strlen = strlen($tempdata);
        $endstr = substr($tempdata, $strlen - 6, 2);
        $strartstr = substr($data, 0, 2);
        if ($strartstr == "##" && $endstr != "&&") {
            $this->buffer = $data;
            return;
        } else if ($strartstr != "##" && $endstr == "&&" && $this->buffer && VerifyUtil::verifyCrc($this->buffer . $tempdata)) {
            $data = rtrim($this->buffer, "\r\n") . $data;
        } else if ($strartstr != "##" && $endstr == "&&" && $this->buffer && !VerifyUtil::verifyCrc($this->buffer . $tempdata)) {
            $this->buffer = rtrim($this->buffer, "\r\n") . $tempdata;
            return;
        //##1715QN=20230727145559023;ST=32;CN=3020;PW=123456;MN=000102;Flag=5;CP=&&Data
        }else if($strartstr == "##" && $endstr == "&&" && !VerifyUtil::verifyCrc($this->buffer . $tempdata)){
            $this->buffer = $data;
            return;
        }
        $sourceData = $data;
        $this->t212Parser->setReader($data);
        $this->t212Parser->readHeader();
        $dataLen = $this->t212Parser->readDataLen();
        $data = $this->t212Parser->readDataAndCrc($dataLen);
        $dataConverter = new DataConverter($data);
        $insetdata = $dataConverter->convertData();
        $insetdata['data_len'] = $dataLen;
        $insetdata['crc'] = $this->t212Parser->readCrcInt16();
        $insetdata['source_data'] = $sourceData;
        $cpData = $dataConverter->convertCpData();
        //存在数据空的情况 ##0069QN=20230723080035006;ST=91;CN=9021;PW=123456;MN=000201;Flag=4;CP=&&&&BDC1
        $insetdata['cp_datatime'] = isset($cpData['cpData']['datatime']) ? $cpData['cpData']['datatime'] : 0;
        Db::startTrans();
        try {
            $dataModel = new Data($insetdata);
            $dataModel->save();
            //            $pollutionModel = new Pollution();
            //            foreach ($cpData['pollution'] as $k => $val) {
            //                $pModel = clone $pollutionModel;
            //                $val['data_id'] = $dataModel->id;
            //                $val['code'] = $k;
            //                $pModel->data($val);
            //                $pModel->save();
            //            }
            Db::commit();
        } catch (Exception $e) {
            Db::rollback();
        }
        $str = "QN={$insetdata['qn']};ST=91;CN=9014;PW={$insetdata['pw']};MN={$insetdata['mn']};Flag=4;CP=&&&&\r\n";
        $num = strlen($str);
        $newNum = str_pad($num, 4, "0", STR_PAD_LEFT);
        $resStr = "##" . $newNum . $str;
        $this->buffer = "";
        $this->server->send($fd, $resStr);

    }

    public function onClose($serv, $fd)
    {
        echo "connection close: {$fd}\n";
    }

    /**
     * 同步任务
     * $this->serv->taskwait($data); 触发
     * @param $server
     * @param $task_id
     * @param $data
     */
    function onSyncTask($serv, $task_id, $from_id, $data)
    {
        echo "Sync task Callback: " . $data;

        $this->t212Parser->setReader($data);
        $header = $this->t212Parser->readHeader();
        $dataLen = $this->t212Parser->readDataLen();
        $data = $this->t212Parser->readDataAndCrc($dataLen);
        echo $data;

        $result = $data;
        // 通知完成
        $this->server->finish($result);
    }

    /**
     * 异步任务
     * $this->serv->task($data); 触发
     * @param $server
     * @param $task_id
     * @param $data
     */
    function onAsynTask($serv, $task_id, $from_id, $data)
    {
        echo "Asyn task Callback: ";
        // 通知完成
        $this->server->finish($data . "cccccsssss");

    }

    public function onFinish($serv, $task_id, $data)
    {
        echo "任务完成";//taskwait  不触发这个函数。。
    }

    public function getIP()
    {
        if (isset($_SERVER)){
            if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])){
                $realip = $_SERVER["HTTP_X_FORWARDED_FOR"];
            } else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
                $realip = $_SERVER["HTTP_CLIENT_IP"];
            } else {
                $realip = isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : '127.0.0.1';
            }
        } else {
            if (getenv("HTTP_X_FORWARDED_FOR")){
                $realip = getenv("HTTP_X_FORWARDED_FOR");
            } else if (getenv("HTTP_CLIENT_IP")) {
                $realip = getenv("HTTP_CLIENT_IP");
            } else {
                $realip = getenv("REMOTE_ADDR");
            }
        }
        return $realip;
    }

}