MODBUS协议对接
通讯方式:NIO
SocketChanel简介:
socketChanel属于NIOSocket,不同与传统socket, NIOSocket是引入了三个概念:Channel、Selector、Buffer。Buffer是将很多请求打包,一次性发出去。有Selector扮演选择器的角色,将请求分转给对应的通道(Channel)进行处理响应。
Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。
Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道, 就像标准IO中的ServerSocket一样。ServerSocketChannel类在 java.nio.channels包中。
这里用到的是ServerSocketChannel 的非阻塞模式。
关键方法:
.open()//创建连接
.close()//关闭连接
.configureBlocking//设定阻塞或非阻塞
.write()//写入
.read()//获取
.bind()//绑定监听端口
.register(Selector sel, Int ops)//向给定的选择器注册此通道,返回一个选择键( SelectionKey)
// ops参数:
// OP_ACCEPT:用于套接字接受操作的操作集位
// OP_CONNECT:用于套接字连接操作的操作集位
// OP_READ:用于读取操作的操作集位
// OP_WRITE:用于写入操作的操作集位
实现代码:
public void messageReceiver() throws Exception {
//监听新进来的TCP连接 对每一个新进来的连接都会创建一个SocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定8089端口
serverSocketChannel.socket().bind(new InetSocketAddress(8098));
//与Selector一起使用时,Channel必须处于非阻塞模式下
serverSocketChannel.configureBlocking(false);
//打开Selector 可在单线程状态监听多个Channel
Selector selector = Selector.open();
//注册监听的channel
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
logger.info("服务器启动成功");
while (true) {
selector.select();
//注册到该Selector的通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//ServerSocketChannel 接受了一个连接
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
logger.info("客户端连接成功:" + socketChannel.getRemoteAddress());
} else if (key.isReadable()) {
//ServerSocketChannel已准备好阅读
SocketChannel socketChannel = (SocketChannel) key.channel();
InetSocketAddress addresss = (InetSocketAddress) socketChannel.getRemoteAddress();
String address = addresss.getAddress().getHostAddress();
String port = String.valueOf(addresss.getPort());
//创建容量为 128 字节的缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//获取读取到的字节数,读入缓冲区
int len = socketChannel.read(byteBuffer);
if (len > 0) {
byteBuffer.flip();//使缓冲区准备好读取
byte[] dst = new byte[len];
byteBuffer.get(dst);//一次读取到的字节数据
logger.info("接收到消息:" + toHex(dst) + " len:" + dst.length + " from:" + address + ":" + port);
/**
* 业务模块
**/
} else if (len == -1) {
//如果客户端断开连接,关闭socket
logger.info("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
协议解析:对象、报文互转
modbus通用工具类:
数据转对象:
public static ModbusDataAnalyzeBean dataAnalyzeBill(byte[] data) {
ModbusDataAnalyzeBean modbusDataAnalyzeBean = new ModbusDataAnalyzeBean();
modbusDataAnalyzeBean.setFrameHeader(Integer.parseInt(getOctFromHexBytes(data, 0, 1)));//获取帧头
modbusDataAnalyzeBean.setLength(Integer.parseInt(getOctFromHexBytes(data, 2)));//获取数据长度
modbusDataAnalyzeBean.setFuncode(Integer.parseInt(getOctFromHexBytes(data, 4)));//获取控制字
modbusDataAnalyzeBean.setAddr(Integer.parseInt(getOctFromHexBytes(data, 5)));//获取loraId(地址)
modbusDataAnalyzeBean.setDataO(bcdToString(data, 7, 18));
return modbusDataAnalyzeBean;
}
对象转数据:
public static byte[] data(ModbusDataFormationBean modbusDataFormationBean) {
byte[] command = {};
command = append(command, octInt2ByteArray(modbusDataFormationBean.getFrameHeader1(), 0));//设置帧头1
command = append(command, octInt2ByteArray(modbusDataFormationBean.getFrameHeader2(), 1));//设置帧头1
command = append(command, octInt2ByteArray(modbusDataFormationBean.getLength(), 0)); //设置数据长度
command = append(command, octInt2ByteArray(modbusDataFormationBean.getLength2(), 0));
command = append(command, octInt2ByteArray(modbusDataFormationBean.getFuncode(), 0));//设置控制字
command = append(command, octInt2ByteArray(modbusDataFormationBean.getLoraIdLow(), 0));//设置地址低字节(loraId)
command = append(command, octInt2ByteArray(modbusDataFormationBean.getLoraIdHigh(), 0));//设置地址高字节
command = append(command, str2Bcd(modbusDataFormationBean.getData()));//设置数据值
command = append(command, octInt2ByteArray(andVerification(command), 0));// 设置效验和
return command;
}
字符串转换为Ascii码
public static String stringTransformAscii(String hex) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hex.length() - 1; i += 2) {
String h = hex.substring(i, (i + 2));
int decimal = Integer.parseInt(h, 16);
sb.append((char) decimal);
}
return sb.toString();
}
取得十制数组的from~to位,并按照十六进制转化值
private static String getOctFromHexBytes(byte[] data, Object from, Object... to) {
if (data != null && data.length > 0 && from != null) {
try {
byte[] value;
int fromIndex = Integer.parseInt(from.toString());
if (to != null && to.length > 0) {
int toIndex = Integer.parseInt(to[0].toString());
if (fromIndex >= toIndex || toIndex <= 0) {
value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
} else {
value = Arrays.copyOfRange(data, fromIndex, toIndex + 1);
}
} else {
value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
}
if (value != null && value.length > 0) {
long octValue = 0L;
int j = -1;
for (int i = value.length - 1; i >= 0; i--, j++) {
int d = value[i];
if (d < 0) {
d += 256;
}
octValue += Math.round(d * Math.pow(10, 2 * j + 2));
}
return new Long(octValue).toString();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
16进制的字符串表示转成字节数组
public static byte[] hexString2ByteArray(String hexString, int... capacity) {
hexString = hexString.toLowerCase();
if (hexString.length() % 2 != 0) {
hexString = "0" + hexString;
}
int length = hexString.length() / 2;
if (length < 1) {
length = 1;
}
int size = length;
if (capacity != null && capacity.length > 0 && capacity[0] >= length) {
size = capacity[0];
}
final byte[] byteArray = new byte[size];
int k = 0;
for (int i = 0; i < size; i++) {
if (i < size - length) {
byteArray[i] = 0;
} else {
byte high = (byte) (Character.digit(hexString.charAt(k), 16) & 0xff);
if (k + 1 < hexString.length()) {
byte low = (byte) (Character.digit(hexString.charAt(k + 1), 16) & 0xff);
byteArray[i] = (byte) (high << 4 | low);
} else {
byteArray[i] = (byte) (high);
}
k += 2;
}
}
return byteArray;
}
连接字节流
private static byte[] append(byte[] datas, byte[] data) {
if (datas == null) {
return data;
}
if (data == null) {
return datas;
} else {
return concat(datas, data);
}
}
字节流拼接
private static byte[] concat(byte[]... data) {
if (data != null && data.length > 0) {
int size = 0;
for (int i = 0; i < data.length; i++) {
size += data[i].length;
}
byte[] byteArray = new byte[size];
int pos = 0;
for (int i = 0; i < data.length; i++) {
byte[] b = data[i];
for (int j = 0; j < b.length; j++) {
byteArray[pos++] = b[j];
}
}
return byteArray;
}
return null;
}
校验和获取,所有数据求和后%256 ,返回一个字节
public static Integer andVerification(byte[] bytes) {
int iSum = 0;
for (int i = 0; i < bytes.length; i++) {
iSum += bytes[i];
}
iSum %= 0x100;
return iSum;
}
String转成BCD码
public static byte[] StrToBCDBytes(String s) {
if (s.length() % 2 != 0) {
s = "0" + s;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
char[] cs = s.toCharArray();
for (int i = 0; i < cs.length; i += 2) {
int high = cs[i] - 48;
int low = cs[i + 1] - 48;
baos.write(high << 4 | low);
}
return baos.toByteArray();
}
BCD码的from~to位转成String
public static String bcdToString(byte[] data, Object from, Object... to) {
if (data != null && data.length > 0 && from != null) {
try {
byte[] value;
int fromIndex = Integer.parseInt(from.toString());
if (to != null && to.length > 0) {
int toIndex = Integer.parseInt(to[0].toString());
if (fromIndex >= toIndex || toIndex <= 0) {
value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
} else {
value = Arrays.copyOfRange(data, fromIndex, toIndex + 1);
}
} else {
value = Arrays.copyOfRange(data, fromIndex, fromIndex + 1);
}
if (value != null && value.length > 0) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < value.length; i++) {
int h = ((value[i] & 0xff) >> 4) + 48;
sb.append((char) h);
int l = (value[i] & 0x0f) + 48;
sb.append((char) l);
}
return sb.toString();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
对象设置:
modbus解析为对象的实体类
public class ModbusDataAnalyzeBean {
private Integer frameHeader;//帧头
private Integer length;//数据长度
private Integer funcode;//控制字
private Integer addr;//地址
private ArrayList<String> data;//数据段
private Integer num;//集抄数
private String dataO;//单数据
public Integer getFrameHeader() {
return frameHeader;
}
public void setFrameHeader(Integer frameHeader) {
this.frameHeader = frameHeader;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public Integer getFuncode() {
return funcode;
}
public void setFuncode(Integer funcode) {
this.funcode = funcode;
}
public Integer getAddr() {
return addr;
}
public void setAddr(Integer addr) {
this.addr = addr;
}
public ArrayList<String> getData() {
return data;
}
public void setData(ArrayList<String> data) {
this.data = data;
}
public String getDataO() {
return dataO;
}
public void setDataO(String dataO) {
this.dataO = dataO;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
}
拼接为modbus数据的实体类
public class ModbusDataFormationBean {
private Integer frameHeader1;//帧头1
private Integer frameHeader2;//帧头2
private Integer length;//数据长度
private Integer length2;//
private Integer funcode;//控制字
private Integer loraIdLow;//(地址)低字节
private Integer loraIdHigh;// (地址)高字节
private Integer num;//数据个数
private String data;//数据段
private ArrayList<String> dataLong; //数据段集合
private Integer checksum;//效验和
public ArrayList<String> getDataLong() {
return dataLong;
}
public void setDataLong(ArrayList<String> dataLong) {
this.dataLong = dataLong;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public Integer getLength2() {
return length2;
}
public void setLength2(Integer length2) {
this.length2 = length2;
}
public Integer getFrameHeader1() {
return frameHeader1;
}
public void setFrameHeader1(Integer frameHeader1) {
this.frameHeader1 = frameHeader1;
}
public Integer getFrameHeader2() {
return frameHeader2;
}
public void setFrameHeader2(Integer frameHeader2) {
this.frameHeader2 = frameHeader2;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public Integer getFuncode() {
return funcode;
}
public void setFuncode(Integer funcode) {
this.funcode = funcode;
}
public Integer getLoraIdLow() {
return loraIdLow;
}
public void setLoraIdLow(Integer loraIdLow) {
this.loraIdLow = loraIdLow;
}
public Integer getLoraIdHigh() {
return loraIdHigh;
}
public void setLoraIdHigh(Integer loraIdHigh) {
this.loraIdHigh = loraIdHigh;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Integer getChecksum() {
return checksum;
}
public void setChecksum(Integer checksum) {
this.checksum = checksum;
}
}