网络高级(学习)2024.9.10

news2024/9/24 23:31:03

目录

一、Modbus简介

1.起源

2.特点

3.应用场景

二、Modbus TCP协议

1.特点

2.协议格式

3.MBAP报文头

4.功能码

5.寄存器

(1)线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。

(2)离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,每个bit表示一个开关量,而开关量只能读取输入的开关信号,不能写的。

(3)保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。

(4)输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。

三、工具软件

1.modbus软件

Modbus slave端

Modbus poll端

2.wireshark软件

过滤器选择

过滤条件

3.网络调试助手

四、代码编程

1.读取保持寄存器中的数值(功能号03),起始地址40001,寄存器个数1个

2.编程实现主机功能,写入单个线圈状态(功能号05)

一、Modbus简介

1.起源

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

2.特点

免费、简单、容易使用

3.应用场景

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

二、Modbus TCP协议

1.特点

(1)Modbus TCP采用主从问答方式(master/slave)通信,有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点(可以多个),每一个slave设备都有一个唯一的地址 
(2)Modbus TCP是基于TCP实现的应用层协议
(3)默认端口号为502

2.协议格式

3.MBAP报文头

Modbus TCP协议包含一个7字节报文头

事务处理标识符:2字节,报文的序列号

协议标识符:2字节,0000表示Modbus TCP协议

长度:2字节,字节长度

单元标识符:1字节,从机地址

4.功能码

根据四种不同的寄存器设置了8种功能码,根据实际需要设置不同的功能码
在协议中,功能码占1个字节

功能码作用寄存器PLC地址位操作/字操作操作数量
01读线圈状态00001-09999位操作单个或多个
02读离散输入状态10001-19999位操作单个或多个
03读保持寄存器40001-49999字操作单个或多个
04读输入寄存器30001-39999字操作单个或多个
05写单个线圈00001-09999位操作单个
06写单个保持寄存器40001-49999字操作单个
15写多个线圈00001-09999位操作多个
16写多个保持寄存器40001-49999字操作多个

5.寄存器

Modbus TCP通过寄存器的方式存储数据。

一共有四种类型的寄存器,分别是:离散量输入、线圈、输入寄存器、保持寄存器。

离散量和线圈其实就是位寄存器(每个寄存器数据占1个字节),工业上主要用于控制IO设备。输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。

(1)线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。

所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。
功能码:0x01   0x05    0x0f

(2)离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,每个bit表示一个开关量,而开关量只能读取输入的开关信号,不能写的。

比如读取外部按键是按下还是松开。
功能码:0x02

(3)保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。

比如设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写
功能码:0x03    0x06    0x10

(4)输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。

一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值
功能码:0x04

三、工具软件

1.modbus软件

Modbus slave端

此端是从机,相当于服务器,需要先运行
        设置:setup->设置从机ID、指定寄存器、起始地址、个数
        连接:connection->connect,设置ip和端口号,进行连接

Modbus poll端

此端是主机,相当于客户端
        设置:setup->设置从机ID、功能码、起始地址、个数   
        连接:connection->connect,设置ip和端口号,进行连接

2.wireshark软件

过滤器选择

如果是在windows下本地测试选择Loopback adapter
如果数据经过路由器,选择WLAN

过滤条件

过滤ip:ip.addr == ip地址
过滤端口号:tcp.port == 端口号
过滤协议类型:协议类型名 
注:每个条件通过&&连接,最后敲回车生效

3.网络调试助手

四、代码编程

1.读取保持寄存器中的数值(功能号03),起始地址40001,寄存器个数1个

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd 失败");
        return -1;
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(502);
    saddr.sin_addr.s_addr = inet_addr("192.168.50.121");
    socklen_t addrlen = sizeof(saddr);

    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect失败\n");
        return -1;
    }
    printf("connect 成功\n");
    
#define N 32
    uint8_t buf[N] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
    uint8_t buf1[N];
    unsigned int n;
    send(sockfd, buf, N, 0);
    sleep(1);
    int ret = recv(sockfd, buf1, N, 0);
    if (ret < 0)
    {
        perror("recv失败");
        return -1;
    }
    else if (ret == 0)
    {
        printf("连接关闭\n");
        return 0;
    }
    else
    {
        for (int i = 0; i < ret; i++)
        {
            printf("0x%X ", buf1[i]);
        }
        printf("\n");
    }
    close(sockfd);
    return 0;
}

封装成函数:

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

#define N 32

int sockfd;

void read_hold_register(int socket, uint8_t addr, uint8_t fun, uint16_t addra, uint16_t count)
{
    uint8_t buf[N] = {0};
    buf[0] = 0x00; // 事务处理标识符(高位)
    buf[1] = 0x00; // 事务处理标识符(低位)
    buf[2] = 0x00; // 协议标识符(高位)
    buf[3] = 0x00; // 协议标识符(低位)
    buf[4] = 0x00;
    buf[5] = 0x06;            // 字节长度
    buf[6] = addr;            // 从机地址
    buf[7] = fun;             // 功能码
    buf[8] = addra >> 8;      // 寄存器起始地址(高位)
    buf[9] = addra & 0x00ff;  // 寄存器起始地址(低位)
    buf[10] = count >> 8;     // 寄存器数量(高位)
    buf[11] = count & 0x00ff; // 寄存器数量(低位)

    send(socket, buf, N, 0); // 发送请求

    memset(buf, 0, N);                 // 清空缓冲区
    int ret = recv(socket, buf, N, 0); // 接收响应
    if (ret < 0)
    {
        perror("recv失败");
        return;
    }
    else if (ret == 0)
    {
        printf("连接关闭\n");
        return;
    }
    else
    {
        for (int i = 0; i < ret; i++)
        {
            printf("0x%X ", buf[i]);
        }
        printf("\n");
    }
}

int main(int argc, char const *argv[])
{
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket 失败");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(502);
    saddr.sin_addr.s_addr = inet_addr("192.168.50.121");
    socklen_t addrlen = sizeof(saddr);

    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect 失败");
        close(sockfd);
        return -1;
    }
    printf("connect 成功\n");

    uint8_t fun, addr;
    uint16_t addra, count;

    printf("请输入功能码(格式0x01):");
    scanf(" %hhx", &fun);
    printf("请输入从机地址(格式0x01):");
    scanf(" %hhx", &addr);
    printf("请输入起始地址(格式0x0001):");
    scanf(" %hx", &addra);
    printf("请输入寄存器数量(格式0x0001):");
    scanf(" %hx", &count);

    read_hold_register(sockfd, addr, fun, addra, count);

    close(sockfd);
    return 0;
}

2.编程实现主机功能,写入单个线圈状态(功能号05)

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <errno.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd 失败");
        return -1;
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(502);
    saddr.sin_addr.s_addr = inet_addr("192.168.50.121");
    socklen_t addrlen = sizeof(saddr);

    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect失败\n");
        return -1;
    }
    printf("connect 成功\n");
    
#define N 32
    uint8_t buf[N] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00};
    uint8_t buf1[N];
    unsigned int n;
    send(sockfd, buf, N, 0);
    sleep(1);
    int ret = recv(sockfd, buf1, N, 0);
    if (ret < 0)
    {
        perror("recv失败");
        return -1;
    }
    else if (ret == 0)
    {
        printf("连接关闭\n");
        return 0;
    }
    else
    {
        for (int i = 0; i < ret; i++)
        {
            printf("0x%X ", buf1[i]);
        }
        printf("\n");
    }
    close(sockfd);
    return 0;
}

 封装成函数:

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

#define N 32

int sockfd;

void read_hold_register(int socket, uint8_t addr, uint8_t fun, uint16_t addra, uint16_t count)
{
    uint8_t buf[N] = {0};
    buf[0] = 0x00; // 事务处理标识符(高位)
    buf[1] = 0x00; // 事务处理标识符(低位)
    buf[2] = 0x00; // 协议标识符(高位)
    buf[3] = 0x00; // 协议标识符(低位)
    buf[4] = 0x00;
    buf[5] = 0x06;            // 字节长度
    buf[6] = addr;            // 从机地址
    buf[7] = fun;             // 功能码
    buf[8] = addra >> 8;      // 线圈地址(高位)
    buf[9] = addra & 0x00ff;  // 线圈地址(低位)
    buf[10] = count >> 8;     // 断通标志(高位)
    buf[11] = count & 0x00ff; // 断通标志(低位)

    send(socket, buf, 12, 0); // 发送请求

    memset(buf, 0, N);                 // 清空缓冲区
    int ret = recv(socket, buf, N, 0); // 接收响应
    if (ret < 0)
    {
        perror("recv失败");
        return;
    }
    else if (ret == 0)
    {
        printf("连接关闭\n");
        return;
    }
    else
    {
        for (int i = 0; i < ret; i++)
        {
            printf("0x%X ", buf[i]);
        }
        printf("\n");
    }
}

int main(int argc, char const *argv[])
{
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket 失败");
        return -1;
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(502);
    saddr.sin_addr.s_addr = inet_addr("192.168.50.121");
    socklen_t addrlen = sizeof(saddr);

    if (connect(sockfd, (struct sockaddr *)&saddr, addrlen) < 0)
    {
        perror("connect 失败");
        close(sockfd);
        return -1;
    }
    printf("connect 成功\n");

    uint8_t fun, addr;
    uint16_t addra, count;

    printf("请输入功能码(格式0x01):");
    scanf(" %hhx", &fun);
    printf("请输入从机地址(格式0x01):");
    scanf(" %hhx", &addr);
    printf("请输入线圈地址(格式0x0001):");
    scanf(" %hx", &addra);
    printf("请输入断通标志(格式0x0001):");
    scanf(" %hx", &count);

    read_hold_register(sockfd, addr, fun, addra, count);

    close(sockfd);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++——STL——栈(stack)

栈的定义 栈 &#xff08; stack &#xff09;是限定仅在表的一端进行插入和删除操作的线性表&#xff0c;允许插入和删除的一端称 为栈顶&#xff0c;另一端称为栈底&#xff0c;不含任何数据元素的栈称为空栈。 栈的示意图 因为栈只能够在一端进行插入和删除&#xff0c;所以…

【Lua学习】Lua入门

上一篇帖子【Lua学习】Lua最最基础的 – 经云的清净小站 (skycreator.top)讲了Lua是什么&#xff0c;Lua如何安装在Linux和Windows上。那么安装好之后&#xff0c;我们就要使用Lua实现我们的各种功能了。 首先&#xff0c;我们要先了解Lua一些最基本的内容&#xff0c;比如怎么…

杀毒软件 | Malware Hunter v1.189.0.816 绿色版

软件简介 Malware Hunter是由Glarysoft开发的一款专业安全防护软件。该软件的主要目的是保护用户的计算机免受恶意软件、病毒和其他网络威胁的侵害。它通过采用高效的云引擎和小红伞引擎&#xff0c;能够快速且全面地扫描电脑中的恶意软件&#xff0c;并进行强力清除&#xff…

5--SpringBoot、Mybatis

目录 Mybatis Mybatis入门操作步骤 1.准备工作 创建springboot工程 创建数据库表和实体类 连接数据库 创建接口XxxMapper 2.数据库连接池 Lombok 使用 Mybatis 准备工作 删除 日志输入 参数占位符 新增 更新 查询 驼峰命名 条件查询 XML 创建XML文件 编…

关于前端知识中框架概念部分的详细介绍

1、为什么要学习流行框架&#xff1f; 企业&#xff1a;为了提高效率&#xff0c;因为时间就是金钱。开发人员&#xff1a;提高了开发效率发展进程&#xff1a; JS>JQuery>模板引擎>框架时代&#xff08;Angular(2)、React、Vue&#xff09;好处&#xff1a;不用直接…

2.安卓逆向-初识java语言

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 上一个内容&#xff1a;1.安卓逆向-说明 关于java语言的介绍就不写了没啥用直接开始 首先java语言写的代码运行说明 …

kolors文生图框架安装

环境安装 根据Kolors【github】的指引&#xff0c;安装命令如下&#xff1a; apt-get install git-lfs git clone https://github.com/Kwai-Kolors/Kolors cd Kolors conda create --name kolors python3.8 conda activate kolors pip install -r requirements.txt python3 s…

Vue3+TS项目封装SVG图标显示组件vite-plugin-svg-icons插件使用

准备好svg文件 假设从iconfont-阿里巴巴矢量图标库下载了一个svg格式的图标&#xff0c;放在我们项目里&#xff0c;并重命名为ic_money.svg&#xff0c;相对路径为&#xff1a;src\assets\images\icons\ic_money.svg 安装vite-plugin-svg-icons插件 npm install vite-plugi…

常用电路及分析

前言 最近在研究一些简单的硬件知识&#xff0c;把在网上看到的一些常见电路分析总结了一下。 有纰漏请指出&#xff0c;转载请说明。 学习交流请发邮件 1280253714qq.com 串联稳压电路 三极管串联线性稳压电路原理详解及Multisim仿真_三极管稳压电路-CSDN博客 线性稳压电…

Java小白一文讲清Java中集合相关的知识点(八)

HashMap底层机制及源码剖析 HashMap底层维护了Node类型的数组table,默认为null 当创建对象时&#xff0c;将加载因子初始化为0.75&#xff1b; 当添加key-value时&#xff0c;通过key的哈希值得到在table的索引&#xff0c;然后判断该索引处是否有元素&#xff0c;如果没有元…

力扣 — — 2555. 两个线段获得的最多奖品

力扣 — — 2555. 两个线段获得的最多奖品 一、题目描述 题目大意&#xff1a;给定一个数组prizePositions&#xff0c;数组中的值表示的是奖品的位置&#xff0c;每一个位置可以有多个奖品&#xff0c;并且设定一个线段的长度 K K K&#xff0c;要求从所有奖品位置中选择两个…

修改Netty 中EventLoopGroup的线程名字前缀

此方案针对 netty 4 , 阅读Netty 源码的过程中涉及到多种线程跳转&#xff0c;2-1 3-1 4-1 类似的命名头晕眼花&#xff0c;直接改了成方便辨认的名字吧&#xff01; 代码如下&#xff1a; public static EventLoopGroup getEventLoopGroup(String name, int nThread) {Defaul…

第十一周:机器学习

第十一周周报 摘要Abstract机器学习1. 注意力机制&#xff08;下&#xff09;1.1 multi-head self-attention&#xff08;多头注意力机制&#xff09;1.2 Positional Encoding&#xff08;位置编码&#xff09;1.3 truncated self attention&#xff08;截断式注意力机制&#…

SVGJS使用

svgjs用于操作 SVG 和动画的轻量级库。 官网 SVG.js v3.2 |家 (svgjs.dev) 1、安装 npm install svgdotjs/svg.js 或者下载直接引用 2、使用 <script src"https://cdn.jsdelivr.net/npm/svgdotjs/svg.js3.0/dist/svg.min.js"></script> import { S…

Win11 22H2/23H2用户速来!9月更新补丁KB5043076已发布

系统之家于9月11日发出最新报道&#xff0c;微软针对Win11用户发布了9月最新的更新补丁KB5043076&#xff0c;22H2用户升级系统后&#xff0c;版本号升至22621.4169&#xff0c;23H2用户的系统版本也升至22631.4169。此次更新支持用户从Windows分享窗口将内容共享到安卓设备。以…

95. UE5 GAS RPG 实现创建多段飞弹攻击敌人

从这篇开始&#xff0c;我们将实现一些技能&#xff0c;比如多段火球术&#xff0c;闪电链等等。 在这一篇里&#xff0c;我们先实现多段火球术&#xff0c;技能可以通过配置发射出多个火球术进行攻击。 创建多段火球函数 首先在我们之前创建的RPGFireBolt.h类里面增加一个生…

k8s的环境配置

一、前期系统环境准备 准备3台主机&#xff1a;硬盘50G cpu2个 内存2G 1、3台主机同时配置 1&#xff09;关闭防火墙与selinux、NetworkManager [rootk8s-master ~]# systemctl stop firewalld[rootk8s-master ~]# systemctl disable firewalldRemoved symlink /etc/systemd/…

ctfshow-web入门-sql注入-web248-UDF 注入

udf 全称为&#xff1a;user defined function&#xff0c;意为用户自定义函数&#xff1b;用户可以添加自定义的新函数到 Mysql 中&#xff0c;以达到功能的扩充&#xff0c;调用方式与一般系统自带的函数相同&#xff0c;例如 contact()&#xff0c;user()&#xff0c;versio…

VUE实现刻度尺进度条

一、如下图所示效果: 运行后入下图所示效果: 实现原理是用div画图并动态改变进度, 二、div源码 <div style="width: 100%;"><div class="sdg_title" style="height: 35px;"><!--对话组[{{ dialogGroup.index }}]编辑-->&…

如何在微信中使用AI智能回复,真AI大模型;微加机器人免费智能回复功能

之前一直想实现在微信中使用AI大模型进行消息回复&#xff0c;也使用过很多开源的工具自己调OpenAI的API&#xff0c;但是整体太复杂&#xff0c;而且跑在自己电脑上也不稳定 今天发现微加机器人也支持AI回复&#xff0c;而且AI功能还是免费的&#xff0c;没有tokens收费 微加…