基础
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;
}
}