后续可以进一步实现更多的功能,适应实际环境中的通信:
1. 如果数据发送不连续
2. 如果收到的数据有误 解析不了 报错?
3. 批量读且解析
4. 修改tcp寄存器的数值
5. 如果换个协议 怎么尽可能减少代码的修改 融入设计模式
1. 数据发送不连续 阻塞等待 设置接收长度判断和超时判断
2. 要查看配置是否有问题:
1个寄存器数量=1个长度=16位=2个字节 unsigned short
而浮点数是4个字节 32位需要2个寄存器去存放 int/uint也是4个字节
带浮点数的进制转化:
c9 41 9a 99 -> 41 c9 99 9a(但并不是简单地拆分计算)
以四个字节的数据为准:4*8=32位 31位为符号,30到23八个字节为阶码,由第二步计算得来,尾数部分则是剩余的小数部分(由后23位决定)
1.将经纬度数据(10进制,浮点型)转换成二进制形式;
2.小数点向左移动n位,直到最后一个1停止,得到左移的位数,计算阶码,127+(左移位数);
3.尾数(22到0位置总计23个位数)为小数部分的二进制编码,剩余未填满的位数用0补齐;
4.再将所有得到的2进制编码,转换成16进制的数据。
e.g.
首先将6.91转换为二进制形式:110.111010001111010111000
将其规范化:调整使其实数第一位大于1小于2
6.91 = 1.10111010001111010111000 * 2^2
所以S:0
EXP : 2(小数点左移位数)+127(10进制) =129(10进制) = 10000001(2进制)
Fraction : 10111010001111010111000 (注意:小数点前面的1不要了)
组合一下: 0 10000001 10111010001111010111000
= 0100 0000 1101 1101 0001 1110 1011 1000= 4 0 D D 1 E B 8
在线转换网页:https://lostphp.com/hexconvert/
3.批量读并解析:
多条指令
一条指令
4.修改寄存器的数值:
多条指令一起修改
读写读三遍比对:
注:如果要修改的值超出字符范围大小,则将全部字节进行(byte)强制转换。
5. 面向对象:设计模式
(1)
main方法必须要static来修饰,是因为main方法是Java解释器调用的,那时候还没有任何对象产生。
用static修饰的方法,无须产生类的实例对象就可以调用该方法。
没有static修饰的方法,需要产生一个类的实例对象才可以调用该方法。
(2)
java main函数通过new一个对象调用非静态函数:
main方法都是静态方法,如果想调用其它的方法,要么只能是其它的静态方法,要么就是将当前类实例化在调用它的非静态方法 main方法里面调用非静态方法时,需要new一个对象,因为静态方法、静态类属于模板,非静态类和方法属于对象。
(3)
别的类里的非静态变量不可直接调用,也是需要在本类创建该变量(通过new的方法调用非静态函数创建)。
我自己设计的框架:
导师给我提供的更为规范 实际开发应用的框架:
框架梳理:
接口interface
IDevice:定义connect()判断函数、读写泛型函数
IEncoder:编写 读/写指令
getRead/WriteCommand
IDecoder:获取报文id和长度和值、检查合法性
getId、getHeadLength、getContentLength、getValue、checkIdLegality
通用/公共类
BytesOperate类:
负责合并两个byte数组(报文头+报文正文内容)
Converter类:
重写getBytes():将不同类型的数据转换成byte型
getShort、getChar、getInt、getLong、getFloat、getDouble、getString、getHexString、getCRC():将byte[]转换成不同类型的数据
enum PLCAddressType:
线圈寄存器、离散寄存器、保持寄存器、输入寄存器
OperateResult:泛型,存放操作结果判断等等
DeviceTemplate类:
实现接口implements IDevice 设置地址和端口:
public DeviceTemplate(String ipAddress, int port) {
this.ipAddress = ipAddress;
this.port = port;
}
判断是否连接成功:
public boolean connect() {
try {
if(!socket.isConnected() || socket.isClosed()){
socket = new Socket();
SocketAddress remoteAddr=new / InetSocketAddress(ipAddress,port);
socket.connect(remoteAddr, 3000);
}
} catch (IOException e) {
e.printStackTrace();
}
return socket.isConnected() && !socket.isClosed();
}
发送数据:
private OperateResult<byte[]> Send(byte[] sendBuffer){
OperateResult<byte[]> result = new OperateResult<byte[]>();
if(socket.isConnected()){
Pass
}
先展示要发送的数据,建立输出输入流:
System.out.println("Send:" +Converter.getHexString(sendBuffer));
OutputStream outputStream = socket.getOutputStream();
outputStream.write(sendBuffer);
outputStream.flush();
InputStream is = socket.getInputStream();
接收时要先做判断:
byte[] headerBuffer = new byte[decoder.getHeadLength()];
if(Receive(is, headerBuffer, 3000) == headerBuffer.length){
byte[] contentBuffer = new byte[decoder.getContentLength(headerBuffer)];
if(Receive(is, contentBuffer, 3000) == contentBuffer.length) {
byte[] recvBuffer = BytesOperate.addBytes(headerBuffer, contentBuffer);
if(decoder.checkIdLegality(sendBuffer, recvBuffer))
{
if(decoder.checkPacketLegality(sendBuffer, recvBuffer)){
if(decoder.checkSumLegality(sendBuffer, recvBuffer)){
System.out.println("Recv:" +Converter.getHexString(recvBuffer));
if(recvBuffer.length >= 9){
if(((recvBuffer[7] & 0X83) == 0X83) && ((recvBuffer[8] & 0X02) == 0X02)){
result.isSuccess = false;
result.message = "Send command error";
}else{
result.isSuccess = true;
result.message = "Success";
result.content = recvBuffer;
}
当缓冲区1数据长度报文头部长度(从decoder.getHeadLength获取)时->当缓冲区2数据长度报文正文长度(从decoder.getContentLength
获取)时->合并报文->检查id合法,否则报错:
public boolean checkIdLegality(byte[] sendByte, byte[] recvByte) {
if(sendByte.length > 2 && recvByte.length >2){
return sendByte[0] == recvByte[0] && sendByte[1] == recvByte[1];
}
return false;
}
->输出byte转十六进制:判断数据字节长度&判断是否错误代码
接收函数:
计算并设置超时时间+接收数据的长度判断
比较长度大小 先读进缓冲区
private int Receive(InputStream is, byte[] recvBuffer, int
timeoutMillis) throws IOException {
int bufferOffset = 0;
long maxTimeMillis = System.currentTimeMillis() + timeoutMillis;
while (System.currentTimeMillis() < maxTimeMillis && bufferOffset < recvBuffer.length) {
int readLength = java.lang.Math.min(is.available(), recvBuffer.length - bufferOffset);
int readResult = is.read(recvBuffer, bufferOffset, readLength);
if (readResult == -1) {
break;
}
bufferOffset += readResult;
}
return bufferOffset;
}
read(byte[] b, int off, int len)
:从b[off]开始将输入流中最多len个数据字节读入 byte 数组,以整数形式返回实际读取的字节数。
读函数:
泛型【在ModbusTcpDeviceTest被调用】
先判断是否连接成功:if(this.connect())
发送编码后的报文byte[]:Send(encoder.getReadCommand())
【定义在ModbusTcpEncoder】
根据响应报文判断是否发送成功:this.decoder.getValue()【定义在ModbusTcpDecoder】
否则报错
public <T> OperateResult<String> read(short dataAddress,
PLCAddressType addressType, short dataAddressLength, Class<T> clazz) {
OperateResult<String> result = new OperateResult<String>();
if(this.connect())
{
OperateResult<byte[]> operateResult = Send(encoder.getReadCommand(stationNumber, dataAddress, addressType, dataAddressLength));
if(operateResult.isSuccess){
result.isSuccess = true;
result.content = this.decoder.getValue(operateResult.content, clazz);
result.message = "Success";
}else{
result.isSuccess = false;
result.message = operateResult.message;
}
}else{
result.isSuccess = false;
result.message = "Connect error";
}
return result;
}
}
TCP类:
【要实现接口中定义的全部函数功能】
ModbusTcpDecoder implements Idecoder
从头指令获取报文正文长度:
public short getContentLength(byte[] recvHeadByte) {
byte[] contentBytes = new byte[] {
recvHeadByte[5], recvHeadByte[4]
} ;
short value = Converter.getShort(contentBytes);
return Converter.getShort(contentBytes);
}
getValue()
判断是短整型还是浮点型:取出相应的字节
public <T> String getValue(byte[] recvByte, Class<T> clazz){
String result = null;
try {
switch (clazz.getName()){
case "float": {
byte[] dataBytes = new byte[]{ recvByte[11], recvByte[12], recvByte[9], recvByte[10]};
float value = Converter.getFloat(dataBytes);
result = String.valueOf(value);
break;
} case "short":{
byte[] dataBytes = new byte[]{recvByte[9], recvByte[10]};
short value = Converter.getShort(dataBytes);
result = String.valueOf(value);
break;
}
default:break;
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
ModbusTcpDevice extends DeviceTemplate
定义
public ModbusTcpDevice(String ipAddress, int port) {
super(ipAddress, port);
this.decoder = new ModbusTcpDecoder();
this.encoder = new ModbusTcpEncoder();
}
ModbusTcpEncoder implements Iencoder
编码发送内容
读指令:
private short id = 0X01;
public byte[] getReadCommand(byte stationNumber, short dataAddress, PLCAddressType addressType, int dataAddressLength) {
byte[] headerBuffer = Converter.getBytes((short)id++);
byte[] dataAddressBuffer = Converter.getBytes(dataAddress);
byte addressByte = (byte)(addressType.ordinal() + 1);
return new byte[]{
headerBuffer[0],headerBuffer[1],
0X00,0X00,0X00,0X06,
stationNumber,
addressByte,
dataAddressBuffer[1], dataAddressBuffer[0],
0X00, (byte)dataAddressLength
};
}
写指令可以参考开头提到的那篇rtu文章