赞
踩
1 简介
modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的modbus协议:modbusTCP
Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。
标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。
2 ModbusTCP数据帧
ModbusTCP的数据帧可分为两部分:MBAP+PDU。
2.1 报文头MBAP
MBAP为报文头,长度为7字节,组成如下:
事务处理标识 协议标识 长度 单元标识符
2字节 2字节 2字节 1字节
事务处理标识 :可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符 :00 00表示ModbusTCP协议。
长度 :表示接下来的数据长度,单位为字节。
单元标识符 :设备地址。
2.2 帧结构PDU
PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。
2.2.1 功能码
modbus的操作对象有四种:线圈、离散输入、输入寄存器、保持寄存器。
线圈:PLC的输出位,开关量,在MODBUS中可读可写
离散量:PLC的输入位,开关量,在MODBUS中只读
输入寄存器:PLC中只能从模拟量输入端改变的寄存器,在MODBUS中只读
保持寄存器RW:PLC中用于输出模拟量信号的寄存器,可读可写
根据对象的不同,modbus的功能码有:
0x01:读线圈
0x05:写单个线圈
0x0F:写多个线圈
0x02:读离散量输入
0x04:读输入寄存器
0x03:读保持寄存器
0x06:写单个保持寄存器
0x10:写多个保持寄存器
ip过滤
ip.src ==192.168.1.104 显示源地址为192.168.1.104的数据包列表
ip.dst==192.168.1.104, 显示目标地址为192.168.1.104的数据包列表
ip.addr == 192.168.1.104 显示源IP地址或目标IP地址为192.168.1.104的数据包列表
端口过滤
tcp.port ==80, 显示源主机或者目的主机端口为80的数据包列表。
tcp.srcport == 80, 只显示TCP协议的源主机端口为80的数据包列表。
tcp.dstport == 80,只显示TCP协议的目的主机端口为80的数据包列表。
Http模式过滤
http.request.method=="GET", 只显示HTTP GET方法的。
逻辑运算符为 and/or/not
过滤多个条件组合时,使用and/or,比如获取IP地址为192.168.1.104的ICMP数据包表达式为ip.addr == 192.168.1.104 and icmp
平时看到的数据都是十进制,在tcp/socket发出的消息将以十六进制编码,且有两种显示方式“0x00,0x01”和”00 01”(WireShark捕获的是后者)。记得要先把显示十六进制的选择项选中,在数据发送窗口即处于HEX输入模式了, 在里面直接输入HEX格式内容。
连接成功后,从WireShark的捕获可以看到通信中使用的是ascii码。
字节流转换为十六进制的函数:
public static String printHexString(byte[] b) {
String res = "";
for (int i = 0; i < b.length; i++) {
String hex = Integer.toHexString(b[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
res += hex;
}
return res;
}
socket通信/数据采集程序:
假设现在需要读取保持寄存器的数值,已知设备的地址、寄存器地址和数量,对应上述的tcp报文格式进行询问报文编码。
//创建Socket对象
Socket socket=new Socket("127.0.0.1",502);
//根据输入输出流和服务端连接
OutputStream outputStream=socket.getOutputStream();//获取一个输出流,向服务端发送信息
byte[] sendInfo = new byte[] { 0x00,0x00,0x00,0x00,0x00,0x06,0x01,0x03,0x00, 0x64, 0x00, 0x01 }; //发送给Modbus软件的消息
outputStream.write(sendInfo);
outputStream.flush();
InputStream inputStream=socket.getInputStream();//获取一个输入流,接收服务端的信息
byte[] bs = new byte[32];
inputStream.read(bs);
String res1=printHexString(bs); //输出十六进制,调用刚刚的转换函数
System.out.println(res1);
WireShark对应的详情:(十六进制与ascii码)
主站询问=client发出的tcp报文
从站应答=client收到的报文
在IntelliJ中的逐步调试情况:
其中,要注意的是字节的存放顺序:
大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中(正序);小端模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中(逆序)。
所以,上图中捕获的59139
和e7 03
实际为十进制999
和其十六进制03e7
的小端存储结果。到此,已成功取出想要的RW数值。
//关闭相对应的资源
socket.shutdownOutput();//关闭输出流
inputStream.close();
outputStream.close();
socket.close();
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。