[PHP] 收发客户端与服务端

75 阅读5分钟

一.服务端的实现(使用开源Gateway项目部署在linux)

下载此项目代码Gitee 但是此项目缺少了3个文件:

start_register.php 
start_gateway.php 
start_businessworker.php

从此项目中下载demo获得这三个文件放入对应目录: Gateway手册

将项目部署在linux服务器中, 然后服务端入口代码如下, 其余文件可以不用看

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

use \GatewayWorker\Lib\Gateway;

/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 * 主要处理都是16进制处理
 */
class Events
{
       /**
     * 当客户端连接时触发
     * 如果业务不需此回调可以删除onConnect
     * @param int $client_id 连接id
     */
    public static function onConnect($client_id)
    {
        // 向当前client_id发送数据
        Gateway::sendToClient($client_id, "Hello $client_id");
        // 向所有人发送
        Gateway::sendToAll("$client_id, I am server, now I send message to you");
    }

      
    // 最新: 这里去除了首位的7e, 因为这个项目好像算的是去掉之后, 但他又没有去除
    public static  function onMessage($client_id, $message){

        $hex_string = bin2hex($message);
        $hex_string = ltrim($hex_string, '7e');
        $hex_string = rtrim($hex_string, '7e');
        echo "新的消息: ".$hex_string .PHP_EOL;
        $hanlder=new \YourApp\Handler\Jt808Handler($hex_string, $client_id);
        $hanlder->deal();

    }
}

二.Windows客户端的实现(使用workerman部署在windows)

开源项目地址: Github

  • 由于此项目的composer配置有问题, 所以不clone下来, 而是自己新建
  • 1.在windows新建一个空白目录
  • 2.直接安装composer官方扩展
composer require workerman/workerman
  • 3.在根目录下创建src目录, 再添加JT808.php文件
  • 4.在根目录添加客户端测试文件client.php, 然后终端输入php client.php 即可测试
<?php
namespace lttxws;
class JT808 {
	public static $sequenceNumber = 0; //流水号初始值
	/**
	 * description  转成10进制字符串数组
	 * @param $string 16进制字符串
	 * @return array   10进制数组
	 */
	public function get10Bytes($string) {
		$bytes = array();
		$len = strlen($string);
		for ($i = 0; $i < $len; $i++) {
			array_push($bytes, ord($string[$i]));
		}
		return $bytes;
	}

	/**
	 * description  10进制字符串数组转成16进制字符串数组
	 * @param $data 10进制字符串数组
	 * @return mixed 16进制字符串数组
	 */
	public function getTo16Bytes($data) {
		//$get10Bytes = $this->get10Bytes($data);
		$content = bin2hex($data);
		$res = explode('7e7e', $content);
		$array = [];
		//解决粘包
		if (count($res) > 1) {
			foreach ($res as $k => $v) {
				if ($k == reset($res)) {
					$array[$k] = str_split($v . '7e', 2);
				} else if ($k == end($res)) {
					$array[$k] = str_split('7e' . $v, 2);
				} else {
					$array[$k] = str_split('7e' . $v . '7e', 2);
				}
			}
		} else {
			$array[] = str_split($res[0], 2);
		}
		return $array;

		/*$array = [];
			foreach ($get10Bytes as $k => $v) {
				$array[$k] = base_convert($v, 10, 16);
			}
		*/
		//return $array;
	}

	/**
	 * description   接受到的16进制字符补0  例如:01=>0x01
	 * @param $data 16进制数组
	 * @return array  补0之后的16进制数组
	 */
	public function supplementZero($data) {
		$len = count($data);
		$res = [];
		for ($j = 0; $j < $len; $j++) {
			if (strlen($data[$j]) == 1) {
				$res[$j] = "0x" . "0" . $data[$j];
			} else {
				$res[$j] = "0x" . $data[$j];
			}
		}
		return $res;
	}

	/**
	 * description  把一个4位的数组转化位整形
	 * @param array  接受数组
	 * @return int  返回int
	 */
	public function bytesToInt($data) {
		$res = [];
		foreach ($data as $k => $v) {
			$res[$k] = intval(base_convert($v, 16, 10));
		}
		$temp0 = $res[0] & 0xFF;
		$temp1 = $res[1] & 0xFF;
		$temp2 = $res[2] & 0xFF;
		$temp3 = $res[3] & 0xFF;
		return (($temp0 << 24) + ($temp1 << 16) + ($temp2 << 8) + $temp3);
	}

	/**
	 * description  BCD码转字符串
	 * @param array  数组
	 * @return bool|string  返回字符串
	 */
	public function bcdToString($data) {
		$len = count($data);
		$temp = "";
		for ($i = 0; $i < $len; $i++) {
			// 高四位
			$temp .= (($data[$i] & 0xf0) >> 4);
			// 低四位
			$temp .= ($data[$i] & 0x0f);
		}
		return (substr($temp, 0, 1) == 0) ? substr($temp, 1) : $temp;
	}

	/**
	 * description  从接受到的16进制数组中获取设备号数组
	 * @param $data  接受到的16进制数组
	 * @return string 设备号id
	 */
	public function getSensorId($data) {
		$sensorArray = array_slice($data, 3, 6);
		$sensorArrayZero = $this->supplementZero($sensorArray);
		$res = [];
		foreach ($sensorArrayZero as $k => $v) {
			$res[$k] = intval(base_convert($v, 16, 10));
		}
		$string = $this->bcdToString($res);
		return $string;
	}

	/**
	 * description   把一个二字节数组转化成整型
	 * @param $data  二字节数组
	 * @return int   整型
	 */
	public function twoBytesToInteger($data) {
		$res = [];
		foreach ($data as $k => $v) {
			$res[$k] = intval(base_convert($v, 16, 10));
		}
		$temp0 = $res[0] & 0xFF;
		$temp1 = $res[1] & 0xFF;
		return (($temp0 << 8) + $temp1);
	}

	/**
	 * description  接受内容中4字节数组转成int
	 * @param $data 16进制字节数组
	 * @param int $a 开始位
	 * @return int   int值
	 */
	public function getNum($data, $a = 0) {
		$numArray = array_slice($data, $a, 4);
		$res = $this->bytesToInt($numArray);
		return $res;
	}

	/**
	 * description  按位异或
	 * @param $data
	 * @return int
	 */
	public function getEveryXor($data) {
		$len = count($data);
		$rew = 0;
		for ($i = 1; $i < $len; $i++) {
			$rew = $rew ^ $data[$i];
		}
		return $rew;
	}
	/**
	 * [checkCode 生成验证码带开头7e]
	 * @author litaotxws@163.com
	 * @DateTime 2020-05-01T00:19:06+0800
	 * @param    [array]                   $data [数组]
	 * @return   [array]                         [返回2位数数组]
	 */
	public function checkCode($data) {
		$sum = 0;
		//去掉开头的7e
		unset($data[0]);
		//for ($i = 0; $i < count($arr); $i++) {
		foreach ($data as $k => $v) {
			$sum = $sum ^ hexdec($v);
		}
		return str_pad(dechex($sum), 2, "0", STR_PAD_LEFT);
	}
	/**
	 * description   将字节数组转为字符串
	 * @param array   字节数组
	 * @return string   返回字符串
	 */
	public function bytesArrayToString($data) {
		$str = '';
		foreach ($data as $ch) {
			$str .= chr($ch);
		}
		return $str;
	}
	/**
	 * [arrayToBytes 返回二进制内容]
	 * @author litaotxws@163.com
	 * @DateTime 2020-05-01T00:34:33+0800
	 * @param    [type]                   $data [数组]
	 * @return   [type]                         [二进制内容]
	 */
	public function arrayToBytes($data) {
		$ret = implode($data);
		return hex2bin($ret);
	}
	/**
	 * description 拼接字符串
	 * @param $str
	 * @param int $n
	 * @param string $char
	 * @return string
	 */
	public function getTurnStr($str, $n = 1, $char = '0') {
		for ($i = 0; $i < $n; $i++) {
			$str = $char . $str;
		}
		return $str;

		//return str_pad($str, $n, $char, STR_PAD_LEFT);
	}

	/**
	 * description  转成二进制字符串
	 * @param $data array 16进制数组
	 * @return string  字符串
	 */
	public function getTwoStr($data) {
		//转成2进制
		$str = array();
		$req = array();
		foreach ($data as $key => $value) {
			$str[$key] = base_convert($data[$key], 16, 2);
			$leng = 8 - strlen($str[$key]);
			$req[] = $this->getTurnStr($str[$key], $leng, "0");
		}
		//拼接字符串
		$rtq = implode("", $req);
		return $rtq;
	}
	/**
	 * description  获取设备号
	 * @param $data array 16进制数组
	 * @param $length num 补全长度
	 * @return bool|string   返回字符串设备号
	 */
	public function getEquipmentNumber($data, $length = 12) {
		$equipmentArray = array_slice($data, 5, 6);
		$res = [];
		foreach ($equipmentArray as $k => $v) {
			$res[$k] = base_convert($v, 16, 10);
		}
		$equipmentNumber = $this->bcdToString($res);
		return str_pad($equipmentNumber, $length, "0", STR_PAD_LEFT);
	}
	/**
	 * description  获取16进制数组来计算出设备号
	 * @param $data 16进制数组
	 * @return array  返回设备号数组
	 */
	public function getEquipmentNumberArray($data) {
		$num_array = array_slice($data, 5, 6);
		/*$res = [];
			foreach ($num_array as $k => $v) {
				$res[$k] = base_convert($v, 16, 10);
			}
		*/
		return $num_array;
	}
	/**
	 * description  获取报警信息
	 * @param $data array 16进制数组
	 * @param $index  数组索引13
	 * @return int  返回数字代表报警信息
	 */
	public function getAlarmMessage($data, $index, $type = false) {
		$alarmArray = $this->getTwoStr(array_slice($data, $index, 4));
		if ($type == true) {
			if (substr($alarmArray, -8, 1) == 1) {
				//主电源断电
				$alarm = "主电源断电";
			} elseif (substr($alarmArray, -30, 1) == 1) {
				//碰撞预警
				$alarm = "碰撞预警";
			} elseif (substr($alarmArray, -31, 1) == 1) {
				//侧翻预警
				$alarm = "侧翻预警";
			}
//        elseif (substr($alarmArray, -26, 1) == 1) {
			//            //脱落(光感)报警
			//            $alarm = 4;
			//        }
			else {
				//正常
				$alarm = "一切正常";
			}
		}
		// return $alarm;
		return $alarmArray;
	}

	/**
	 * description   获取状态信息
	 * @param $data array 16进制数组
	 * @param $index   数组索引
	 * @param $type   返回数据还是原本信息
	 * @return array  状态信息数组
	 */
	public function getPositionStatus($data, $index, $type = false) {
		$positionArray = $this->getTwoStr(array_slice($data, $index, 4));
		if ($type == true) {
			//判断是否定位,0定位,1未定位
			$isPosition = substr($positionArray, -2, 1) == 0 ? $isPosition = "未定位" : $isPosition = "定位";
			//判断南北纬,0北纬,1南纬
			$isNorSou = substr($positionArray, -3, 1) == 0 ? $isNorSou = "北纬" : $isNorSou = "南纬";
			//判断东西经,0东经,1西经
			$isEasWes = substr($positionArray, -4, 1) == 0 ? $isEasWes = "东经" : $isEasWes = "西经";
			//判断定位方式
			if (substr($positionArray, -19, 1) == 1 && substr($positionArray, -20, 1) == 0) {
				//北斗定位
				$positionMethod = "北斗定位";
			} elseif (substr($positionArray, -19, 1) == 0 && substr($positionArray, -20, 1) == 1) {
				//GPS定位
				$positionMethod = "GPS定位";
			} elseif (substr($positionArray, -19, 1) == 1 && substr($positionArray, -20, 1) == 1) {
				//北斗GPS双定位
				$positionMethod = "北斗GPS双定位";
			} else {
				//北斗GPS都未定位
				$positionMethod = "北斗GPS都未定位";
			}
			$positionStatusArray = array(
				'position' => $isPosition,
				'ns' => $isNorSou,
				'ew' => $isEasWes,
				'gps' => $positionMethod,
			);
			return $positionStatusArray;
		} else {
			return $positionArray;
		}
	}

	/**
	 * description   获取纬度
	 * @param $data array  16进制数组
	 * @param $index  数组索引
	 * @param $type  返回字符类型 i整形 f浮点形
	 * @return float|int   纬度
	 */
	public function getLatitude($data, $index, $type = 'f') {
		$latitudeBytes = array_slice($data, $index, 4);
		$latitude = $this->bytesToInt($latitudeBytes);
		if ($type == 'f') {
			$number = $latitude / pow(10, 6);
		}
		if ($type == 'i') {
			$number = $latitude;
		}
		return $number;
	}

	/**
	 * description  获取经度
	 * @param $data array  16进制数组
	 * @param $index  数组索引
	 * @param $type  返回字符类型 i整形 f浮点形
	 * @return float|int  经度
	 */
	public function getLongitude($data, $index, $type = 'f') {
		$longitudeBytes = array_slice($data, $index, 4);
		$longitude = $this->bytesToInt($longitudeBytes);
		if ($type == 'f') {
			$number = $longitude / pow(10, 6);
		}
		if ($type == 'i') {
			$number = $longitude;
		}
		return $number;
	}
	/**
	 * [getHeight 获取高度]
	 * @author litaotxws@163.com
	 * @DateTime 2020-04-30T14:12:30+0800
	 * @param    [type]                   $data  [16进制数组]
	 * @param    [type]                   $index [数组索引29]
	 * @return   [type]                          [高度]
	 */
	public function getHeight($data, $index) {
		$heightBytes = array_slice($data, $index, 2);
		$height = $this->twoBytesToInteger($heightBytes);
		return $height;
	}
	/**
	 * [getSpeed 获取速度]
	 * @author litaotxws@163.com
	 * @DateTime 2020-04-30T14:14:55+0800
	 * @param    [type]                   $data  [16进制数组]
	 * @param    [type]                   $index [数组索引31]
	 * @return   [type]                          [速度]
	 */
	public function getSpeed($data, $index) {
		$speedBytes = array_slice($data, $index, 2);
		$speed = $this->twoBytesToInteger($speedBytes);
		return $speed;
	}
	/**
	 * [getDirection 获取方向]
	 * @author litaotxws@163.com
	 * @DateTime 2020-04-30T14:15:33+0800
	 * @param    [type]                   $data  [16进制数组]
	 * @param    [type]                   $index [数组索引33]
	 * @return   [type]                          [方向]
	 */
	public function getDirection($data, $index) {
		$directionBytes = array_slice($data, $index, 2);
		$direction = $this->twoBytesToInteger($directionBytes);
		return $direction;
	}
	/**
	 * description  获取日期时间
	 * @param $data array  16进制数组
	 * @param $index  数组索引35
	 * @return string   日期时间字符串
	 */
	public function getDatetime($data, $index) {
		$datetimeArray = array_slice($data, $index, 6);
		$res = [];
		foreach ($datetimeArray as $k => $v) {
			$res[$k] = base_convert($v, 16, 10);
		}
		$datetime = $this->bcdToString($res);
		$datetimeStr = "20" . substr($datetime, 0, 2) . "-" . substr($datetime, 2, 2) . "-" . substr($datetime, 4, 2) . " " . substr($datetime, 6, 2) . ":" . substr($datetime, 8, 2) . ":" . substr($datetime, 10, 2);
		return $datetimeStr;
	}

	/**
	 * description   获取平台流水号
	 * @return array   返回流水号数组
	 */
	public function getSequenceNumberArray() {
		//计算流水号
		$number = $this->$sequenceNumber++;
		if ($number > 65025) {
			// 255 * 255 -1
			$number = 1;
		}
		//将十进制流水号换算成16进制流水号
		$get16Number = base_convert($number, 10, 16);
		$af = substr($get16Number, 0, 2);
		$bf = substr($get16Number, 2);
		$systemNumber = [];
		//判断
		if ($number > 0xff) {
			$systemNumber = array('0x' . $af, '0x' . $bf);
		} else {
			$systemNumber = array('0x00', '0x' . $get16Number);
		}
		foreach ($systemNumber as $k => $v) {
			$systemNumber[$k] = intval(base_convert($v, 16, 10));
		}
		return $systemNumber;
	}

	/**
	 * description  获取消息流水号
	 * @param $data 16进制数组
	 * @return array  消息流水号数组
	 */
	public function getMessageNumberArray($data) {
		$messageNumber = array_slice($data, 11, 2);
		//$messageNumber = $this->supplementZero($messageNumber);
		return $messageNumber;
	}

	/**
	 * description  获取消息id
	 * @param $data 16进制数组
	 * @return array  消息id数组
	 */
	public function getMessageIdArray($data) {
		$messageId = array_slice($data, 1, 2);
		//$messageId = $this->supplementZero($messageId);
		return $messageId;
	}
	/**
	 * description  获取消息id
	 * @param $data array 16进制数组
	 * @param $length num 消息体长度 前位补0
	 * @return bool|string   消息id字符串
	 */
	public function getMessageIdNumber($data, $length = 4) {
		$messageArray = array_slice($data, 1, 2);
		$res = [];
		foreach ($messageArray as $k => $v) {
			$res[$k] = base_convert($v, 16, 10);
		}
		$messageNumber = $this->bcdToString($res);
		return str_pad($messageNumber, $length, "0", STR_PAD_LEFT);
	}
	/**
	 * description   获取消息体
	 * @param $data 16进制数组
	 * @return array   消息体数组
	 */
	public function getMessageBodyArray($data) {
		//消息体 = 消息流水号 + 消息id
		$messageNumber = $this->getMessageNumberArray($data);
		//$messageId = $this->getMessageIdArray($data);
		//$messageBody = array_merge($messageNumber, $messageId);
		/*foreach ($messageBody as $k => $v) {
				$res[$k] = intval(base_convert($v, 16, 10));
			}
		*/
		return $messageNumber;
	}

	/**
	 * description  发送给客户端的回传数据
	 * @param $data 16进制数组
	 * @return string   返回客户端字符串
	 */
	public function getVerifyNumberArray($data, $auth = '31313131') {
		//数组开始五位
		//$arrayStartFiveBytes = array('7E', '80', '01');
		//注册应答结果0
		$ret = array('00');
		//消息ID
		$messageId = $this->getMessageIdArray($data);
		$messageid = implode($messageId);
		//消息体
		$messageBody = $this->getMessageBodyArray($data);
		if ($messageid == '0100') {
			$arrayStartFiveBytes = array('7E', '81', '00');
			$jianquan = str_split($auth, 2);
			$messageBody = array_merge($messageBody, $ret, $jianquan);
		} else {
			$arrayStartFiveBytes = array('7E', '80', '01');
			$jianquan = [];
			$messageBody = array_merge($messageBody, $messageId, $ret, $jianquan);
		}
		//设备号
		$equipmentNumber = $this->getEquipmentNumberArray($data);
		//平台流水号
		//$systemNumber = $this->getSequenceNumberArray();
		$systemNumbers = $this->getMessageNumberArray($data);

		//消息体长度
		/*if ($messageid == '0100') {
			$msglength = count(array_merge($systemNumbers, $messageId, $ret, $jianquan));
		} else {
			$msglength = count(array_merge($systemNumbers, $messageId, $ret));
		}*/
		$msglength = count(array_merge($systemNumbers, $messageId, $ret, $jianquan));
		$msglength = decbin($msglength);

		//补齐16位,不加密,无版本号
		$attr = sprintf("%016d", $msglength); //消息体属性
		//前置补0
		$attr_str = str_pad(dechex(bindec($attr)), 4, '0', STR_PAD_LEFT);
		//$attr_str = dechex(bindec($attr));
		//分割字符
		$attrarray = str_split($attr_str, 2);
		//与消息体合并
		$array_attr = array_merge($arrayStartFiveBytes, $attrarray);
		//数组开始5位和设备号合并
		$arrayStartAndEquipmentNumber = array_merge($array_attr, $equipmentNumber);
		//接上一步继续与平台流水号合并
		$startEquipmentAndSystemNumber = array_merge($arrayStartAndEquipmentNumber, $systemNumbers);
		//接上一步继续与消息体合并
		$startEquipmentSystemAndMessageBody = array_merge($startEquipmentAndSystemNumber, $messageBody);
		//接上一步应答结果
		//$dataAndRet = array_merge($startEquipmentSystemAndMessageBody, $ret);
		$dataAndRet = $startEquipmentSystemAndMessageBody;
		//生成校验码
		//$dataAndRetXor = $this->getEveryXor($dataAndRet);
		$dataAndRetXor = $this->checkCode($dataAndRet);
		//数组末尾两位
		$arrayEndTwoBytes = array($dataAndRetXor, '7E');
		//整个数组
		$completeArray = array_merge($dataAndRet, $arrayEndTwoBytes);
		//发送给客户端的字符串
		$sendClientStr = $this->arrayToBytes($completeArray);

		return $sendClientStr;
	}
}
?>
  • client.php
<?php

/**
 *  说明: 这份文件使用workerman运行
 *  使用方法, 
 *      1.新建一个空白目录,composer require workerman/workerman
 *      2.把这个文件放在根目录下, php client.php (改名) 运行
 * 
 */

require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;
use \Workerman\Lib\Timer;

//获取校验码
function calCheckSum($hex_string)
{
    $cs = hexdec(substr($hex_string, 0, 2));
    $str_len = strlen($hex_string);
    for ($i = 2; $i < $str_len; $i += 2) {
        $cs ^= hexdec(substr($hex_string, $i, 2));
    }
    return bin2hex(pack('C',$cs));
}

$worker = new Worker();
$status = true;
$worker->onWorkerStart = function () {
	// 以websocket协议连接远程websocket服务器 ws://IP:端口
	$ws_connection = new AsyncTcpConnection('tcp://IP:8282');

	// 连接成功
	$ws_connection->onConnect = function ($connection) {

		//注册
		$msg = "7e0100002d019501137397001800000000383838383800000000000000000000000000000000000000003131333733393702d4c1423030303030327e";
		//心跳包
		$msg1 = '7e0002000001453409748303c8477e7e0200003a0145340972520bdb00000000000000030205d4b406b9efe800ed00000120200430194209010400000716eb16000c00b28986043103188076978800060089fffffffff77e';
		//位置信息上报
		$msg2 = '7e0200003a01453408683900b600000000000000030205b1f006b97930015e00000111200430182601010400000018eb16000c00b28986043103188076975700060089ffffffffb67e';
		$data = hex2bin($msg);
		$data1 = hex2bin($msg1);
		$data2 = hex2bin($msg2);
		//定时3秒
		$time_interval = 3;

        $diy_data_heart = hex2bin('7e00020018013739700000295011800746573745f617574685f3737de7e');
        $diy_data_location = hex2bin('7e020000181137397000002950118014e3dcbf347b245d06863dc0ad44f013f401032153b6d937e');

		//改为data为注册 data1心跳包 data2位置信息上报
		$sendData = $diy_data_location;
		// 给connection对象临时添加一个timer_id属性保存定时器id
		$connection->timer_id = Timer::add($time_interval, function () use ($connection, $sendData) {
			$connection->send($sendData);
		});

	};

	// 远程websocket服务器发来消息时
	$ws_connection->onMessage = function ($connection, $data) {
		$data1 = bin2hex($data);
		echo "receive: $data1\n";
        $code = calCheckSum($data1);
        echo '校验码:' .$code . PHP_EOL;
	};

	// 连接上发生错误时,一般是连接远程websocket服务器失败错误
	$ws_connection->onError = function ($connection, $code, $msg) {
		echo "error: $msg\n";
	};

	// 当连接远程websocket服务器的连接断开时
	$ws_connection->onClose = function ($connection) {
		echo "connection closed\n";
		// 连接关闭时,删除对应连接的定时器
		// 删除定时器
		Timer::del($connection->timer_id);

	};
	// 设置好以上各种回调后,执行连接操作
	$ws_connection->connect();

};
// 执行
Worker::runAll();