Day40-45 网络高级(modbus)

Modbus起源

1.起源:

Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。
Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种
其中Modbus TCP是在施耐德收购Modicon后1997年发布的。

2.分类:

1)Modbus RTU:
运行在串口上的协议,采用二进制表现形式以及紧凑型数据结构,通信效率高,应用广泛
2)Modbus ASCII:
运行在串口上的协议,采用ASCII码传输,并且利用特殊字符作为其字节的开始与结束标识,其传输效率要远远低于Modbus RTU协议,一般只有在通信数据量较小的情况下才考虑使用Modbus ASCII通信协议
3)Modbus TCP:
运行在以太网上的协议

3.优势:

 免费、简单、容易使用

4.应用场景:

Modbus协议是现在国内工业领域应用最多的协议,不只PLC设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备

5.ModbusTCP特点:

1.采用主从问答方式进行通信
2.ModbusTCP是应用层协议,基于传输层TCP进行传输的
3.ModbusTCP端口号默认为502

Modbus TCP协议格式

ModbusTcp协议包含三部分:报文头、功能码、数据
在这里插入图片描述

Modbus TCP/IP协议最大数据帧长度为260字节

1.报文头

包含7个字节,分别是:
在这里插入图片描述

2.寄存器

包含四种:离散量输入、线圈、输入寄存器、保持寄存器
1)离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。
线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。
对应上面的功能码也就是:0x01 0x05 0x0f
离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。
所以功能码也简单就一个读的 0x02
2)输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。
保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写
所以功能码有对应的三个:0x03 0x06 0x10
输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值
对应的功能码也就一个 0x04

3.功能码

在这里插入图片描述
具体协议分析可参考:
http://www.360doc.com/content/20/0804/12/43769266_928452485.shtml

读数据:
主机->从机:报文头(7) + 功能码(1) + 起始地址(2)+ 数量(2)
从机->主机:报文头(7) + 功能码(1) + 字节计数(1)+ 数据(?)

写单个:
主机->从机:报文头(7) + 功能码(1) + 地址(2)+ 数据/断通标志(2)

写多个:
主机->从机:报文头(7) + 功能码(1) + 起始地址(2)+ 数量(2)+字节计数(1)+数据(?)

练习:读传感器数据,读1个寄存器数据,写出主从数据收发协议。
主机给从机:
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-起始地址-|-寄存器个数-|
0x0000 0x0000 0x0006 0x01 0x03 0x0000 0x0001
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-数据长度-|-寄存器数据-|
0x0000 0x0000 0x0005 0x01 0x03 0x02 0x0102

工具软件使用

.Modbus Slave&Poll

1)软件默认安装
2)破解
点击connection->connect,输入序列号即可
3)使用
先设置
在这里插入图片描述
后连接(连接时注意先开启slave端(相当于服务器),后起poll端(相当于客户端))
在这里插入图片描述查询windows IP:

win r 输入cmd
输入 ipconfig

2.网络调试助手

在这里插入图片描述

.Wireshark的使用

捕获器选择:
windows如果连接有线网络,选择本地连接/以太网
如果连接无线网络,选择WLAN
如果只是在本机上的通信,选择NPCAP Loopback apdater
或Adapter for loopback traffic capture
过滤条件:
过滤端口:tcp.port==502
过滤IP:ip.addr == 192.168.3.11(windows 的ip)
在这里插入图片描述
在这里插入图片描述

Modbus RTU

1、与Modbus TCP的区别

在一般工业场景使用modbus RTU的场景还是更多一些,modbus RTU基于串行协议进行收发数据,包括RS232/485等工业总线协议。
与modbus TCP不同的是RTU没有报文头MBAP字段,但是在尾部增加了两个CRC检验字节(CRC16),因为网络协议中自带校验,所以在TCP协议中不需要使用CRC校验码。
RTU和TCP的总体使用方法基本一致,只是在创建modbus对象时有所不同,TCP需要传入网络socket信息;而RTU需要传入串口相关信息。

2、Modbus RTU特点

ModbusRTU也是主从问答协议,由主机发起,一问一答
设置串口参数:
设置串口参数时要求:
波特率为9600
8位数据位
1位停止位
无流控

3.ModbusRTU协议格式:

ModbusRTU协议数据帧包含四部分:地址码、功能码、数据、校验码
地址码:从机ID 1-247
功能码:同ModbusTCP协议
数据:起始地址、数量、数据
校验码:2字节,对地址码、功能码、数据部分进行校验,调用函数自动生成

4.报文详解:

03功能码:
主机->从机: 01 03 00 00 00 01 84 0A
01:从机ID
03:功能码
00 00:起始地址
00 01:数量
84 0A:校验码

从机->主机:01 03 02 00 14 b8 44
1.01:从机ID
03:功能码
02: 字节计数
00 14:数据
b8 44 :校验码

参考示例:
https://blog.csdn.net/qq153471503/article/details/124317894

5.模拟器的使用

由于实际硬件产品成本较高,我们这里可以使用Modbus软件模拟器,进行数据模拟从而分析Modbus协议。
使用工具:

  1. ModbusPoll(模拟主机)和ModbusSlave(模拟从机)
  2. vspd虚拟串口
  3. UartAssist串口调试工具
    设置串口参数要求:波特率为9600 8位数据位 1位停止位 无流控 无校验
    虚拟串口的使用:

一、虚拟串口的安装

1.将压缩包解压后,双击vspd.exe文件进行安装
在这里插入图片描述
2.安装完成后,找到安装目录,将Cracked下的文件复制到软件安装目录

3.打开软件,添加com1和com2端口(用完记得删除端口)
4.添加完端口后,打开设备管理器,这里出现如下图所示即可。

在这里插入图片描述

二、虚拟机绑定端口

  1. 将虚拟机在系统关机(必须是关机状态,挂起不行)状态下,点击虚拟机->设置->硬件->添加串行端口,添加COM1
    2.添加完成后,第一次使用需要将电脑重启
    3.重启之后,开启虚拟机,点击虚拟机->可移动设备->串行端口->连接
    4.当连接上虚拟串口后,在终端输入dmesg | grep tty,可以查看到对应的设备文件,其中默认的会有ttyS0文件,剩下的就是虚拟串口对应的设备文件

三、测试通信

1.Windows打开串口调试工具,选择好串口COM2->COM1,设置对应的波特率
在这里插入图片描述
2.在虚拟机运行minicom
在虚拟机安装minicom软件
sudo apt-get install minicom
在终端执行sudo minicom -s
1)选择serial port setup,回车
在这里插入图片描述2)设置设备文件,波特率,关闭流控,按如下图设置(文件改成自己的)

在这里插入图片描述
3)修改完成后,回车,保存修改,选择save setup as dfl,敲回车,再次选择exit回车
在这里插入图片描述4)退出后就可以和windows下的串口调试工具进行通信测试
5)也可以在这个界面输入字符,查看串口助手的显示情况。

在这里插入图片描述
6)退出:ctrl+A、Z,在弹出的界面里输入X 回车,即可退出。

四、将Modbus Slave模拟器作为RTU设备的从机

虚拟机绑定COM1端口,slave连接COM2端口,虚拟机通过编程测试串口通信
Modbus Slave端的配置如下:
在这里插入图片描述

Modbus库

【1】库的安装

1.库的安装配置

1.在linux中解压压缩包
tar -xvf libmodbus-3.1.7.tar.gz
2.进入源码目录,创建文件夹(存放头文件、库文件)
cd libmodbus-3.1.7
mkdir install
3.执行脚本configure,进行安装配置(指定安装目录)
./configure --prefix=$PWD/install
4.执行make和make install
make//编译
make install//安装
执行完成后会在install文件夹下生产对应的头文件、库文件件夹。install用于存放产生的头文件、库文件等

2.库的使用

要想编译方便,可以将头文件和库文件放到系统路径下
sudo cp install/include/modbus/.h /usr/include
sudo cp install/lib/
-r /lib -d
后期编译时,可以直接gcc xx.c -lmodbus
头文件默认搜索路径:/usr/include 、/usr/local/include
库文件默认搜索路径:/lib、/usr/lib

【2】函数接口

modbus_t*   modbus_new_tcp(const char *ip, int port)
功能:以TCP方式创建Modbus实例,并初始化
参数:
    ip   :ip地址
    port:端口号
返回值:成功:Modbus实例
      失败:NULL
int modbus_set_slave(modbus_t *ctx, int slave)
功能:设置从机ID
参数:
    ctx   :Modbus实例
    slave:从机ID
返回值:成功:0
       失败:-1
int   modbus_connect(modbus_t *ctx)
功能:和从机(slave)建立连接
参数:
    ctx:Modbus实例
返回值:成功:0
       失败:-1
void   modbus_free(modbus_t *ctx)
功能:释放Modbus实例
参数:ctx:Modbus实例
void   modbus_close(modbus_t *ctx)
功能:关闭套接字
参数:ctx:Modbus实例
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的状态值
int  modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取输入状态,可读取多个连续输入的状态(对应功能码为0x02)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb   :寄存器个数
    dest :得到的状态值
返回值:成功:返回nb的值
int  modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1
int   modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)
参数:
    ctx   :Modbus实例
    addr :寄存器起始地址
    nb    :寄存器个数
    dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
       失败:-1
int  modbus_write_bit(modbus_t *ctx, int addr, int status);
功能:写入单个线圈的状态(对应功能码为0x05)
参数:
    ctx     :Modbus实例
    addr  :线圈地址
    status:线圈状态
返回值:成功:0
      失败:-1
int  modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);
功能:写入多个连续线圈的状态(对应功能码为15)
参数:
    ctx     :Modbus实例
    addr  :线圈地址
    nb     :线圈个数
    src    :多个线圈状态
返回值:成功:0
      失败:-1
int  modbus_write_register(modbus_t *ctx, int addr, int value);
功能:  写入单个寄存器(对应功能码为0x06)
参数: 
    ctx    :Modbus实例
    addr  :寄存器地址
    value :寄存器的值 
返回值:成功:0
       失败:-1
int  modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);
功能:写入多个连续寄存器(对应功能码为16)
参数:
    ctx    :Modbus实例
    addr  :寄存器地址
    nb     :寄存器的个数
    src    :多个寄存器的值 
返回值:成功:0
      失败:-1


【3】编程流程

1.创建实例
modbus_new_tcp
2.设置从机ID
modbus_set_slave
3.连接
modbus_connect
4.读写操作
调用功能码对应函数
5.关闭套接字
modbus_close
6.释放示例
modbus_free

基于Modbus的工业数据采集项目

1.Http简介

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于Web Browser(浏览器)到Web Server(服务器)进行数据交互的传输协议。
HTTP是应用层协议
HTTP是一个基于TCP通信协议传输来传递数据(HTML 文件, 图片文件, 查询结果等)
HTTP协议工作于B/S架构上,浏览器作为HTTP客户端通过URL主动向HTTP服务端即WEB服务器发送所有请求,Web服务器根据接收到的请求后,向客户端发送响应信息。
HTTP默认端口号为80,但是你也可以改为8080或者其他端口

2.Http特点

HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

3.Http协议格式

客户端请求消息格式

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行、请求头部、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
在这里插入图片描述

a. 请求行:

请求行是由请求方法字段、url字段、http协议版本字段3个部分组成。请求行定义了本次请求的方式,格式如下:GET /example.html HTTP/1.1(CRLF)。

http的请求方式:
http协议中共定义了八种数据的请求方法。分别是:OPTIONS、HEAD、GET、POST、PUT、DELETE、TRACE、CONNECT;我们在实际应用中常用的也就是 get 和 post,其他请求方式也都可以通过这两种方式间接的来实现。
(增POST 删DELETE 改PUT 查GET)
GET方法和POST方法的区别?
GET通常用来从服务器上获得数据,而非修改信息;POST用来向服务器传递数据。
1.请求数据带参数,;GET请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。POST请求会把请求的数据放置在HTTP请求包的包体中。
因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。
2.传输数据的大小;在HTTP规范中,没有对URL的长度和传输的数据大小进行限制。但是在实际开发过程中,对于GET,特定的浏览器和服务器对URL的长度有限制。因此,在使用GET请求时,传输数据会受到URL长度的限制。对于POST,由于不是URL传值,理论上是不会受限制的,但是实际上各个服务器会规定对POST提交数据大小进行限制,Apache、IIS都有各自的配置
3.GET请求返回的内容可以被浏览器缓存起来。而每次提交的POST,浏览器在你按 下F5的时候会跳出确认框,浏览器不会缓存POST请求返回的内容
4.GET对数据进行查询,POST主要对数据进行增删改!简单说,GET是只读,POST是写
5.对于参数的数据类型,get只接受ASCII字符,而post没有限制。

b. 请求头:

也被称作消息报头,请求头是由一些键值对组成,每行一对,关键字和值用英文冒号“:”分隔。允许客户端向服务器发送一些附加信息或者客户端自身的信息,典型的请求头如下:
Accept:作用:描述客户端希望接收的 响应body 数据类型;示例:Accept:text/html
Accept-Charset:作用:浏览器可以接受的字符编码集;示例:Accept-Charset:utf-8
Accept-Language:作用:浏览器可接受的语言;示例:Accept-Language:en
Connection:作用:表示是否需要持久连接,注意HTTP1.1默认进行持久连接;示例:Connection:close
Content-Length:作用:请求的内容长度:示例:Content-Length:348
Content-Type:作用:描述客户端发送的 body 数据类型

c. 空行

最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。

d. 请求体

请求数据:请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的请求头是Content-Type和Content-Length。

WebServer

简单TCP服务器示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>

#define PORT 8080
#define BUFFER_SIZE 1024

void handle_request(int client_socket) {
    char buffer[BUFFER_SIZE];
    char response[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n<html><body><h1>Hello, World!</h1></body></html>";
 // 从客户端读取请求
        ssize_t bytes_read = read(client_socket, buffer, BUFFER_SIZE - 1);
    if (bytes_read == -1) {
        perror("读取请求失败");
        return;
    }
    buffer[bytes_read] = '\0';
 // 打印请求内容
    printf("收到请求:\n%s\n", buffer);
 // 发送响应给客户端
    ssize_t bytes_written = write(client_socket, response, strlen(response));
    if (bytes_written == -1) {
        perror("发送响应失败");
    }
}

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_address, client_address;
    socklen_t client_address_len;
    
    //创建套接字
    if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("创建套接字失败");
        exit(1);
    }
    //设置地址重用
    int reuse = 1;
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        perror("设置地址重用失败");
        exit(1);
    }
    //初始化绑定地址
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(PORT);
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("绑定地址失败");
        exit(1);
    }
    //启动监听
    if (listen(server_socket, 10) == -1) {
        perror("启动监听失败");
        exit(1);
    }

    printf("服务器已启动,监听端口 %d\n", PORT);
    
    // 接受连接并处理请求
    while (1) {
        client_address_len = sizeof(client_address);
        if ((client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_address_len)) == -1) {
            perror("接受连接失败");
            continue;
        }

        printf("接受新连接\n");
        
         // 处理请求
        handle_request(client_socket);
        // 关闭客户端套接字
        close(client_socket);
        
        printf("连接已关闭\n");
    }
     // 关闭服务器套接字
    close(server_socket);

    return 0;
}

1.服务器源码分析:

1.初始化服务器
2.循环等等待连接,连接后创建线程,调用线程函数msg_request,再函数中调用
handler_msg函数分析请求
3.handler_msg函数中,先查看请求内容,其次获取请求方法、URL、参数,判断请求方法是什么,对need_handler赋值,确定请求资源路径,如果请求地址没有携带任何资源,则默认返回index.html文件,如果资源不存在,返回404,如果需要处理(get请求带参数,post请求)调用handle_request函数,如果不需要(get请求不带参数且资源存在),调用echo_www函数,直接返回资源
4.handle_request函数主要用来获取post请求正文数据,调用parse_and_process函数处理正文内容(需要自己额外添加函数)

2.安装使用postman

在这里插入图片描述

3.结合Modbus部分整体流程分析

在这里插入图片描述

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值