目录
MODBUS-RTU
1、Modbus Slave连接串口
2、MODBUS-RTU配置环境
3、ModBus-RTU的通信代码
4、ModBus-RTU执行的方法细节,简说
MODBUS-TCP 与UDP
1、 配置模拟传感器TCP方法连接
2、引入TCP连接需要的包
3、UDP的读的方式修改如下,其他如TCP一样
基于Socket通信(DTU设备连接)
我服务端设计的逻辑如下:
服务器端代码如下
用到的工具类代码
Socket连接的线程如下
线程主要业务逻辑如下
因为项目要用到连接传感器的业务需求,所以学习了这几种协议,并记录下来,下面所有的代码,我都使用过,并且能够拿取数据。(下面是我参考网上代码做的学习测试总结)
MODBUS-RTU
1、Modbus Slave连接串口
一、简单说一下:
(1)、Modbus Slave是模拟传感器,连接串口的,相当于模拟的传感器
(2)、Configure Virtual Serial Port Driver这个软件是,开启串口的软件,可以在电脑开模拟串口
二、Modbus Slave以 Serial Port串口的方式连接,并且连接到COM2端口下面
三、查看配置传感器参数
点击传感器,右键Slave Definition ,配置传感器ID,配置(03 Holding Register),线圈方式读取传感器的值
2、MODBUS-RTU配置环境
由于当时,第一次学习弄得就是RTU的环境,所以出现的坑,我也给大家说明,当时遇到报错连接
1、如果出现下面报错,则是缺少RXTX的脚本文件
资源我放在这里:配套资源
将文件拷贝到java的JDK的bin目录就行了
2、引入包
拷贝上面资源中的三个包,并把它引入SpringBoot项目中(这样基础的环境已经配置完成)
3、ModBus-RTU的通信代码
1、封装实体类
public class SerialPortWrapperImpl implements SerialPortWrapper {private final Logger log = LoggerFactory.getLogger(this.getClass());/*** 串口对象*/private SerialPort serialPort;/*** 串口*/private String port;/*** 波特率*/private Integer baudRate;/*** 数据位的位数,RTU是8位,ASCII是7位*/private Integer dataBits;/*** 停止位的位数,如果无奇偶校验为2,有奇偶校验为1*/private Integer stopBits;/*** 奇偶校验位,无校验是0,奇校验是1,偶校验是2*/private Integer parity;/*** 硬件之间输入流应答控制*/private Integer flowControlIn;/*** 硬件之间输出流应答控制*/private Integer flowControlOut;public SerialPortWrapperImpl() {super();}public SerialPortWrapperImpl(String port, int baudRate, int dataBits, int stopBits, int parity,int flowControlIn, int flowControlOut) {this.port = port;this.baudRate = baudRate;this.dataBits = dataBits;this.stopBits = stopBits;this.parity = parity;this.flowControlIn = flowControlIn;this.flowControlOut = flowControlOut;}@Overridepublic void close() throws Exception {SerialPortUtils.close(serialPort);}@Overridepublic void open() throws Exception {serialPort = SerialPortUtils.open(port, baudRate, dataBits, stopBits, parity);}@Overridepublic InputStream getInputStream() {InputStream in = null;try {in = serialPort.getInputStream();} catch (IOException e) {log.error("获取串口输入流错误", e);}return in;}@Overridepublic OutputStream getOutputStream() {OutputStream out = null;try {out = serialPort.getOutputStream();} catch (IOException e) {log.error("获取串口输出流错误", e);}return out;}@Overridepublic int getBaudRate() {return this.baudRate;}@Overridepublic int getDataBits() {return this.dataBits;}@Overridepublic int getStopBits() {return this.stopBits;}@Overridepublic int getParity() {return this.parity;}public int getFlowControlIn() {return this.flowControlIn;}public int getFlowControlOut() {return this.flowControlOut;}public SerialPort getSerialPort() {return serialPort;}public void setSerialPort(SerialPort serialPort) {this.serialPort = serialPort;}public String getPort() {return port;}public void setPort(String port) {this.port = port;}public void setBaudRate(Integer baudRate) {this.baudRate = baudRate;}public void setDataBits(Integer dataBits) {this.dataBits = dataBits;}public void setStopBits(Integer stopBits) {this.stopBits = stopBits;}public void setParity(Integer parity) {this.parity = parity;}public void setFlowControlIn(Integer flowControlIn) {this.flowControlIn = flowControlIn;}public void setFlowControlOut(Integer flowControlOut) {this.flowControlOut = flowControlOut;}}
2、方法类的封装
public class SerialPortUtils implements SerialPortEventListener {// 检测系统中可用的通讯端口类private CommPortIdentifier commPortId;// 枚举类型private Enumeration<CommPortIdentifier> portList;// RS232串口private SerialPort serialPort;// 输入流private InputStream inputStream;// 输出流private OutputStream outputStream;// 保存串口返回信息private String data;// 保存串口返回信息十六进制private String dataHex;/*** 初始化串口** @throws* @author LinWenLi* @date 7月21日下午3:44:16* @Description: TODO* @param: paramConfig 存放串口连接必要参数的对象(会在下方给出类代码)* @return: void*/@SuppressWarnings("unchecked")public void init() throws SerialPortException {// 获取系统中所有的通讯端口portList = CommPortIdentifier.getPortIdentifiers();// 记录是否含有指定串口boolean isExsist = false;// 循环通讯端口while (portList.hasMoreElements()) {commPortId = portList.nextElement();// 判断是否是串口if (commPortId.getPortType() == CommPortIdentifier.PORT_SERIAL) {// 比较串口名称是否是指定串口if ("COM7".equals(commPortId.getName())) {// 串口存在isExsist = true;// 打开串口try {// open:(应用程序名【随意命名】,阻塞时等待的毫秒数)serialPort = (SerialPort) commPortId.open(Object.class.getSimpleName(), 2000);// 设置串口监听serialPort.addEventListener(this);// 设置串口数据时间有效(可监听)serialPort.notifyOnDataAvailable(true);// 设置串口通讯参数:波特率,数据位,停止位,校验方式serialPort.setSerialPortParams(9600, 8,1, 0);} catch (PortInUseException e) {throw new SerialPortException("端口被占用");} catch (TooManyListenersException e) {throw new SerialPortException("监听器过多");} catch (UnsupportedCommOperationException e) {throw new SerialPortException("不支持的COMM端口操作异常");}// 结束循环break;}}}// 若不存在该串口则抛出异常if (!isExsist) {throw new SerialPortException("不存在该串口!");}}/*** 实现接口SerialPortEventListener中的方法 读取从串口中接收的数据*/@Overridepublic void serialEvent(SerialPortEvent event) {}/*** 读取串口返回信息** @author LinWenLi* @date 7月21日下午3:43:04* @return: void*/public void readCommPort() throws SerialPortException {}/*** 发送信息到串口** @throws* @author LinWenLi* @date 7月21日下午3:45:22* @param: data* @return: void*/public void sendComm(String data) throws SerialPortException {byte[] writerBuffer = null;try {// writerBuffer = hexToByteArray(data);writerBuffer = data.getBytes(StandardCharsets.UTF_8);} catch (NumberFormatException e) {throw new SerialPortException("命令格式错误!");}try {outputStream = serialPort.getOutputStream();outputStream.write(writerBuffer);outputStream.flush();} catch (NullPointerException e) {throw new SerialPortException("找不到串口。");} catch (IOException e) {throw new SerialPortException("发送信息到串口时发生IO异常");}}/*** 关闭串口** @throws* @author LinWenLi* @date 7月21日下午3:45:43* @Description: 关闭串口* @param:* @return: void*/public void closeSerialPort() throws SerialPortException {if (serialPort != null) {serialPort.notifyOnDataAvailable(false);serialPort.removeEventListener();if (inputStream != null) {try {inputStream.close();inputStream = null;} catch (IOException e) {throw new SerialPortException("关闭输入流时发生IO异常");}}if (outputStream != null) {try {outputStream.close();outputStream = null;} catch (IOException e) {throw new SerialPortException("关闭输出流时发生IO异常");}}serialPort.close();serialPort = null;}}/*** 十六进制串口返回值获取*/public String getDataHex() {String result = dataHex;// 置空执行结果dataHex = null;// 返回执行结果return result;}/*** 串口返回值获取*/public String getData() {String result = data;// 置空执行结果data = null;// 返回执行结果return result;}/*** Hex字符串转byte** @param inHex 待转换的Hex字符串* @return 转换后的byte*/public static byte hexToByte(String inHex) {return (byte) Integer.parseInt(inHex, 16);}/*** hex字符串转byte数组** @param inHex 待转换的Hex字符串* @return 转换后的byte数组结果*/public static byte[] hexToByteArray(String inHex) {int hexlen = inHex.length();byte[] result;if (hexlen % 2 == 1) {// 奇数hexlen++;result = new byte[(hexlen / 2)];inHex = "0" + inHex;} else {// 偶数result = new byte[(hexlen / 2)];}int j = 0;for (int i = 0; i < hexlen; i += 2) {result[j] = hexToByte(inHex.substring(i, i + 2));j++;}return result;}/*** 数组转换成十六进制字符串** @param bArray* @return HexString*/public static final String bytesToHexString(byte[] bArray) {StringBuffer sb = new StringBuffer(bArray.length);String sTemp;for (int i = 0; i < bArray.length; i++) {sTemp = Integer.toHexString(0xFF & bArray[i]);if (sTemp.length() < 2)sb.append(0);sb.append(sTemp.toUpperCase());}return sb.toString();}/*** 打卡串口* @param portName 串口名* @param baudRate 波特率* @param dataBits 数据位* @param stopBits 停止位* @param parity 校验位* @return 串口对象*/public static SerialPort open(String portName, Integer baudRate, Integer dataBits,Integer stopBits, Integer parity) {SerialPort result = null;try {// 通过端口名识别端口CommPortIdentifier identifier = CommPortIdentifier.getPortIdentifier(portName);// 打开端口,并给端口名字和一个timeout(打开操作的超时时间)if(identifier.isCurrentlyOwned()) {return null;}CommPort commPort = identifier.open(portName, 2000);// 判断是不是串口if (commPort instanceof SerialPort) {result = (SerialPort) commPort;// 设置一下串口的波特率等参数result.setSerialPortParams(baudRate, dataBits, stopBits, parity);}else{}} catch (Exception e) {e.printStackTrace();}return result;}/*** 关闭串口* @param serialPort*/public static void close(SerialPort serialPort) {if (serialPort != null) {serialPort.close();}}
3、通讯的方法
@Componentpublic class ModbusRtuMaster {// 检测系统中可用的通讯端口类private static SerialPortWrapperImpl wrapper;private static ModbusMaster master;private static ModbusFactory modbusFactory;//private static ModbusRtuMaster modbusRtuMaster;public ModbusRtuMaster() {}/** public static synchronized ModbusRtuMaster getInstance() { if(modbusRtuMaster* == null) { modbusRtuMaster = new ModbusRtuMaster(); } return modbusRtuMaster;* }*//*** 端口是否在使用** @param serialNumber* @return*/public boolean inUse(String serialNumber) {boolean bl = false;if (this.wrapper == null) {return bl;}if (this.wrapper.getPort() != null && this.wrapper.getPort().equalsIgnoreCase(serialNumber)) {bl = master.isInitialized();}return bl;}public void createRtuMaster(String serialNumber, int baudRate, int dataBit, int stopBit, int checkoutBit) throws Exception {//relayParamConfig.getSerialNumber(), relayParamConfig.getBaudRate(),//relayParamConfig.getDataBit(),relayParamConfig.getStopBit(), relayParamConfig.getCheckoutBit()// 设置串口参数,串口是COM1,波特率是9600wrapper = new SerialPortWrapperImpl(serialNumber, baudRate,dataBit, stopBit, checkoutBit, 0, 0);//SerialPort.PARITY_NONEmodbusFactory = new ModbusFactory();// System.out.println("relayParamConfig.getSerialNumber()->"+relayParamConfig.getSerialNumber());master = modbusFactory.createRtuMaster(wrapper);master.init();// 从站设备ID是1// int slaveId = 1;// 读取保持寄存器// readHoldingRegisters(master, slaveId, 0, 3);// 将地址为0的保持寄存器数据修改为0// writeRegister(master, slaveId, offset, value);// 再读取保持寄存器// readHoldingRegisters(master, slaveId, 0, 3);// short[] s = {00};// controlRelay(master, slaveId, 0, s);// RtuMasterTest.writeRegistersTest(master, slaveId, 0, s);}/** public void init(RelayParamConfig relayParamConfig) throws Exception {* this.relayParamConfig = relayParamConfig;* if(!this.inUse(relayParamConfig.getSerialNumber())) { this.createRtuMaster();* } }*/public void closeRtuMaster() throws Exception {wrapper.close();}public void controlRelay(int slaveId, int offset, int value) throws Exception {writeRegister(master, slaveId, offset, value);readHoldingRegisters(master, slaveId, 1, 3);// this.closeRtuMaster();}public short[] readRelay(int slaveId, int start, int len) throws Exception {short[] shorts = readHoldingRegisters(master, slaveId, start, len);return shorts;}// 读取保持寄存器// readHoldingRegisters(master, slaveId, 0, 3);// 将地址为0的保持寄存器数据修改为0// writeRegister(master, slaveId, offset, value);private short[] readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) throws Exception {ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);if (response.isException()) {System.out.println("读取保持寄存器错误,错误信息是" + response.getExceptionMessage());} else {System.out.println("读取保持寄存器=" + Arrays.toString(response.getShortData()));}return response.getShortData();}private void writeRegister(ModbusMaster master, int slaveId, int offset, int value) throws Exception {WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);ByteQueue queue = new ByteQueue();WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);if (response.isException()) {System.out.println("写保持寄存器错误,错误信息是" + response.getExceptionMessage());} else {System.out.println("指令发送成功!");}}public void writeRegistersTest(ModbusMaster master, int slaveId, int start, short[] values) {try {WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);System.out.println("request:" + request);System.out.println("FunctionCode:" + request.getFunctionCode());System.out.println("SlaveId:" + request.getSlaveId());WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);if (response.isException())System.out.println("Exception response: message=" + response.getExceptionMessage());else {System.out.println("Success");}} catch (ModbusTransportException e) {e.printStackTrace();}}public static void main(String[] args) {ModbusRtuMaster rmt = new ModbusRtuMaster();try {rmt.createRtuMaster("COM3", 9600, 8, 1, 0);for (int i = 0; i < 10; i++) {rmt.controlRelay(1, 2, 3);}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}
4、ModBus-RTU执行的方法细节,简说
1、就执行的代码而言,简单讲解一下,具体自己分析
public static void main(String[] args) {ModbusRtuMaster rmt = new ModbusRtuMaster();try {rmt.createRtuMaster("COM3", 9600, 8, 1, 0);for (int i = 0; i < 10; i++) {rmt.controlRelay(1, 2, 3);}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}
2、因为上面传感器连接的串口是(com2-com3),所以相当于传感器连接的是com2,我们要通过com3来访问传感器数据,一个串口,可以连接多个传感器
rmt.createRtuMaster("COM3", 9600, 8, 1, 0);
这个是连接传感器状态(四个参数分别是:串口,波特率,停止位数,奇偶效验)
/*** 串口*/private String port;/*** 波特率*/private Integer baudRate;/*** 数据位的位数,RTU是8位,ASCII是7位*/private Integer dataBits;/*** 停止位的位数,如果无奇偶校验为2,有奇偶校验为1*/private Integer stopBits;/*** 奇偶校验位,无校验是0,奇校验是1,偶校验是2*/private Integer parity;
3、下面这个是main函数中,for循环的调用的函数,
第一个方法:writeRegister()是写线圈(03)的方法,传入四个参数salveID(传感器的ID值),offset(写入第几位,从零开始数),value(写入的值)
第二个方法:readHoldingRegisters()是读取线圈(03)的方法,传入slaveID(传感器ID),以及读取的开始长度(我写的是1),读取长度(我写的是3)返回的是一个数组
public void controlRelay(int slaveId, int offset, int value) throws Exception {writeRegister(master, slaveId, offset, value);readHoldingRegisters(master, slaveId, 1, 3);// this.closeRtuMaster();}
4、写入传感器线圈的方法
private void writeRegister(ModbusMaster master, int slaveId, int offset, int value) throws Exception {WriteRegisterRequest request = new WriteRegisterRequest(slaveId, offset, value);ByteQueue queue = new ByteQueue();WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);if (response.isException()) {System.out.println("写保持寄存器错误,错误信息是" + response.getExceptionMessage());} else {System.out.println("指令发送成功!");}}
5、读取线圈的方法,返回一个short数组
private short[] readHoldingRegisters(ModbusMaster master, int slaveId, int start, int len) throws Exception {ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);if (response.isException()) {System.out.println("读取保持寄存器错误,错误信息是" + response.getExceptionMessage());} else {System.out.println("读取保持寄存器=" + Arrays.toString(response.getShortData()));}return response.getShortData();}
执行代码运行如下,ModBus-RTU,读写都是分开的,可以单独读,也可以单独写,一般都是读
6、我把写的方法注释了,然后打开软件查看读的报文记录
报文如下
读取的方法如下,上面是返回的报文信息
readHoldingRegisters(master, slaveId, 1, 3);
蓝色的第一行表示读的报文
读的报文:01:表示读取传感器的ID为1;
03:表示读取传感器种类03是线圈类型
00 01:表示读取的开始长度(从0开始算起)第一个
00 03:表示读取的传感器长度为3
后面的两位是校验码:54 0B
第二行是返回的数据
返回的报文:01:表示读取传感器的ID为1;
03:表示读取传感器种类03是线圈类型
06:表示返回的有效数字长度;(总共三组,两个一组)
00 00:表示一组数据
00 03:表示一组数据
00 03:表示一组数据
后面的两位是校验码:91 74
MODBUS-TCP 与UDP
modbus-tcp比起RTU要简单点,不用配置额外的java环境,大部分使用的是MODBUS4j的方法包
1、 配置模拟传感器TCP方法连接
2、引入TCP连接需要的包
1、添加modbus4j依赖
<dependency><groupId>com.infiniteautomation</groupId><artifactId>modbus4j</artifactId><version>3.0.3</version></dependency>
2、注意,还要添加下面一段,指定网上下包的网址,不然使用以前maven配置的阿里云的仓库,下载不到modbus4j的包
<repositories><repository><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots><id>ias-snapshots</id><name>Infinite Automation Snapshot Repository</name><url>/repository/ias-snapshot/</url></repository><repository><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots><id>ias-releases</id><name>Infinite Automation Release Repository</name><url>/repository/ias-release/</url></repository></repositories>
3、主要的代码如下
Componentpublic class ReadAWriteUtil {/*** 批量写数据到保持寄存器* @param ip 从站IP* @param port modbus端口* @param slaveId 从站ID* @param start 起始地址偏移量* @param values 待写数据*/public static void modbusWTCP(String ip, int port, int slaveId, int start, short[] values) {ModbusFactory modbusFactory = new ModbusFactory();// 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502IpParameters params = new IpParameters();params.setHost(ip);// 设置端口,默认502if (502 != port) {params.setPort(port);}ModbusMaster tcpMaster = null;// 参数1:IP和端口信息 参数2:保持连接激活tcpMaster = modbusFactory.createTcpMaster(params, true);try {tcpMaster.init();System.out.println("=======初始化成功========");} catch (ModbusInitException e) {System.out.println("初始化异常");}try {WriteRegistersRequest request = new WriteRegistersRequest(slaveId, start, values);WriteRegistersResponse response = (WriteRegistersResponse) tcpMaster.send(request);if (response.isException()){System.out.println("Exception response: message=" + response.getExceptionMessage());}else{System.out.println("Success");}} catch (ModbusTransportException e) {e.printStackTrace();}}/*** 读保持寄存器上的内容* @param ip 从站IP* @param port modbus端口* @param start 起始地址偏移量* @param readLenth 待读寄存器个数* @return*/public static Integer modbusTCP(String ip, int slaveId, int port, int start, int readLenth) {ModbusFactory modbusFactory = new ModbusFactory();// 设备ModbusTCP的Ip与端口,如果不设定端口则默认为502IpParameters params = new IpParameters();params.setHost(ip);//设置端口,默认502if(502!=port){params.setPort(port);}ModbusMaster tcpMaster = null;tcpMaster = modbusFactory.createTcpMaster(params, true);try {tcpMaster.init();System.out.println("========初始化成功=======");} catch (ModbusInitException e) {return null;}ModbusRequest modbusRequest=null;try {//功能码03 读取保持寄存器的值modbusRequest = new ReadHoldingRegistersRequest(slaveId, start, readLenth);} catch (ModbusTransportException e) {e.printStackTrace();return null;}ModbusResponse modbusResponse=null;try {modbusResponse = tcpMaster.send(modbusRequest);} catch (ModbusTransportException e) {e.printStackTrace();return null;}ByteQueue byteQueue= new ByteQueue(1024);modbusResponse.write(byteQueue);// System.out.println(modbusRequest());System.out.println("功能码:"+modbusRequest.getFunctionCode());System.out.println("从站地址:"+modbusRequest.getSlaveId());System.out.println("收到的响应信息大小:"+byteQueue.size());System.out.println("收到的响应信息值:"+byteQueue);int i = byteQueue.size() - 1;int i2 = byteQueue.size() - 2;byte peek = byteQueue.peek(i);byte peek2 = byteQueue.peek(i2);String s = hex10To16(peek);String s2 = hex10To16(peek2);String tt=s2+s;// System.out.println(tt);Integer b=Integer.parseInt(tt,16);// System.out.println(b);//for()return b;}public static String hex10To16(int valueTen) {return String.format("%02x", valueTen);}public static void main(String[] args) {String ip="127.0.0.1";int port=502;int start=2;int readLenth=2;int slaveId=1;Integer byteQueue = modbusTCP(ip,slaveId, port, start, readLenth);System.out.println("打印的值为"+byteQueue);}
运行代码收到的报文如下
和RTU报文类似,但是有效的报文就第七位开始
01 表示传感器ID
03 表示读取线圈的类型
00 02 开始读取的值的地址
00 02 读取的长度(为2)
3、UDP的读的方式修改如下,其他如TCP一样
tcpMaster = modbusFactory.createTcpMaster(params, true);// tcpMaster = modbusFactory.createUdpMaster(params);
基于Socket通信(DTU设备连接)
因为以前从来没有搞过这些东西,突然甲方说用DTU进行通信,网上查找资料,学习之后,发现其实就是Socket的东西,与ModBus关系已经很小了。
基本的逻辑是,设备厂商,将传感器通过串口绑定到DTU上,DTU相当于Socket的客户端,连接服务器的IP和端口,服务器通过端口与客户端通信(DTU),向客户端(DTU)发送指令,客户端回复有效值
我服务端设计的逻辑如下:
1、服务端监听5502端口
2、用while(true)的逻辑来不断监听客户端的连接,并开启线程来执行指令,我这边有7个DTU,也就是要开7个线程;
3、socket的accept方法来监听客户端连接
Socket client = server.accept();这个方法在接收不到客户端的请求时候,会一直处于阻塞状态
4、获取客户端发送的注册包(客户端第一次连接时候,输出流会发送一个注册包,相当于认证码)
InputStream is = client.getInputStream();System.out.println(client.getInetAddress() + "已成功连接到此台服务器上。");byte[] bytes = ModBusUtils.readInputStream(is);
5、解析注册包信息,也就是将上面客户端的字节流,转化为byte字节数组,
String str = "";for (int i = 0; i < bytes.length; i++) {int uu = bytes[i];if (i == 0) {System.out.print("十进制:" + bytes[i] + " ");} else {System.out.print(bytes[i] + " ");}if ((uu >= 48 && uu <= 57) || (uu >= 65 && uu <= 90) || (uu >= 97 &&uu<= 122)) {str = str + ModBusUtils.byteToASCLL(bytes[i]);}}System.out.println("十六进制:" + ModBusUtils.bytes2HexString(bytes));System.out.println("注册包:" + str);
封装在自定义的ModBusUtils这个类中byteToASCLL方法如下
public static char byteToASCLL(byte b){return (char) b;}
请不要小看这个if判断,我在这里踩了半天的坑
if ((uu >= 48 && uu <= 57) || (uu >= 65 && uu <= 90) || (uu >= 97 &&uu<= 122)) {}
为了拿到注册包名称信息(方便后面线程内使用):上面的for循环通过byteToASCLL方法,将字节数组byte[ ],转化为char的UNICOEDE字符,但是有些字符是日志中看不到
这就导致我迷茫了一下午,为什么服务器日志中名称相同,SQL相同,但是返回的结果不同,服务器日志入下:
表如下,我照着表,过滤其他一些特殊字符
下面是我服务器拷贝的真实的字节数组测试
public static void main(String[] args) {byte[] bytes = {116, 98, 7, 4, 49, 51, 48, 54, 54, 54, 54, 56, 56, 56, 56};String str = "";String str2="";for (int i = 0; i < bytes.length; i++) {int uu=bytes[i];if (i == 0) {System.out.print("十进制:" + bytes[i] + " ");} else {System.out.print(bytes[i] + " ");}if((uu>=48&&uu<=57)||(uu>=65&&uu<=90)||(uu>=97&&uu<=122)){str = str + ModBusUtils.byteToASCLL(bytes[i]);}str2 = str2 + ModBusUtils.byteToASCLL(bytes[i]);}System.out.println("十六进制:" + ModBusUtils.bytes2HexString(bytes));System.out.println("Str为:" + str);System.out.println("未转化的Str2为:" + str2);}
中间的两个问号乱码,但是日志不会显示,这就解释了为什么,看起来明明一样的日志,但是数据库返回结果却不同。
6、将注册包和客户端连接信息传入线程中,并开启线程
Service service = new Service(client, str);service.start();
服务器端代码如下
@Componentpublic class SocketServer extends Thread {public void run() {try {//创建socket服务端@SuppressWarnings("resource")ServerSocket server = new ServerSocket(5502);System.out.println("执行了这个");while (true) {System.out.println("建立监听");Socket client = server.accept();System.out.println("建立连接");//获取客户端发送注册包InputStream is = client.getInputStream();System.out.println(client.getInetAddress() + "已成功连接到此台服务器上。");byte[] bytes = ModBusUtils.readInputStream(is);String str = "";for (int i = 0; i < bytes.length; i++) {int uu = bytes[i];if (i == 0) {System.out.print("十进制:" + bytes[i] + " ");} else {System.out.print(bytes[i] + " ");}if ((uu >= 48 && uu <= 57) || (uu >= 65 && uu <= 90) || (uu >= 97 && uu <= 122)) {str = str + ModBusUtils.byteToASCLL(bytes[i]);}}System.out.println("十六进制:" + ModBusUtils.bytes2HexString(bytes));System.out.println("注册包:" + str);//添加客户端//clientMap.put(str,client);Service service = new Service(client, str);service.start();}} catch (Exception e) {e.printStackTrace();}}}
用到的工具类代码
package com.nswi.socketTest;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;/*** @program: modbus* @ClassName ModBusUtils* @description:* @author:蒋皓洁* @create: -07-31 09:57* @Version 1.0**/public class ModBusUtils {public static byte[] readInputStream(InputStream inputStream) throws IOException {byte[] buffer = new byte[1024];int len = 0;ByteArrayOutputStream bos = new ByteArrayOutputStream();if((len = inputStream.read(buffer)) != -1) {bos.write(buffer, 0, len);}bos.close();return bos.toByteArray();}public static char byteToASCLL(byte b){return (char) b;}//Integer x = Integer.parseInt(hex,16);/** 字节数组转16进制字符串*/public static String bytes2HexString(byte[] b) {String r = "";for (int i = 0; i < b.length; i++) {String hex = Integer.toHexString(b[i] & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}r += hex.toUpperCase()+" ";}return r;}public static String bytes2HexStringOne(byte b) {String r = "";// for (int i = 0; i < b.length; i++) {String hex = Integer.toHexString(b & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}r += hex.toUpperCase()+" ";// }return r;}/*** @TODO : 计算CRC校验码* @AUTH : linfeng* @DATE : 8月27日 下午2:11:30* @return_type : String* @param data* @return*/public static String getCRC3(byte[] data) {byte[] crc16_h = {(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41,(byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x01, (byte) 0xC0, (byte) 0x80, (byte) 0x41, (byte) 0x00, (byte) 0xC1, (byte) 0x81, (byte) 0x40};byte[] crc16_l = {(byte) 0x00, (byte) 0xC0, (byte) 0xC1, (byte) 0x01, (byte) 0xC3, (byte) 0x03, (byte) 0x02, (byte) 0xC2, (byte) 0xC6, (byte) 0x06, (byte) 0x07, (byte) 0xC7, (byte) 0x05, (byte) 0xC5, (byte) 0xC4, (byte) 0x04,(byte) 0xCC, (byte) 0x0C, (byte) 0x0D, (byte) 0xCD, (byte) 0x0F, (byte) 0xCF, (byte) 0xCE, (byte) 0x0E, (byte) 0x0A, (byte) 0xCA, (byte) 0xCB, (byte) 0x0B, (byte) 0xC9, (byte) 0x09, (byte) 0x08, (byte) 0xC8,(byte) 0xD8, (byte) 0x18, (byte) 0x19, (byte) 0xD9, (byte) 0x1B, (byte) 0xDB, (byte) 0xDA, (byte) 0x1A, (byte) 0x1E, (byte) 0xDE, (byte) 0xDF, (byte) 0x1F, (byte) 0xDD, (byte) 0x1D, (byte) 0x1C, (byte) 0xDC,(byte) 0x14, (byte) 0xD4, (byte) 0xD5, (byte) 0x15, (byte) 0xD7, (byte) 0x17, (byte) 0x16, (byte) 0xD6, (byte) 0xD2, (byte) 0x12, (byte) 0x13, (byte) 0xD3, (byte) 0x11, (byte) 0xD1, (byte) 0xD0, (byte) 0x10,(byte) 0xF0, (byte) 0x30, (byte) 0x31, (byte) 0xF1, (byte) 0x33, (byte) 0xF3, (byte) 0xF2, (byte) 0x32, (byte) 0x36, (byte) 0xF6, (byte) 0xF7, (byte) 0x37, (byte) 0xF5, (byte) 0x35, (byte) 0x34, (byte) 0xF4,(byte) 0x3C, (byte) 0xFC, (byte) 0xFD, (byte) 0x3D, (byte) 0xFF, (byte) 0x3F, (byte) 0x3E, (byte) 0xFE, (byte) 0xFA, (byte) 0x3A, (byte) 0x3B, (byte) 0xFB, (byte) 0x39, (byte) 0xF9, (byte) 0xF8, (byte) 0x38,(byte) 0x28, (byte) 0xE8, (byte) 0xE9, (byte) 0x29, (byte) 0xEB, (byte) 0x2B, (byte) 0x2A, (byte) 0xEA, (byte) 0xEE, (byte) 0x2E, (byte) 0x2F, (byte) 0xEF, (byte) 0x2D, (byte) 0xED, (byte) 0xEC, (byte) 0x2C,(byte) 0xE4, (byte) 0x24, (byte) 0x25, (byte) 0xE5, (byte) 0x27, (byte) 0xE7, (byte) 0xE6, (byte) 0x26, (byte) 0x22, (byte) 0xE2, (byte) 0xE3, (byte) 0x23, (byte) 0xE1, (byte) 0x21, (byte) 0x20, (byte) 0xE0,(byte) 0xA0, (byte) 0x60, (byte) 0x61, (byte) 0xA1, (byte) 0x63, (byte) 0xA3, (byte) 0xA2, (byte) 0x62, (byte) 0x66, (byte) 0xA6, (byte) 0xA7, (byte) 0x67, (byte) 0xA5, (byte) 0x65, (byte) 0x64, (byte) 0xA4,(byte) 0x6C, (byte) 0xAC, (byte) 0xAD, (byte) 0x6D, (byte) 0xAF, (byte) 0x6F, (byte) 0x6E, (byte) 0xAE, (byte) 0xAA, (byte) 0x6A, (byte) 0x6B, (byte) 0xAB, (byte) 0x69, (byte) 0xA9, (byte) 0xA8, (byte) 0x68,(byte) 0x78, (byte) 0xB8, (byte) 0xB9, (byte) 0x79, (byte) 0xBB, (byte) 0x7B, (byte) 0x7A, (byte) 0xBA, (byte) 0xBE, (byte) 0x7E, (byte) 0x7F, (byte) 0xBF, (byte) 0x7D, (byte) 0xBD, (byte) 0xBC, (byte) 0x7C,(byte) 0xB4, (byte) 0x74, (byte) 0x75, (byte) 0xB5, (byte) 0x77, (byte) 0xB7, (byte) 0xB6, (byte) 0x76, (byte) 0x72, (byte) 0xB2, (byte) 0xB3, (byte) 0x73, (byte) 0xB1, (byte) 0x71, (byte) 0x70, (byte) 0xB0,(byte) 0x50, (byte) 0x90, (byte) 0x91, (byte) 0x51, (byte) 0x93, (byte) 0x53, (byte) 0x52, (byte) 0x92, (byte) 0x96, (byte) 0x56, (byte) 0x57, (byte) 0x97, (byte) 0x55, (byte) 0x95, (byte) 0x94, (byte) 0x54,(byte) 0x9C, (byte) 0x5C, (byte) 0x5D, (byte) 0x9D, (byte) 0x5F, (byte) 0x9F, (byte) 0x9E, (byte) 0x5E, (byte) 0x5A, (byte) 0x9A, (byte) 0x9B, (byte) 0x5B, (byte) 0x99, (byte) 0x59, (byte) 0x58, (byte) 0x98,(byte) 0x88, (byte) 0x48, (byte) 0x49, (byte) 0x89, (byte) 0x4B, (byte) 0x8B, (byte) 0x8A, (byte) 0x4A, (byte) 0x4E, (byte) 0x8E, (byte) 0x8F, (byte) 0x4F, (byte) 0x8D, (byte) 0x4D, (byte) 0x4C, (byte) 0x8C,(byte) 0x44, (byte) 0x84, (byte) 0x85, (byte) 0x45, (byte) 0x87, (byte) 0x47, (byte) 0x46, (byte) 0x86, (byte) 0x82, (byte) 0x42, (byte) 0x43, (byte) 0x83, (byte) 0x41, (byte) 0x81, (byte) 0x80, (byte) 0x40};int crc = 0x0000ffff;int ucCRCHi = 0x00ff;int ucCRCLo = 0x00ff;int iIndex;for (int i = 0; i < data.length; ++i) {iIndex = (ucCRCLo ^ data[i]) & 0x00ff;ucCRCLo = ucCRCHi ^ crc16_h[iIndex];ucCRCHi = crc16_l[iIndex];}crc = ((ucCRCHi & 0x00ff) << 8) | (ucCRCLo & 0x00ff) & 0xffff;//高低位互换,输出符合相关工具对Modbus CRC16的运算crc = ((crc & 0xFF00) >> 8) | ((crc & 0x00FF) << 8);return String.format("%04X", crc);}/*** 16进制表示的字符串转换为字节数组** @param hexString 16进制表示的字符串* @return byte[] 字节数组*/public static byte[] hexStringToByteArray(String hexString) {hexString = hexString.replaceAll(" ", "");int len = hexString.length();byte[] bytes = new byte[len / 2];for (int i = 0; i < len; i += 2) {// 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));}return bytes;}}
Socket连接的线程如下
package com.nswi.socketTest;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.nswi.entity.CgqCall;import com.nswi.entity.CgqNumber;import com.nswi.entity.CgqType;import com.nswi.entity.CgqTypeDetails;import com.nswi.mapper.CgqCallMapper;import com.nswi.mapper.CgqNumberMapper;import com.nswi.mapper.CgqTypeDetailsMapper;import com.nswi.mapper.CgqTypeMapper;import com.nswi.service.ICgqCallService;import com.nswi.service.ICgqTypeDetailsService;//import com.nswi.utils.test.SpringContextJobUtil;import com.nswi.utils.test.SpringContextJobUtil;import org.springframework.beans.factory.annotation.Autowired;import java.io.InputStream;import java.io.OutputStream;import java.lang.reflect.Type;import .Socket;import java.text.SimpleDateFormat;import java.util.*;/*** @program: modbus* @ClassName Service* @description:* @author:蒋皓洁* @create: -07-31 10:03* @Version 1.0**/public class Service extends Thread {// 传感器@AutowiredCgqTypeMapper cgqTypeMapper;//传感器的细节存入@AutowiredCgqTypeDetailsMapper cgqTypeDetailsMapper;// 传感器使用@AutowiredCgqCallMapper cgqCallMapper;@AutowiredCgqNumberMapper cgqNumberMapper;private int numberTime = 0;private Socket clientMap;private String jiedianbao;public Service() {}public Service(Socket clientMap, String uuu) {this.clientMap = clientMap;this.jiedianbao = uuu;}private static String getType(Object a) {return a.getClass().toString();}public void run() {while (true) {long start = System.currentTimeMillis();System.out.println(jiedianbao);try {//注册包 节点编号String registrationPacket = jiedianbao;String getThreadName = Thread.currentThread().getName();Socket socket = clientMap;assert socket != null;OutputStream os = socket.getOutputStream();cgqTypeMapper = (CgqTypeMapper) SpringContextJobUtil.getBean("cgqTypeMapper");LambdaQueryWrapper<CgqType> cgqTypeLambdaQueryWrapper = new LambdaQueryWrapper<>();cgqTypeLambdaQueryWrapper.eq(CgqType::getRtuId, jiedianbao);List<CgqType> cgqTypes = cgqTypeMapper.selectList(cgqTypeLambdaQueryWrapper);System.out.println(jiedianbao + "的长度为:" + cgqTypes.size());for (CgqType cgt : cgqTypes) {int id = cgt.getTypeId();int startLength = cgt.getCodeStart();int endLength = cgt.getCodeLength();byte[] by = new byte[6];by[0] = (byte) id;by[1] = 3;by[2] = 0;by[3] = (byte) startLength;by[4] = 0;by[5] = (byte) endLength;//crc3校验String crc = ModBusUtils.getCRC3(by);byte[] crcByte = ModBusUtils.hexStringToByteArray(crc);byte[] data = new byte[8];data[6] = crcByte[0];data[7] = crcByte[1];for (int i = 0; i < by.length; i++) {data[i] = by[i];}os.write(data);System.out.println(registrationPacket + ":往DTU写入数据为:" + Arrays.toString(data));InputStream is2 = socket.getInputStream();long et2 = System.currentTimeMillis();boolean enterTT = true;while (is2.available() == 0 && enterTT) {is2 = socket.getInputStream();long et3 = System.currentTimeMillis();// 大于一秒结束请求if (et3 - et2 > 1000) {enterTT = false;System.out.println("没有请求到数据包");}}Date date = new Date();//获得系统时间.SimpleDateFormat sdf = new SimpleDateFormat(" yyyy-MM-dd HH:mm:ss");String nowTime = sdf.format(date);Date time = sdf.parse(nowTime);System.out.println(time);if (numberTime == 0) {if (is2.available() != 0) {System.out.println("能拿到数据");byte[] bytes = ModBusUtils.readInputStream(is2);if (bytes.length > 5) {String uu = ModBusUtils.bytes2HexStringOne(bytes[3]) + ModBusUtils.bytes2HexStringOne(bytes[4]);String u2 = uu.replace(" ", "");int b = Integer.parseInt(u2, 16);CgqTypeDetails cgqTypeDetails = new CgqTypeDetails();short us = (short) b;cgqTypeDetails.setCreateTime(time);cgqTypeDetails.setCgqValue(us);cgqTypeDetails.setCgqId(cgt.getTypeId());cgqTypeDetails.setName(cgt.getName());cgqTypeDetails.setCgqType(1);if (cgt.getSetType() == 1) {short hi = (short) cgt.getHighValue();short lo = (short) cgt.getLowValue();if (us > hi || us < lo) {CgqCall cgqCall = new CgqCall();cgqCall.setName(cgt.getName());cgqCall.setCgqId(cgt.getTypeId());cgqCall.setCreateTime(time);cgqCall.setShowType(1);if (us > hi) {cgqTypeDetails.setCgqType(3);cgqCall.setErrCode(300);} else {cgqCall.setErrCode(200);cgqTypeDetails.setCgqType(2);}cgqCall.setCgqValue(us);cgqCall.setLocation(cgt.getLocation());cgqCallMapper = (CgqCallMapper) SpringContextJobUtil.getBean("cgqCallMapper");int save2 = cgqCallMapper.insert(cgqCall);if (save2 > 0) {System.out.println("插入错误码成功");} else {System.out.println("插入错误码失败");}}}cgqTypeDetailsMapper = (CgqTypeDetailsMapper) SpringContextJobUtil.getBean("cgqTypeDetailsMapper");int save1 = cgqTypeDetailsMapper.insert(cgqTypeDetails);if (save1 > 0) {System.out.println("添加成功");} else {System.out.println("添加失败");}System.out.println(Thread.currentThread().getName() + "**********" + cgt.getTypeId() + "***************" + b + "***********************************");System.out.println("线程名称:" + Thread.currentThread().getName() + "获取传感器:" + "的第一个Byte数组值为:" + ModBusUtils.bytes2HexStringOne(bytes[0]));String str = ModBusUtils.bytes2HexString(bytes);System.out.print("线程名称:" + Thread.currentThread().getName() + "获取传感器" + "的十六进制数组为:" + str);}} else {System.out.println(Thread.currentThread().getName() + "*****拿不到数据*****" + cgt.getTypeId() + "**************************************************" + time);CgqCall cgqCall = new CgqCall();cgqCall.setName(cgt.getName());cgqCall.setCgqId(cgt.getTypeId());cgqCall.setCreateTime(time);cgqCall.setErrCode(100);cgqCall.setShowType(1);cgqCall.setLocation(cgt.getLocation());cgqCallMapper = (CgqCallMapper) SpringContextJobUtil.getBean("cgqCallMapper");int save = cgqCallMapper.insert(cgqCall);if (save > 0) {System.out.println("插入错误码成功");} else {System.out.println("插入错误码失败");}}} else {System.out.println(Thread.currentThread().getName() + "**********" + cgt.getTypeId() + "***************" + "不记录值" + "***********************************");continue;}}} catch (Exception e) {e.printStackTrace();}try {long end = System.currentTimeMillis();System.out.println("********************" + Thread.currentThread().getName() + "**********循环一次时间****************" + (end - start) + "毫秒");System.out.println("这个线程当先开始等待" + Thread.currentThread().getName());cgqNumberMapper = (CgqNumberMapper) SpringContextJobUtil.getBean("cgqNumberMapper");CgqNumber cgqNumber = cgqNumberMapper.selectById(1);int mm=4;if (cgqNumber != null) {Integer numberTime = cgqNumber.getNumberTime();mm=(numberTime*numberTime+1)*2;}//睡眠3万毫秒if(numberTime<mm){numberTime=numberTime+1;}else {numberTime=0;}Thread.sleep(30000);long end3 = System.currentTimeMillis();System.out.println("这个线程等待当先执行完毕" + Thread.currentThread().getName() + "**********************************" + (end3 - end) + "毫秒");} catch (InterruptedException e) {e.printStackTrace();}}}}
线程主要业务逻辑如下
1、当主线程执行Start方法,子线程开始执行Run方法,在run方法中加入while(true),让子线程不断读取客户端(DTU的)信息,发送指令(传感器ID,读取长度等信息),DTU返回信息
2、我用的是mybatisPlus框架来操作数据库,(因为我这边设备DTU有7个,所以总共会开启七个线程,每个线程分开执行)数据库中有个注册包的字段,通过注册包名称来区分线程,并通过注册包名称来区分DTU下面管理的传感器ID
3、因为读取传感器信息,要与客户端保持连接,但是因为业务需求(有2分钟,5分钟,10分钟)录入传感器数据,但是线程不能休眠时间过长,否则socket会断开连接。所以定了一个全局变量a加上数据库数据,来实现定时数据录入的功能
发送数据核心代码:
byte[] by = new byte[6];by[0] = (byte) id;by[1] = 3;by[2] = 0;by[3] = (byte) startLength;by[4] = 0;by[5] = (byte) endLength;//crc3校验String crc = ModBusUtils.getCRC3(by);byte[] crcByte = ModBusUtils.hexStringToByteArray(crc);byte[] data = new byte[8];data[6] = crcByte[0];data[7] = crcByte[1];for (int i = 0; i < by.length; i++) {data[i] = by[i];}os.write(data);
接收数据核心代码(因为我的业务只需要读取返回的一组数据,所以写死了数据采集)
byte[] bytes = ModBusUtils.readInputStream(is2);if (bytes.length > 5) {String uu = ModBusUtils.bytes2HexStringOne(bytes[3]) +ModBusUtils.bytes2HexStringOne(bytes[4]);String u2 = uu.replace(" ", "");int b = Integer.parseInt(u2, 16);
如果一秒请求不到DTU返回的数据,则默认是求情不到数据包,视为传感器断线
boolean enterTT = true;while (is2.available() == 0 && enterTT) {is2 = socket.getInputStream();long et3 = System.currentTimeMillis();// 大于一秒结束请求if (et3 - et2 > 1000) {enterTT = false;System.out.println("没有请求到数据包");}}
花了一下午记录了一下,这一个星期遇到的坑,大家共同学习