基于 Modbus 的工业数据采集、控制(part 1)

news2024/10/1 19:33:54

在这里插入图片描述

HTTP 协议

简介

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

特点

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

协议格式

客户端请求消息格式

客户端发送一个 HTTP 请求到服务器,请求消息由:
请求行、请求头部、空行请求数据 四个部分组成。
在这里插入图片描述

请求行

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

请求方式

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 没有限制。

请求头

也被称作消息报头,请求头是由一些键值对组成,每行一对,关键字和值用英文冒号“:”分隔。允许客户端向服务器发送一些附加信息或者客户端自身的信息,典型的请求头如下:
在这里插入图片描述

空行

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

请求体

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

服务器响应消息格式

HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行响应正文
状态行:由三部分组成,HTTP 协议的版本号、状态码、以及对状态码的文本描述。
例如:HTTP/1.1 200 OK(CRLF)。(200表示请求已经成功)
在这里插入图片描述

WebServer

入门

// 循环服务器

#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;
}

效果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

postman 模拟测试 HTML

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后一口吃成胖子

在这里插入图片描述
在这里插入图片描述

服务器源码分析:
1)服务器初始化;
2)循环等待连接,连接后创建线程,调用 msg_request 函数,并在该函数中调用 handler_msg 函数分析数据;
3)handler_msg 中,首先查看协议,其次读请求方法、url 、参数,判断方法是什么,并对need_handler 赋值,确定请求资源路径。如果请求地址没有携带任何资源,那么默认返回 index.html;如果资源不存在,返回 404;如果需要处理,调用 handle_request 函数;如果不需要处理,调用 echo_www 函数,直接返回资源;
4)handle_request 函数主要用来获取 post 请求数据,调用 parse_and_process 函数处理正文内容(自行添加所需功能)
结合 post.html 网页部分的内容,做测试。

注意

1、网页发送正文内容需要加“”,因为网页发送的数据是字符串。
2、共享内存和消息队列数据收发问题的解决方案:
1) 在代码中加打印语句,确保两个进程用的是同一个id;
2) 由于程序是强制结束,再下次运行代码时,将消息队列删除一下;

查看和删除共享内存和消息队列:
ipcs -m :查看共享内存
ipcrm -m shmid:删除共享内存
ipcs -q:查看消息队列
ipcrm -q semid:删除消息队列

3、key值的创建路径指定/目录下的某个新建文件。
4、多使用打印语句,排查错误位置。

服务器端

// custom_handle.c

parse_and_process(char * input)	{				// input :post请求发送的正文

    ...
    // 请求modbus数据
    else if(strstr(input, "modbus_get"))
    {
        return handle_get(sock, input);
    }
    // 控制modbus设备
    else if(strstr(input, "modbus_set"))
    {
        return handle_set(sock, input);
    }
	...
}

static int handle_get(int sock, const char *input) {
    // 1.创建key
    // 2.创建或打开共享内存
    // 3.映射
    // 4.读取共享内存中的数据
    // 5.发送
}

static int handle_set(int sock, const char *input){
    // 1.创建key
    // 2.创建或打开消息队列
    // 3.将postman中下发指令添加到消息队列
    // 4.发送
}

采集控制程序

// client.c


// 采集传感器数据线程
void *info(void *arg) {
    // 创建或打开共享内存、映射
    while(1) {
        // 读寄存器的数据
        // 写入共享内存
        sleep(1);
    }
}

// 控制设备线程
void *control(void *arg) {
    //创建或打开消息队列
    while(1) {
        // 读消息队列
        // 控制设备
    }
}

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

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

相关文章

docker compose搭建渗透测试vulstudy靶场示例

前言 渗透测试&#xff08;Penetration test&#xff09;即网络安全工程师/安全测试工程师/渗透测试工程师通过模拟黑客&#xff0c;在合法授权范围内&#xff0c;通过信息搜集、漏洞挖掘、权限提升等行为&#xff0c;对目标对象进行安全测试&#xff08;或攻击&#xff09;&am…

docker部署paddleocr

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 环境 1. CentOS7  2. docker  3. PaddleOCR2.5.2 1.准备 1. 首先准备好需要打包的项目 2. 在该项目中创建Dockerfile文件 touch Dockerfile2. 编写Dockerfile # 从Python 3.8的官方镜像中创建(pyt…

OpenStack-train版安装之基础组件安装

基础组件安装 安装MariaDB&#xff08;数据库&#xff09;安装RabbitMQ&#xff08;消息队列&#xff09;安装Memcached&#xff08;缓存&#xff09; 安装MariaDB&#xff08;数据库&#xff09; 安装 # yum install mariadb mariadb-server python2-PyMySQL -y数据库配置 …

实用篇 | T-SNE可视化工具详情及代码示例

本文主要是为了快速的了解t-sne和如何快速使用&#xff01; 简要了解TSNE TSNE&#xff0c;降维方法之一。降维在机器学习中非常重要。这是因为如果使用高维数据创建模型&#xff0c;则很容易欠拟合。换句话说&#xff0c;有太多无用的数据需要学习。可以通过从各种数据中仅…

计算机毕业设计项目选题推荐(免费领源码)Java+ssm+MYSQL酒店大数据资源管理系统的设计与实现02029

摘要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对酒店大数据资源管理系统等问题&#xff0c;对…

常见网络安全防护

1 阻断服务攻击&#xff08;DOS&#xff09; 阻断服务攻击&#xff0c;想办法目标网络资源用尽变种&#xff1a;分布式阻断服务攻击 影响&#xff1a; 宽带消耗性&#xff08;消耗目标的带宽&#xff09;资源消耗型&#xff08;消耗目标的计算资源&#xff09; 解决方案&am…

flutter 输入框组件 高度问题

使用的组件名字为 TestField 组件 TestField 配置 占位文字 设置 decoration 属性 InputDecoration 中hintText去掉输入到 输入框的间距 InputDecoration 中contentPadding EdgeInsets.zero去掉边框中的间距 InputDecoration 中 使用 isDense:true设置输入框内文字的颜色 …

基于单片机设计的大气气压检测装置(STC89C52+BMP180实现)

一、前言 本项目设计一个大气气压检测装置&#xff0c;该装置以单片机为基础&#xff0c;采用STC89C52作为核心控制芯片&#xff0c;结合BMP180模块作为气压传感器。大气气压&#xff0c;也就是由气体重力在大气层中产生的压力&#xff0c;其变化与天气预报、气象观测以及高度…

双系统Ubuntu-22.04.3安装编译kaldi

Ubuntu物理内存要求85-100G以上&#xff0c;运行内存5-6G以上&#xff08;如果第一次安装的Ubuntu物理内存不够&#xff0c;请勿进行扩容&#xff0c;扩容易出现黑屏、蓝屏、死机的情况&#xff0c;应该卸载Ubuntu重新安装&#xff0c;在安装过程中进行内存分配&#xff1b;运行…

苹果手机内存满了怎么清理?这里有你想要的答案!

手机内存不足是一个比较普遍的现象。由于现在手机应用程序的功能越来越强大&#xff0c;所以占用的内存也越来越大。同时用户会在手机中存储大量的数据&#xff0c;如照片、视频、文档等&#xff0c;这些都会占用大量的手机空间。那么&#xff0c;苹果手机内存满了怎么清理&…

维视智造推出「镜片自动脱模视觉检测系统」,助力镜片脱模从高耗到高效

​ 一、行业热潮呼唤数字化转型 机器代人难题重重 随着人们生活水平的提高和消费观念的转变&#xff0c;眼镜的市场需求量也在逐年增加。青控镜、成人渐进镜、数码型眼镜......消费者在不同场景的细分需求催生出市场上种类繁多的镜片产品&#xff0c;面对行业热潮&#xff0c;提…

算法-二叉树-简单-二叉树的直径、将有序数组转换成二叉搜索树

记录一下算法题的学习9 二叉树的直径 题目&#xff1a;给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。两节点之间路径的 长度 由它们之间边数表示 读完题目&…

Java面向对象(高级)-- final关键字的使用

文章目录 一、 final的意义二、 final的使用&#xff08;1&#xff09; final修饰类&#xff08;2&#xff09; final修饰方法&#xff08;3&#xff09; final修饰变量1. 修饰成员变量1.1 举例11.2 举例2 2. 修饰局部变量2.1 举例12.2 举例2 &#xff08;4&#xff09;final搭…

信号功率放大器的工作原理和特点是什么

信号功率放大器是一种电子设备&#xff0c;用于将输入信号的功率进行放大&#xff0c;以达到所需的输出功率水平。它在各个领域中都有广泛的应用&#xff0c;包括音频放大器、射频放大器、激光功率放大器等。下面将详细介绍信号功率放大器的工作原理和特点。 工作原理&#xff…

基于 Modbus 的工业数据采集、控制(part 3)

Modbus 设备(利用 slave 模拟) Modbus 采集程序 client.c #include "client.h"modbus_t *ctx; key_t key_shm, key_msg; int shmid, msgid; struct shm *shm0; struct msgbuf msg0;void *collector(void *arg) {struct shm *p = (struct shm *)arg;while (1){sle…

ArgoWorkflow教程(一)---DevOps 另一选择?云原生 CICD: ArgoWorkflow 初体验

来自&#xff1a;探索云原生 https://www.lixueduan.com 原文&#xff1a;https://www.lixueduan.com/posts/devops/argo-workflow/01-deploy-argo-workflows/ 本文主要记录了如何在 k8s 上快速部署云原生的工作流引擎 ArgoWorkflow。 ArgoWorkflow 是什么 Argo Workflows 是…

深入理解路由协议:从概念到实践

路由技术是Internet得以持续运转的关键所在&#xff0c;路由是极其有趣而又复杂的课题&#xff0c;永远的话题。 SO&#xff1a;这是一个解析路由协议的基础文章。 目录 前言路由的概念路由协议的分类数据包在网络中的路由过程理解路由表的结构路由器关键功能解析 前言 在互联…

文件差分服务设计

需求 OTA&#xff08;Over-The-Air&#xff09;升级是一种至关重要的技术&#xff0c;用于更新嵌入式设备的固件或软件&#xff0c;以确保设备具备最新功能和修复漏洞。在OTA升级过程中&#xff0c;使用差异算法工具&#xff08;如bsdiff、hdiffpatch和xdelta3&#xff09;能够…

eNSP-直连通信实验

实验拓扑&#xff1a; 实验需求&#xff1a; 1. 按照图中的设备名称&#xff0c;配置各设备名称 2. 按照图中的IP地址规划&#xff0c;配置IP地址 3. 测试R1与R2是否能ping通 4. 测试R2与R3是否能ping通 5. 测试R1与R3是否能ping通 实验步骤&#xff1a; 1. 加入设备&…

2023亚太杯数学建模C题思路 - 我国新能源电动汽车的发展趋势

1 赛题 问题C 我国新能源电动汽车的发展趋势 新能源汽车是指以先进技术原理、新技术、新结构的非常规汽车燃料为动力来源( 非常规汽车燃料指汽油、柴油以外的燃料&#xff09;&#xff0c;将先进技术进行汽车动力控制和驱动相结 合的汽车。新能源汽车主要包括四种类型&#x…