基于Webserver的工业数据采集项目
html
cgi
Modbus协议(应用层)
工具:ModusSlave/Poll wireshark Postman
一、Modbus起源
1.起源:
Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。
Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是ModbusRTU、ModbusASCII和ModbusTCP三种
其中ModbusTCP是在施耐德收购Modicon后1997年发布的。
2.分类:
1)ModbusRTU:运行在串口上的协议,采用二进制表示形式以及紧凑的数据结构,通信效率较高,应用比较广泛
2)ModbusASCII:运行在串口上的协议,采用ASCII码传输,利用特殊字符作为字节开始和结束的标志,传输效率较低,只有在传输数据量较少的时候才会考虑它
3)ModbusTCP:运行在以太网上的协议
优势:
免费、简单、容易使用
应用场景:
Modbus协议是现在国内工业领域应用最多的协议,不只PLC设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备
ModbusTCP特点
1)采用主从问答方式进行通信
2)ModbusTcp是应用层协议,基于传输层TCP协议实现
3)ModbusTcp端口号默认502
二、ModbusTCP通信协议
ModbusTcp协议包含三部分:报文头、功能码、数据
ModbusTCP/IP协议最大数据帧长度为260字节
1.报文头
共7字节,分别是:
2.寄存器
包含四种寄存器,分别是线圈、离散量输入、保持寄存器、输入寄存器
1.离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。
线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。
对应上面的功能码也就是:0x010x050x0f
离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。
所以功能码也简单就一个读的0x02
2.输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。
保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写
所以功能码有对应的三个:0x030x060x10
输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值
对应的功能码也就一个0x04
3.功能码
根据四种不同的寄存器设置了8种功能码
点一个灯:05
读温度:03 04
具体协议分析可参考:
实例分享 | ModbusTCP报文详解
练习:读传感器数据,读1个寄存器数据,写出主从数据收发协议。
主机给从机:
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-起始地址-|-寄存器个数-|
0x0000 0x00000x00060x110x030x00150x0001
从机回主机:
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-数据长度-|–数据–|
0x0000 0x0000 0x00050x110x030x020x0102
练习:写出控制IO设备开关的协议数据,操作1个线圈。
主机给从机:
|———-MBAP报文头———-|-功能码-|-起始地址-|-断通标志-|
0x00000x00000x00060x110x050x000b0xFF00
从机回主机:
|———-MBAP报文头———-|-功能码-|-起始地址-|-断通标志-|
0x00000x00000x00060x110x050x000b0xFF00
单个线圈:1线圈占1个字节,1个字节=8位,对应一个IO设备,设置为0xff00表示置位,0x0000表示复位
多个线圈:1位表示一个线圈,1位对应一个IO设备,1个字节对应8个线圈即8个IO设备
三、工具软件使用
1.ModbusSlave/Poll
1.软件默认安装
2.破解
点击connection->connect,输入序列号即可
3.使用
先设置
后连接(连接时注意先开启slave端(相当于服务器),后起poll端(相当于客户端))
查询主机ip:win+r然后输入cmd 然后输入ipconfig即可查询主机ip
2.网络调试助手
3.Wireshark使用
安装使用wireshark时注意把杀毒软件和防火墙关闭
捕获器选择:
windows如果连接有线网络,选择本地连接/以太网
如果连接无线网络,选择WLAN
如果只是在本机上的通信,选择NPCAPLoopbackapdater
或Adapterforloopbacktrafficcapture
过滤条件:
过滤端口:tcp.port==502
过滤IP:ip.addr==192.168.1.156(自己的ip地址)
练习:
在虚拟机写程序实现poll端功能,编写客户端实现和Slave通信。
03功能码:读保持寄存器
uint8_t seq[12]={0x00,….};
//创建套接字
//填充结构体
//连接
//发送
//接收
//打印数据
//关闭套接字
- 分别封装函数实现对保持寄存器的读取和对单个线圈的控制
读保持寄存器:函数参数(发送数据首地址,功能码,寄存器起始地址、寄存器个数、从机ID,接收数据首地址)
intsockfd;void set_slave_id(uint8_t *p, intid) //设置从机id{p[6]=id;//*(p+6)}void read_registers(uint8_t *p, intaddr, intnb, uint8_t *dest){//给p指向数组赋值p[5]=6//字节数p[7]=0x03;p[8]=addr>> 8 ;//地址高位p[9]=addr&0xff;//地址低位p[10]=nb>> 8 ;//数量高位p[11]=nb&0xff;//数量低位//发送send(sockfd,p,12,0);//接收recv(sockfd,dest,64,0);}void write_coil(uint8_t *p, intfunction, intaddr, intnb, uint8_t *dest){inti= 0;*(p+ 5) = 6;//后面字节数*(p+ 7) = (char)function; //功能码*(p+ 8) =addr>> 8;//线圈高位地址*(p+ 9) =addr& 0xff;//线圈低位地址if (nb== 1)*(p+ 10) = 0xff;else if (nb== 0)*(p+ 10) = 0x00;*(p+ 11) = 0x00;send(sockfd,p, 12, 0);recv(sockfd,dest, 64, 0);}int main(){//创建套接字//填充结构体//连接//设置从机ID//调用函数收发//循环打印 for (i= 0;i<dest[8];i++)printf("%#x",dest[9 +i]);printf("\n");//关闭套接字return 0;}
sudohq_vm.sh
四、Modbus库
三方库的使用
【1】库的安装
1.库的安装配置
1.在linux中解压压缩包
将库压缩包复制到linux下,进行解压
tar-xvflibmodbus-3.1.7.tar.gz
2.进入源码目录,创建文件夹(存放头文件、库文件)
cdlibmodbus-3.1.7
mkdirinstall
3.执行脚本configure,进行安装配置(指定安装目录)
./configure –prefix=$PWD/install
4.执行make和makeinstall
make//编译
makeinstall//安装
执行完成后会在install文件夹下生产对应的头文件、库文件件夹,install用于存放产生的头文件、库文件等
2.库的使用
1、gccxx.c-I./install/include/modbus-L./install/lib-lmodbus
./a.out
解释:
-I后需要指定出头文件的路径
-L后需要指定库的路径
-l后需要指定库名
因为库为动态库,所有运行时会报错,解决方法:
修改配置文件:
sudovi/etc/ld.so.conf.d/my.conf
在文件中添加库的路径如:/home/hq/install/lib
sudoldconfig执行生效
2.要想编译方便,可以将头文件和库文件放到系统路径下
sudocpinstall/include/modbus/*.h/usr/include
sudocpinstall/lib/*-r/lib-d
后期编译时,可以直接gccxx.c-lmodbus
头文件默认搜索路径:/usr/include、/usr/local/include
库文件默认搜索路径:/lib、/usr/lib
【2】函数接口
1.建立连接
modbus_new_tcp:创建modbus句柄
modbus_set_slave:设置从机ID
modbus_connect:进行连接
2.销毁操作
modbus_free:释放modbus
modbus_close:关闭套接字
3.功能函数
8个功能码对应的函数
modbus_read_bits:01功能码
modbus_read_input_bits:02功能码
modbus_read_registers:03功能码
modbus_read_input_registers:04功能码
modbus_write_bit:05功能码
modbus_write_register:06功能码
modbus_write_bits:15功能码
modbus_write_registers:16功能码
modbus_t* modbus_new_tcp(const char *ip, intport)功能:以TCP方式创建Modbus实例,并初始化参数:ip:ip地址port:端口号返回值:成功:Modbus实例失败:NULLint modbus_set_slave(modbus_t *ctx, intslave)功能:设置从机ID参数:ctx:Modbus实例slave:从机ID返回值:成功:0失败:-1int modbus_connect(modbus_t *ctx)功能:和从机(slave)建立连接参数:ctx:Modbus实例返回值:成功:0失败:-1void modbus_free(modbus_t *ctx)功能:释放Modbus实例参数:ctx:Modbus实例void modbus_close(modbus_t *ctx)功能:关闭套接字参数:ctx:Modbus实例int modbus_read_bits(modbus_t *ctx, intaddr, intnb, uint8_t *dest)功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)参数:ctx:Modbus实例addr:寄存器起始地址nb:寄存器个数dest:得到的状态值intmodbus_read_input_bits(modbus_t *ctx, intaddr, intnb, uint8_t *dest)功能:读取输入状态,可读取多个连续输入的状态(对应功能码为0x02)参数:ctx:Modbus实例addr:寄存器起始地址nb:寄存器个数dest:得到的状态值返回值:成功:返回nb的值intmodbus_read_registers(modbus_t *ctx, intaddr, intnb, uint16_t *dest)功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)参数:ctx:Modbus实例addr:寄存器起始地址nb:寄存器个数dest:得到的寄存器的值返回值:成功:读到寄存器的个数失败:-1int modbus_read_input_registers(modbus_t *ctx, intaddr, intnb, uint16_t *dest)功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)参数:ctx:Modbus实例addr:寄存器起始地址nb:寄存器个数dest:得到的寄存器的值返回值:成功:读到寄存器的个数失败:-1intmodbus_write_bit(modbus_t *ctx, intaddr, intstatus);功能:写入单个线圈的状态(对应功能码为0x05)参数:ctx:Modbus实例addr:线圈地址status:线圈状态返回值:成功:0失败:-1intmodbus_write_bits(modbus_t *ctx, intaddr, intnb, const uint8_t *src);功能:写入多个连续线圈的状态(对应功能码为15)参数:ctx:Modbus实例addr:线圈地址nb:线圈个数src:多个线圈状态返回值:成功:0失败:-1intmodbus_write_register(modbus_t *ctx, intaddr, intvalue);功能:写入单个寄存器(对应功能码为0x06)参数:ctx:Modbus实例addr:寄存器地址value:寄存器的值返回值:成功:0失败:-1intmodbus_write_registers(modbus_t *ctx, intaddr, intnb, 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
03功能码 读2个寄存器的值
05功能码 写一个线圈
#include #includeint main() { modbus_t *ctx; int slave_id = 1; const char *ip = "192.168.50.96"; int port = 502; int regs_read = 2; uint16_t destination[2]; // 创建Modbus TCP实例 ctx = modbus_new_tcp(ip, port);// 设置从机ID modbus_set_slave(ctx, slave_id);// 连接到从机 modbus_connect(ctx); modbus_write_bit(ctx,0,88); /*// 读取保持寄存器的值 modbus_read_registers(ctx, 0, regs_read, destination);// 打印读取到的寄存器值 for (int i = 0; i <regs_read; i++) { printf("Register %d: %d\n", i, destination[i]); } */ // 关闭套接字和释放实例 modbus_close(ctx); modbus_free(ctx);return 0; }
任务:
编程实现采集传感器数据和控制硬件设备(传感器和硬件通过slave模拟)
传感器:2个,光线传感器、加速度传感器(x\y\z)
硬件设备:2个,led灯、蜂鸣器
要求:
- 多任务编程:多线程
- 循环1s采集一次数据,并将数据打印至终端
- 同时从终端输入指令控制硬件设备
01:led灯打开
00:led灯关闭
11:蜂鸣器开
10:蜂鸣器关
#include #include #include #include #include "modbus.h"modbus_t *ctx;//采集数据void *handler_data(void *arg){//modbus_t *ctx = (modbus_t *)arg;uint16_t dest[64] = {0};int i;while(1){modbus_read_registers(ctx, 0, 4, dest);for(i = 0; i < 4; i++)printf("data:%d ", dest[i]);printf("\n");sleep(1);}}//控制设备void *handler_ctl(void *arg){//modbus_t *ctx = (modbus_t *)arg;int dev, op;while(1){scanf("%d %d", &dev, &op); //1 0modbus_write_bit(ctx, dev, op);sleep(1);}}int main(int argc, const char *argv[]){//1. 创建modbus实例,并初始化int n = 0;uint8_t src[2] = {1, 0};pthread_t tid1, tid2;ctx = modbus_new_tcp(argv[1], atoi(argv[2]));if(ctx == NULL){perror("modbus new tcp error");return -1;}//2. 设置从机IDmodbus_set_slave(ctx, 1);//3. 建立连接if(modbus_connect(ctx) < 0){perror("modbus connect error");modbus_free(ctx);return -1;}//4.创建线程if(pthread_create(&tid1, NULL, handler_data, NULL)!=0){perror("pthread_create data err");return -1;}if(pthread_create(&tid2, NULL, handler_ctl, NULL)!=0){perror("pthread_create ctl err");return -1;}pthread_join(tid1, NULL);pthread_join(tid2, NULL);//5. 关闭套接字modbus_close(ctx);//6. 释放modbus实例modbus_free(ctx);return 0;}
#include #include #include #include #include #include #include int sockfd;uint16_t dest[64] = {};int main(){modbus_t *ctx;ctx = modbus_new_tcp("192.168.50.96", 502);int a = modbus_set_slave(ctx, 1);int b = modbus_connect(ctx);pid_t pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0){while (1){modbus_read_registers(ctx, 0, 4, dest);for (int i = 0; i < 4; i++){if (i == 0){printf("光线传感器:");printf("%d\n", dest[i]);}else if (i == 1){printf("加速度传感器x:%d\n", dest[i]);}else if (i == 2){printf("加速度传感器y:%d\n", dest[i]);}else if (i == 3){printf("加速度传感器z:%d\n", dest[i]);}putchar(10);}sleep(5);}exit(0);}else{int a, b;while (1){scanf("%d", &a);scanf("%d", &b);if (a == 0 && b == 1){printf("led灯打开\n");}else if (a == 0 && b == 0){printf("led灯关闭\n");}else if (a == 1 && b == 1){printf("蜂鸣器开\n");}else if (a == 1 && b == 0){printf("蜂鸣器关\n");}modbus_write_bit(ctx, a, b);}wait(NULL);}modbus_close(ctx);modbus_free(ctx);return 0;}
————————————————————————————————————————–
五、基于Webserver的工业数据采集项目
1.Webserver服务器
WebServer中文名称叫网页服务器或web服务器。WEB服务器也称为WWW(WORLDWIDEWEB)服务器,主要功能是提供网上信息浏览服务。
Webserver的分类:Kangle、Nginx、apache等等
在嵌入式中常见的轻量级的服务器有:Lighttpd、Shttpd,、Thttpd、Boa、Mini_httpd、Appweb、Goahead
1.1 Lighttpd服务器
LigHttpd是一个开源的轻量级嵌入式Webserver,是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的webserver环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。
1.2 服务器安装配置
1)解压
tar-xvflighttpd-1.4.54.tar.gz
2)进入源码目录,创建文件夹web
cdlighttpd-1.4.54
mkdirweb
3) 执行脚本文件
./configure–prefix=$PWD/web
4)执行Makefile文件
make
makeinstall
1.3 目录创建及文件移动
1)将源码目录lighttpd-1.4.54下web文件夹移动到某个路径下
mvlighttpd-1.4.54/web~/work
2)在web目录下创建文件夹(config、log、run、www)
cd~/work/web
mkdirconfiglogrunwww
3)将源码目录lighttpd-1.4.54/doc/config下的conf.dlighttpd.confmodules.conf复制到~/web/config中
cpconf.dlighttpd.confmodules.conf~/work/web/config-r
4)修改log文件夹权限,并在log目录下创建error.log文件修改权限
chmod777log
touchlog/error.log
chmod777log/error.log
5)在www目录下创建htdocs文件夹存放网页文件
mkdirwww/htdocs
1.4 修改配置文件
1)vi~/work/web/config/lighttpd.conf
##var.home_dir="/home/hq/work/web"#lighttpd操作的主目录var.log_root=home_dir+"/log"#日志文件目录(程序执行中出现的错误信息)var.server_root=home_dir+"/www"#存放html、cgi代码目录var.state_dir=home_dir+"/run"#存放pid文件服务运行起来后自动创建var.conf_dir=home_dir+"/config"#存放配置文件##var.vhosts_dir=home_dir+"/vhosts"##var.cache_dir=home_dir+"/cache"##var.socket_dir=home_dir+"/sockets"##server.port=80#端口号为80##server.use-ipv6="disable"#设置为禁用###server.bind="localhost"#默认即可##server.username="hq"#修改为当前用户,nobody为任何人都可以访问#server.groupname="nobody"#将其注释即可##server.document-root=server_root+"/htdocs"#存放html网页的文件##server.pid-file=state_dir+"/lighttpd.pid"##server.errorlog=log_root+"/error.log"#错误日志文件
2)vi~/work/web/config/modules.conf
include”conf.d/cgi.conf” 将此行注释打开(149)
3)vi~/work/web/config/conf.d/cgi.conf
$HTTP[“url”]=~”^/cgi-bin”{
cgi.assign=(“”=>””)
} 将这三行注释打开28-30行
1.5 运行测试
1)运行
cd ~/work/web
sudosbin/lighttpd-fconfig/lighttpd.conf-mlib/
(结束进程为:pkilllighttpd)
2)测试
将index.html文件放到www/htdocs目录下
打开浏览器,在地址栏输入服务器的IP地址(虚拟机IP)即可看到主页。
2. CGI
2.1 CGI简介
早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。
随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序之间数据互通,于是出现了CGI通用网关接口。
简单理解,可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。
CGI(CommonGatewayInterface)通用网关接口,是外部扩展应用程序与Web服务器交互的一个标准接口。
2.2 CGI特点
CGI是Web服务器和一个独立的进程之间的协议,它通过环境变量及标准输入/输出和服务器之间进行数据交互。
- 通过环境变量可以获得网页的请求方式、地址等
- 通过标准输入可以获取网页的消息正文
- 通过标准输出可以发送网页请求的数据
2.3 常见的环境变量
REQUEST_URI:访问此页面需要的URL,比如:“/index.html”
REQUEST_METHOD:获取客户端请求数据的方式:POST或GET
CONTENT_LENGTH:获取用户数据的长度
CONTENT_TYPE:网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
2.4 CGI工作原理
当浏览器向web服务器发送动态数据请求时,Web服务器主进程就会Fork创建出一个新的进程来启动CGI程序,也就是将动态脚本交给CGI程序来处理。
当CGI程序启动后会去解析动态脚本,然后将结果返回给Web服务器,最后由Web服务器将结果返回给客户端,之前Fork出来的进程也随之关闭。
这样,每次用户请求动态脚本,Web服务器都要重新Fork创建一个新进程去启动CGI程序,由CGI程序来处理动态脚本,处理完成后进程随之关闭。
对于一个CGI程序,主要的工作是从环境变量和标准输入中读取数据,然后处理数据,最后向标准输出中输出数据。(这里服务器将标准输入和标准输出做了重定向)
2.5 源码分析
在main函数中handle_requst获取网页发给服务器数据中,请求头(环境变量)和请求正文(标准输入)的信息,调用了parse_and_process函数,在函数中根据正文判断网页需要执行什么操作(获取传感器数据还是控制硬件设备),根据请求完成数据采集或设备控制操作,最终给网页回复(标准输出)数据(遵循HTTP协议)
2.6 源码使用
- 首先将cgi_demo复制到虚拟机web目录下
- uxterm命令打开简化版终端,用whoami命令查看当前终端的文件,根据自己终端的文件修改log_consloe.h文件的内容
3)在www/htdocs下创建cgi-bin文件夹,在cgi源码目录(cgi_demo)执行make,会在cgi-bin路径下生成web.cgi
先确保服务器打开
sudosbin/lighttpd-fconfig/lighttpd.conf-mlib/
(结束进程为:pkilllighttpd)
3. Postman使用
测试使用
按照上面设置完postman后,将lighttpd服务器开启,当点击发送时,会在简化版终端上显示调试信息,同时在postman中也会看到回复的数据
任务:
通过postman模拟浏览器,实现ModbusSlave端数据采集和设备控制。
项目问题总结:
1.存在共享内存和消息队列数据收发问题时。
解决方案:
(1)在代码中加打印语句,确保两个进程用的是同一个id
(2)由于程序是强制结束,再下次运行代码时,将消息队列删除一下
查看和删除共享内存和消息队列:
ipcs-m:查看共享内存
ipcrm-mshmid:删除共享内存
ipcs-q:查看消息队列
ipcrm-qsemid:删除消息队列
2.key值的创建路径指定/目录下的某个新建文件
3.多使用打印语句,学会通过uxterm终端查看打印信息,排查错误位置
4.程序伪代码:
//CGI进程parse_and_process(char *input){if(strcmp(input, "get") == 0){//创建共享内存、映射//读取共享内存数据//将数据写至标准输出}else{//创建消息队列//将postman下发的指令添加至消息队列//给浏览器回复数据}}
//和modbusslave交互的服务进程//采集传感器数据线程void *info(void *arg){//创建或打开共享内存、映射while(1){//读保持寄存器modbus_read_registers();//将从slave端拿到的数据写到共享内存中sleep(1);}}//控制设备线程void *control(void *arg){//创建或打开消息队列while(1){//读取消息队列msgrcv();//根据消息队列的值进行设备控制,写线圈switch(msg.buf){caseLED:写线圈;break;caseBEEP:写线圈;break;}}}
custom_handle.c代码:
#include "req_handle.h"#include "log_console.h"#include "sys/ipc.h"#include "sys/shm.h"#include "errno.h"#include "sys/msg.h"#define KB 1024#define HTML_SIZE (64 * KB)//普通的文本回复需要增加html头部#define HTML_HEAD "Content-Type: text/html\r\n" \"Connection: close\r\n"/** * @brief 处理自定义请求,在这里添加进程通信 * @param input * @return */struct msgbuf{long type;char buf[32];};int parse_and_process(char *input){char val_buf[2048] = {0};key_t key;int shmid;char *p=NULL;int msgid;struct msgbuf msg;//1.创建key值if((key = ftok("/mnt", 'm')) < 0){log_console("ftok err");return -1;}// strcpy(val_buf, input);//这里可以根据接收的数据请求进行处理if(input[0]=='g'){//2.创建或打开共享内存shmid = shmget(key, 128, IPC_CREAT|IPC_EXCL|0666);if(shmid <= 0){if(errno == EEXIST)shmid = shmget(key, 128, 0666);else{log_console("shmget err");return -1;}}//3.映射if((p = shmat(shmid, NULL, 0)) == (char *)-1){log_console("shmat err");return -1;}strcpy(val_buf,p);}else if(input[0]=='s'){//1.创建或打开消息队列msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);if(msgid <= 0){if(errno == EEXIST)msgid = msgget(key, 0666);else{log_console("msgget err");return -1;}} msg.type=1;strcpy(msg.buf,input);strcpy(val_buf,input);//2.添加消息队列msgsnd(msgid, &msg, sizeof(msg)-sizeof(long), 0);log_console("msgbuf:%s\n",msg.buf);}//数据处理完成后,需要给服务器回复,回复内容按照http协议格式char reply_buf[HTML_SIZE] = {0};sprintf(reply_buf, "%sContent-Length: %ld\r\n\r\n", HTML_HEAD, strlen(val_buf));strcat(reply_buf, val_buf);log_console("post json_str = %s", reply_buf);//向标准输出写内容(标准输出服务器已做重定向)fputs(reply_buf, stdout);return 0;}
manage.c 代码
#include #include #include #include #include #include #include #include #include "sys/msg.h"#define LED 0#define BEEP 1struct msgbuf{long type;char buf[32];};void *handler_info(void *arg){uint16_t dest[4] = {0};key_t key;int shmid;char *p = NULL;//1.创建key值 if((key = ftok("/mnt", 'm')) < 0){perror("ftok err");return NULL;}//2.创建或打开共享内存shmid = shmget(key, 128, IPC_CREAT|IPC_EXCL|0666);if(shmid <= 0){if(errno == EEXIST)shmid = shmget(key, 128, 0666);else{perror("shmget err");return NULL;}}//3.映射if((p = shmat(shmid, NULL, 0)) == (char *)-1){perror("shmat err");return NULL;}modbus_t *ctx = (modbus_t *)arg;while(1){modbus_read_registers(ctx, 0, 4, dest);sprintf(p, "%u,%u,%u,%u", dest[0],dest[1],dest[2],dest[3]);printf("传感器数据:%s\n", p);sleep(1);}}void *handler_contl(void *arg){modbus_t *ctx = (modbus_t*)arg;key_t key;int msgid;struct msgbuf msg;int dev, op;if((key = ftok("/mnt", 'm')) < 0){perror("ftok err");return NULL;}msgid = msgget(key, IPC_CREAT|IPC_EXCL|0666);if(msgid <= 0){if(errno == EEXIST)msgid = msgget(key, 0666);else{perror("msgget err");return NULL;}}while(1){//scanf("%d %d", &dev, &op);msgrcv(msgid, &msg, sizeof(msg)-sizeof(long), 1, 0);printf("msgbuf:%s\n", msg.buf);dev = msg.buf[4]-'0';op = msg.buf[6]-'0';switch(dev){case LED:modbus_write_bit(ctx, 0, op);break;case BEEP:modbus_write_bit(ctx, 1, op);break;}}}int main(int argc, const char *argv[]){modbus_t *ctx;pthread_t t1, t2;ctx = modbus_new_tcp(argv[1], 502);if(ctx == NULL){perror("modbus new tcp");return -1;}modbus_set_slave(ctx, 1);if(modbus_connect(ctx) < 0){perror("connect err");goto err;}printf("connect ok\n");if(pthread_create(&t1, NULL, handler_info, ctx) != 0){perror("create thread1 err");goto err;}if(pthread_create(&t2, NULL, handler_contl, ctx) != 0){perror("create thread2 err");goto err;}pthread_join(t1, NULL);pthread_join(t2, NULL);err:modbus_free(ctx);modbus_close(ctx);return 0;}
index.html网页代码:
Document function get_info(){var v = document.getElementsByName("username"); XHR.post('/cgi-bin/web.cgi',"get",function(x,info){ console.log(info); var val = info.split(",");v[0].value=val[0];v[1].value=val[1] + ',' + val[2] + ','+val[3]; })}function set_info(obj1){XHR.post('/cgi-bin/web.cgi',obj1,function(x,info){ })if(obj1 == 'set=1 0'){console.log("蜂鸣器关!");}else if(obj1 == 'set=1 1'){console.log("蜂鸣器开!");}else if(obj1 == 'set=0 0'){console.log("LED灯关!");}else if(obj1 == 'set=0 1'){console.log("LED灯开!");}} ********Webserver工业数据采集********
信息采集
数据采集
光照强度:
加速度:
设备控制
LED灯onoff
蜂鸣器onoff
六、http协议
1、Http简介
HTTP协议是HyperTextTransferProtocol(超文本传输协议)的缩写,是用于WebBrowser(浏览器)到WebServer(服务器)进行数据交互的传输协议。
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协议格式
1)客户端请求消息格式
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行、请求头部、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
a.请求行:请求行是由请求方法字段、url字段、http协议版本字段3个部分组成。请求行定义了本次请求的方式,格式如下:GET/example.htmlHTTP/1.1(CRLF)。
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数据类型
2)服务器响应消息格式:
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
状态行:由三部分组成,HTTP协议的版本号、状态码、以及对状态码的文本描述。例如:HTTP/1.1200OK(CRLF)。(200表示请求已经成功)
一.开发环境:VScode
1.在某路径下先新建文件夹,打开VScode打开文件夹,新建文件,文件命名为index.html
2.安装库openinbrowser
库安装完成后,在编写文本位置右击->openinotherbrowser->选择合适的浏览器即可在网页显示html标签内容
输入html,选择html:5或者!回车可以将框架进行搭建
七、html语法
1.Html简介
HTML(英文HyperTextMarkupLanguage的缩写)中文译为“超文本标记语言”。是用来描述网页的一种语言。
所谓超文本,因为它可以加入图片、声音、动画、多媒体等内容,不仅如此,它还可以从一个文件跳转到另一个文件,与世界各地主机的文件连接。
HTML不是一种编程语言,而是一种标记语言(markuplanguage)
Web浏览器的作用是读取HTML文档,并以网页的形式显示出它们。浏览器不会显示HTML标签,而是使用标签来解释页面的内容
2.Html标签
1.标签格式:
(1)有尖括号包围的关键字,如:
(2)通常成对存在,如
(3)上面的标签前面是开始标签,后面是结束标签
2.标签分类:
(1)单标签:也称空标签如:
(2)双标签:成对存在内容
3.常用标签:
1)h1-h6标题标签
2)p段落标签:
一个段落中会根据浏览器窗口的大小自动换行
格式:
文本内容
3)br换行标签:
格式:
4)div标签:
是一个块级元素,可以把文档分割为独立的、不同的部分,可以在div中嵌套标签
举例:
注:可以给div设置class或id,通过选择器设置属性,则内部成员具有相同属性
5)Input表单标签:
表示输入意思,是单标签
属性有多种:
这里重点讲type为text、radio
当type为text,表示是文本输入框
用法:
当type为radio,表示是单选框
用法:
解释:name:控件名称,同一组单选框设置相同名称
//Value:必须要有,是当点击时会提交的数据
Onclick:点击时会执行双引号中的处理函数
Checked:默认选中,同一组中只设置一个即可
6)Label标签:
label标签为input元素定义标注(标签)
作用:用于绑定一个表单元素,当点击label标签的时候,被绑定的表单元素就会获得输入焦点
注:这里for要跟input中的id一致