C语言之网络高级编程笔记

news2024/12/28 5:09:46

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

html  

cgi

Modbus协议  (应用层)

工具:Modus Slave/Poll wireshark  Postman

一、Modbus起源

1.起源

     Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。

     Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种

     其中Modbus TCP是在施耐德收购Modicon后1997年发布的。

2.分类:

1)Modbus RTU:运行在串口上的协议,采用二进制表示形式以及紧凑的数据结构,通信效率较高,应用比较广泛

2)Modbus ASCII:运行在串口上的协议,采用ASCII码传输,利用特殊字符作为字节开始和结束的标志,传输效率较低,只有在传输数据量较少的时候才会考虑它

3)Modbus TCP:运行在以太网上的协议

优势:

     免费、简单、容易使用

应用场景:

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

Modbus TCP特点

1)采用主从问答方式进行通信

2)Modbus Tcp是应用层协议,基于传输层TCP协议实现

3)Modbus Tcp端口号默认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.功能码

根据四种不同的寄存器设置了8种功能码

点一个灯:05

读温度:03  04

具体协议分析可参考:

实例分享 | ModbusTCP报文详解

练习:读传感器数据,读1个寄存器数据,写出主从数据收发协议。

主机给从机:

|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-起始地址-|-寄存器个数-|

      0x0000         0x0000       0x0006      0x11       0x03     0x0015       0x0001

从机回主机:

|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-数据长度-|--数据--|

0x0000      0x0000      0x0005      0x11      0x03         0x02    0x0102

练习:写出控制IO设备开关的协议数据,操作1个线圈。

主机给从机:

|----------MBAP报文头----------|-功能码-|-起始地址-|-断通标志-|

0x0000  0x0000 0x0006 0x11   0x05     0x000b     0xFF00

从机回主机:

|----------MBAP报文头----------|-功能码-|-起始地址-|-断通标志-|

0x0000  0x0000 0x0006 0x11    0x05     0x000b     0xFF00

单个线圈:1线圈占1个字节,1个字节=8位,对应一个IO设备,设置为0xff00表示置位,0x0000表示复位

多个线圈:1位表示一个线圈,1位对应一个IO设备,1个字节对应8个线圈即8个IO设备

三、工具软件使用

1.Modbus Slave/Poll

1.软件默认安装

2.破解

点击connection->connect,输入序列号即可

3.使用

先设置

后连接(连接时注意先开启slave端(相当于服务器),后起poll端(相当于客户端))

查询主机ip:win + r 然后输入cmd 然后输入ipconfig   即可查询主机ip

2.网络调试助手

3.Wireshark使用

安装使用wireshark时注意把杀毒软件和防火墙关闭

捕获器选择:

windows如果连接有线网络,选择本地连接/以太网

如果连接无线网络,选择WLAN

如果只是在本机上的通信,选择NPCAP Loopback apdater

或Adapter for loopback traffic capture

过滤条件:

过滤端口:tcp.port == 502

过滤IP:ip.addr == 192.168.1.156(自己的ip地址)

练习:

在虚拟机写程序实现poll端功能,编写客户端实现和Slave通信。

03功能码:读保持寄存器

uint8_t seq[12]={0x00,....};

//创建套接字

//填充结构体

//连接

//发送

//接收

//打印数据

//关闭套接字

  1. 分别封装函数实现对保持寄存器的读取和对单个线圈的控制

读保持寄存器:函数参数(发送数据首地址,功能码,寄存器起始地址、寄存器个数、从机ID,接收数据首地址)


int sockfd;

void set_slave_id(uint8_t *p, int id) //设置从机id
{
    p[6]=id;//*(p+6)
}
void read_registers(uint8_t *p, int addr, int nb, 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, int function, int addr, int nb, uint8_t *dest)
{
    int i = 0;
    *(+ 5) = 6;              //后面字节数
    *(+ 7) = (char)function; //功能码
    *(+ 8) = addr >> 8;      //线圈高位地址
    *(+ 9) = addr & 0xff;    //线圈低位地址
    if (nb == 1)
        *(+ 10) = 0xff;
    else if (nb == 0)
        *(+ 10) = 0x00;
    *(+ 11) = 0x00;

    send(sockfd, p, 12, 0);
    recv(sockfd, dest, 64, 0);
}

int main()
{
    
    //创建套接字
    //填充结构体
    //连接
    //设置从机ID
    
    //调用函数收发
    
    //循环打印
     for (= 0; i < dest[8]; i++)
            printf("%#x  ", dest[9 + i]);
        printf("\n");
    //关闭套接字
    return 0
}

sudo hq_vm.sh

四、Modbus库

三方库的使用

【1】库的安装

1.库的安装配置

1.在linux中解压压缩包

将库压缩包复制到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.库的使用

1、gcc xx.c -I./install/include/modbus -L./install/lib -lmodbus

./a.out

解释:

-I 后需要指定出头文件的路径

-L 后需要指定库的路径

-l 后需要指定库名

因为库为动态库,所有运行时会报错,解决方法:

修改配置文件:

sudo vi /etc/ld.so.conf.d/my.conf

在文件中添加库的路径  如:/home/hq/install/lib

sudo ldconfig 执行生效

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】函数接口

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, 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

03功能码 读2个寄存器的值

05功能码 写一个线圈

 #include<stdio.h>
 #include<modbus.h>
 
 int 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灯、蜂鸣器

要求:

  1. 多任务编程:多线程
  2. 循环1s采集一次数据,并将数据打印至终端
  3. 同时从终端输入指令控制硬件设备

0  1 :led灯打开

0  0:led灯关闭

1  1:蜂鸣器开

1 0 :  蜂鸣器关

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#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 0
		modbus_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. 设置从机ID
	modbus_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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <modbus.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>


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服务器

Web Server中文名称叫网页服务器或web服务器。WEB服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览服务。

Web server的分类:Kangle、Nginx、apache等等

在嵌入式中常见的轻量级的服务器有:Lighttpd、 Shttpd,、Thttpd、Boa、Mini_httpd、Appweb、Goahead

1.1 Lighttpd服务器

LigHttpd是一个开源的轻量级嵌入式Web server,是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的web server环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。

1.2 服务器安装配置

1)解压

tar -xvf lighttpd-1.4.54.tar.gz

2)进入源码目录,创建文件夹web

cd lighttpd-1.4.54

mkdir web

3) 执行脚本文件

./configure --prefix=$PWD/web

4)执行Makefile文件

make

make install

1.3 目录创建及文件移动

1)将源码目录lighttpd-1.4.54下web文件夹移动到某个路径下

mv lighttpd-1.4.54/web  ~/work

2)在web目录下创建文件夹(config、log、run、www)

cd ~/work/web

mkdir  config  log  run  www

3)将源码目录lighttpd-1.4.54/doc/config下的conf.d lighttpd.conf modules.conf复制到~/web/config中

cp conf.d  lighttpd.conf  modules.conf   ~/work/web/config -r

4)修改log文件夹权限,并在log目录下创建error.log文件修改权限

chmod  777  log

touch  log/error.log

chmod  777  log/error.log

5)在www目录下创建htdocs文件夹存放网页文件

mkdir  www/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

sudo sbin/lighttpd -f config/lighttpd.conf -m lib/

(结束进程为:pkill lighttpd

2)测试

将index.html文件放到www/htdocs目录下

打开浏览器,在地址栏输入服务器的IP地址(虚拟机IP)即可看到主页。

 

2. CGI

2.1 CGI简介

早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。

随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序之间数据互通,于是出现了CGI通用网关接口。

简单理解,可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。

CGI(Common Gateway Interface)通用网关接口,是外部扩展应用程序与 Web 服务器交互的一个标准接口。

2.2 CGI特点

CGI是Web服务器和一个独立的进程之间的协议,它通过环境变量及标准输入/输出和服务器之间进行数据交互。

  1. 通过环境变量可以获得网页的请求方式、地址等
  2. 通过标准输入可以获取网页的消息正文
  3. 通过标准输出可以发送网页请求的数据

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 源码使用

  1. 首先将cgi_demo复制到虚拟机web目录下
  2. uxterm命令打开简化版终端,用who am i命令查看当前终端的文件,根据自己终端的文件修改log_consloe.h文件的内容

3)在www/htdocs下创建cgi-bin文件夹,在cgi源码目录(cgi_demo)执行make,会在cgi-bin路径下生成web.cgi

先确保服务器打开

sudo sbin/lighttpd -f config/lighttpd.conf -m lib/

(结束进程为:pkill  lighttpd)

3. Postman使用

测试使用

按照上面设置完postman后,将lighttpd服务器开启,当点击发送时,会在简化版终端上显示调试信息,同时在postman中也会看到回复的数据

任务:

通过postman模拟浏览器,实现Modbus Slave端数据采集和设备控制。

项目问题总结:

1.存在共享内存和消息队列数据收发问题时。

解决方案:

      (1)在代码中加打印语句,确保两个进程用的是同一个id

      (2)由于程序是强制结束,再下次运行代码时,将消息队列删除一下

           查看和删除共享内存和消息队列:

           ipcs  -m  :查看共享内存

           ipcrm  -m  shmid:删除共享内存

           ipcs -q:查看消息队列

           ipcrm  -q  semid:删除消息队列

2.key值的创建路径指定/目录下的某个新建文件

3.多使用打印语句,学会通过uxterm终端查看打印信息,排查错误位置

4.程序伪代码:

//CGI进程
parse_and_process(char *input)
{
        if(strcmp(input, "get") == 0)
        {
                //创建共享内存、映射
                //读取共享内存数据
                //将数据写至标准输出
        }
        else
        {
            //创建消息队列
            //将postman下发的指令添加至消息队列
            //给浏览器回复数据
        }
}

//和modbus slave交互的服务进程
//采集传感器数据线程
void *info(void *arg)
{
    //创建或打开共享内存、映射
    while(1)
    {
        //读保持寄存器
        modbus_read_registers();
        //将从slave端拿到的数据写到共享内存中
        sleep(1);
    }
}
//控制设备线程
void *control(void *arg)
{
    //创建或打开消息队列
    while(1)
    {
        //读取消息队列
        msgrcv();
        //根据消息队列的值进行设备控制,写线圈
        switch(msg.buf)
        {
            case LED: 写线圈;break
            case BEEP:写线圈;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 <stdio.h>
#include <modbus.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/msg.h>
#include "sys/msg.h"

#define LED 0
#define BEEP 1

struct 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网页代码:

<!DOCTYPE html>
<html lang="en">
 
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="js/xhr.js"></script>
    <script>
        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灯开!");
            }
        }
    </script>
    
</head>
 
<body>
    <div style = "color: greenyellow;background:darkslategrey">
    <p>
        <h1>********Webserver工业数据采集********</h1>
    </p>
    </div>
        <div style = "color:cyan;background:cornflowerblue">
        <h3>信息采集</h3>
        数据采集 
        <input type="button" name="flash" onclick="get_info()">
        <br/>
        光照强度:
        <input type="text" name="username" value="admin" >
        <br/>
        加速度:
        <input type="text" name="username" value="admin">
        <br/>
        <h3>设备控制</h3>
        LED灯
        on<input type="radio" name = "led" id = "set=0 1" onclick="set_info(id)">
        off<input type="radio" name = "led" id = "set=0 0" checked = "checked" onclick="set_info(id)">
        <br/>
        蜂鸣器
        on<input type="radio" name = "beef" id = "set=1 1" onclick="set_info(id)">
        off<input type="radio" name = "beef" id = "set=1 0" checked = "checked" onclick="set_info(id)">
    </div>
 
    </body>
 
</html>

六、http协议

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协议格式

1)客户端请求消息格式

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

  a. 请求行:请求行是由请求方法字段、url字段、http协议版本字段3个部分组成。请求行定义了本次请求的方式,格式如下:GET /example.html  HTTP/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.1 200 OK (CRLF) 。(200表示请求已经成功)

一. 开发环境:VScode

1. 在某路径下先新建文件夹,打开VScode打开文件夹,新建文件,文件命名为index.html

2. 安装库 open in browser

库安装完成后,在编写文本位置右击->open in other browser->选择合适的浏览器即可在网页显示html标签内容

输入html,选择html:5或者!回车可以将框架进行搭建

七、html语法

1.Html简介

HTML(英文Hyper Text Markup Language的缩写)中文译为“超文本标记语言”。是用来描述网页的一种语言。

所谓超文本,因为它可以加入图片、声音、动画、多媒体等内容,不仅如此,它还可以从一个文件跳转到另一个文件,与世界各地主机的文件连接。

HTML 不是一种编程语言,而是一种标记语言 (markup language)

Web 浏览器的作用是读取 HTML 文档,并以网页的形式显示出它们。浏览器不会显示 HTML 标签,而是使用标签来解释页面的内容

2.Html标签

1.标签格式:

(1)有尖括号包围的关键字,如:<html>

(2)通常成对存在,如<body></body>

(3)上面的标签前面是开始标签,后面是结束标签

2.标签分类:

(1)单标签:也称空标签    <标签名 />   如:<br/>

(2)双标签:成对存在      <标签名> 内容 </标签名>

3.常用标签:

1)h1-h6标题标签

2)p段落标签:

一个段落中会根据浏览器窗口的大小自动换行

格式:<p> 文本内容</p>

3)br换行标签:

格式:<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一致

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

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

相关文章

【Matlab】神经网络遗传算法函数极值寻优——非线性函数求极值

目前关于神经网络遗传算法函数极值寻优——非线性函数求极值的博客资源已经不少了&#xff0c;我看了下来源&#xff0c;最初的应该是来自于Matlab中文论坛&#xff0c;论坛出版的《MATLAB神经网络30个案例分析》第4章就是《神经网络遗传算法函数极值寻优——非线性函数极值寻优…

考研算法35天:三元组的最小距离 【双指针,滑动窗口,多路归并】

算法详解 多路归并;多路归并算法从理论到应用&#xff08;易懂&#xff09;_留恋单行路的博客-CSDN博客 多路归并就是将多个已经归并排序排好序的数组再进行排序(不一定是通过归并排序)。 算法题目 这道题就是一般做法是先通过排序将三个数组排好然后再进行三指针求最小。但…

4.23 时域微积分特性

时域微分还有个证明方式 2式两边求导即可推出时域微分特性

ElasticSearch学习01——Windows10环境下ES安装经验与踩到的坑

由于对ES基本概念和历史演进在网上随处可查,所以本文在此不做赘述.随意本文直接讲述如何安装使用ES 1.安装JDK ​ ElasticSearch是基于lucence开发的&#xff0c;也就是运行需要java jdk支持。所以要先安装JAVA环境。由于ElasticSearch 5.x 往后依赖于JDK 1.8的&#xff0c;所…

配置Jenkins的slave agent并使用它完成构建任务

上一章&#xff0c;使用单机配置并运行了一个简单的maven项目&#xff0c;并发布到了一个服务器上启动。这一章将要配置一个slave agent&#xff0c;并将上一章的job放到agent上执行。我们agent使用的是ssh的方式 前置步骤 准备两台虚拟机&#xff1a; 192.168.233.32&#…

4.27 功率谱

功率信号能量一定是无穷大的 1处解释&#xff0c;由于上述信号是截断信号&#xff0c;只有-T/2 ~ T/2有有效信号&#xff0c;因此有了1式 能量信号和能量密度构成傅里叶变换对 功率信号和功率密度构成傅里叶变换对 自相关函数和他的能量谱或者功率谱构成傅里叶变换对

Quiz 12: Regular Expressions | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Regular Expressions单选题&#xff08;1-8&#xff09;操作题Regular Expressions Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;Python 入门&#xff09; This course aims to teach everyone the …

CPU acceleration status: KVM requires a CPU that supports vmx or svm

关闭虚拟机&#xff0c;选择处理器将虚拟化引擎第一个选项选上&#xff0c;重新打开虚拟机&#xff0c;进行编译打开模拟器。 source build/envsetup.shlunch aosp_x86_64-engemulator

mesh网格数据解析及cesium绘制格网三角网可视化实现

代码实现运行效果 技术术语 Mesh通常指网络拓扑中的网状结构。在计算机网络中&#xff0c;Mesh是指每个节点都与其他节点相连&#xff0c;形成一个无中心的网状结构。Mesh网络常见于分布式计算、传感器网络、互联网等场景中。另外&#xff0c;在3D计算机图形学中&#xff0c;M…

前端web入门-移动web-day08

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 平面转换 平面转换 transform 平面转换 – 平移 平移实现居中效果 平面转换 – 旋转 平面转换 – 改…

最新特斯拉电机控制器4D1实拍

特斯拉发布过自己的扁线电机&#xff0c;最近Ingineerix大神发布了特斯拉最新动力系统的拆机视频&#xff0c;本篇结合之前的Tesla Model 3 SIC 控制器&#xff08;控制器具体命名不清楚&#xff0c;以最熟悉的一款车型进行描述我们不妨称之为第二代&#xff0c;第一代为单管IG…

Web安全——JavaScript基础(加入案例)

JavaScript基础 一、概述二、嵌入方法1、内嵌式2、外链式3、行内式 三、语句四、注释五、变量六、JavaScript 保留关键字七、JavaScript 作用域1、JavaScript 局部变量2、JavaScript 全局变量 八、数据类型1、判断类型2、数字类型&#xff08;Number&#xff09;3、字符串型&am…

满汉楼项目

满汉楼项目 1. 满汉楼介绍 满汉楼是一个综合餐饮管理系统&#xff0c;其主要分为&#xff1a; 人事登记&#xff1a;各部门人员信息登录管理&#xff1a;员工号、姓名、职位、密码菜谱价格&#xff1a;菜谱及价格报表统计&#xff1a;统计销售额成本及库房&#xff1a;名称注…

深度学习优化算法

梯度下降算法 随机梯度下降。随机梯度下降是指每次迭代在训练数据中随机抽取一个数据计算梯度来更新模型得参数。随机梯度下降容易受到噪声干扰,训练时间长,代价函数最终会围绕全局最小值或者局部极小值震荡。批量梯度下降。每次迭代时使用所有的训练数据来计算梯度更新模型的…

vue使用emit控制改变父组件的值,实现子组件的显示与隐藏

vue使用emit控制改变父组件的值&#xff0c;实现子组件的显示与隐藏 需求概述 父组件在提交表单后&#xff0c;弹框进行提示&#xff0c;子组件是一个弹框。 vue版本 v2.x 实现原理 在父组件内建立控制器isShowModal&#xff0c;使用v-if来控制子组件的显示与隐藏。在子组…

Node.js中的process.nextTick与浏览器环境中的nextTick有何不同?

文章目录 nextTick 是一个用于异步操作的函数Node.js中的process.nextTick vs 浏览器环境中的nextTick1. 执行时机2. 微任务队列3. 堆栈溢出风险4. 兼容性 nextTick 是一个用于异步操作的函数 nextTick 是一个用于异步操作的函数&#xff0c;用来在当前执行栈执行完毕后&#…

第2章 k-近邻算法

文章目录 第2章 k-近邻算法2.1k-近邻算法概述2.1.1准备&#xff1a;使用Python导入数据2.1.2实施kNN分类算法 2.2示例&#xff1a;使用k近邻算法改进约会网站的2.2.2分析数据&#xff1a;使用Matplotlib创建散点图2.2.3准备数据&#xff1a;归一化数值2.2.4测试算法 第2章 k-近…

C++中的继承(超详细)

文章目录 &#x1f4cd;前言C中的继承1.继承的概念及定义1.1 继承的概念1.2 继承的定义1.2.1 定义格式1.2.2 继承关系和访问限定符1.2.3 继承基类成员访问方式的变化 2. 基类和派生类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6.继承与静态成员7.复杂的菱…

Anaconda详细安装及配置教程(Windows)

Anaconda详细安装及配置教程&#xff08;Windows&#xff09; 一、下载方式1、官网下载2、网盘下载 二、安装三、配置四、创建虚拟环境 一、下载方式 1、官网下载 点击下载 点击window下载即可。 2、网盘下载 点击下载 二、安装 双击运行 点next 点I agree next 如…

4.26 能量谱

上述函数使用时域计算就很复杂&#xff0c;但是使用帕斯瓦尔就比较简单