基于WebServer工业数据采集项目

注:实训项目,仅为记录分享自己编写项目的历程和心得,无其他用途。

一.项目实现图解

         项目主要要解决的就是网页服务器WebServer和服务程序之间产生连接,为了达成连接,用到了“中间商”CGI,这是一个通用网关接口,通过这个CGI使网页服务器WebServer和服务程序之间可以相互解析,然后实现相应的要求。

二.项目实现详解

        可以分成三个方面详解:一方面是服务程序,第二方面CGI通用接口中对网页输入的内容和服务程序发送的内容的处理,第三方面是网页的制作,在网页中通过一定的操作可以获得相应的数据或者改变线圈的状态。

        1)服务程序

        在服务程序中,用到的是多线程,线程1用来循环读取Modbus设备的数据(相当于是03操作),把数据循环读入共享内存;线程2用来改变线圈的状态(相当于05操作),在消息队列中等待接受队列中的数据。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include "modbus.h"
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
#include <sys/msg.h>

// 宏定义从机IP
#define IP 

// 线程传参结构体
struct keyword
{
    key_t key;
    modbus_t *ctx;
};

// 消息队列结构体
struct msgbuf
{
    long mtype;
    char msg_buf[32];
};

void *fun1(void *arg)
{
    struct keyword *k = (struct keyword *)arg;
    key_t key = k->key;
    modbus_t *ctx = k->ctx;

    // 2)创建或打开共享内存 shmget
    int shmid;
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
        if (17 == errno)
        {
            shmid = shmget(key, 128, 0666);
        }
        else
        {
            perror("shmget err");
            return NULL;
        }
    }

    // 3)映射共享内存到用户空间 shmat
    char *p = shmat(shmid, NULL, 0);
    if (p == (char *)-1)
    {
        perror("shmat err");
        return NULL;
    }

    // 4.寄存器操作
    uint16_t dest1[32] = {0};
    int num1, num2;

    char buf[128] = {0};

    while (1)
    {
        // 功能码对应的函数  __03__
        num1 = modbus_read_registers(ctx, 0, 4, dest1);
        if (num1 < 0)
        {
            perror("modbus_read_registers err");
        }

        for (int i = 0; i < num1 / 3; i++)
        {
            // 把每一次的数据存到共享内存中
            sprintf(buf, "X:%d Y:%d Z:%d 光线传感器:%d", dest1[i], dest1[i + 1], dest1[i + 2], dest1[i + 3]);
            strcpy(p, buf);
            printf("%s\n", p); // 打印一下共享内存内容看一看

            sleep(1);
        }
    }
}

void *fun2(void *arg)
{
    struct keyword *k = (struct keyword *)arg;
    key_t key = k->key;
    modbus_t *ctx = k->ctx;

    // 2))创建或打开消息队列 msgget
    int msgid;
    msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
    if (msgid <= 0)
    {
        if (17 == errno)
        {
            msgid = msgget(key, 0666);
        }
        else
        {
            perror("msgget err");
            return NULL;
        }
    }

    while (1)
    {
        // 4))读取消息:可以按照类型将消息从消息队列中度走 msgrcv
        struct msgbuf val;
        msgrcv(msgid, &val, sizeof(val) - sizeof(long), 1, 0);

        printf("val.buf = %s\n", val.msg_buf);
        int a = (int)val.msg_buf[4] - 48;
        int b = (int)val.msg_buf[5] - 48;

        printf("a = %d  b = %d\n", a, b);
        
        // 功能码对应的函数  __05__
        modbus_write_bit(ctx, a, b);
        if (0 == a)
        {
            if (0 == b)
                printf("LED关\n");
            else
                printf("LED开\n");
        }
        else
        {
            if (0 == b)
                printf("蜂鸣器关\n");
            else
                printf("蜂鸣器开\n");
        }
    }
}

int main(int argc, char const *argv[])
{
    // 创建共享内存
    // 1)创建key值  ftok
    key_t key = 12138;
    // key = ftok(".", 'b');
    // if (key < 0)
    // {
    //     perror("ftok err");
    //     return -1;
    // }

    // 1.创建实例
    // modbus_new_tcp
    modbus_t *ctx = modbus_new_tcp(IP, 502);
    if (NULL == ctx)
    {
        perror("mod_new_tcp err ");
    }

    // 定义结构体变量并给结构体初始化
    struct keyword kkk;
    kkk.key = key;
    kkk.ctx = ctx;

    // 2.设置从机ID
    // modbus_set_slave
    int mss1 = modbus_set_slave(ctx, 1);
    if (mss1 < 0)
    {
        perror("modbus_set_slave err");
    }

    // 3.连接从机
    // modbus_connect
    int mc1 = modbus_connect(ctx);
    if (mc1 < 0)
    {
        perror("modbus_connect err");
    }

    // 创建线程1
    pthread_t tid1;
    if ((pthread_create(&tid1, NULL, fun1, &kkk)) != 0)
    {
        perror("pthread err\n");
        return -1;
    }
    printf("pthread success\n");

    // 创建线程2
    pthread_t tid2;
    if ((pthread_create(&tid2, NULL, fun2, &kkk)) != 0)
    {
        perror("pthread err\n");
        return -1;
    }
    printf("pthread success\n");

    // 等待回收线程2
    pthread_join(tid2, NULL);
    // 等待回收线程1
    pthread_join(tid1, NULL);

    // 5.关闭套接字
    // modbus_close
    modbus_close(ctx);
    // 6.释放实例
    // 	modbus_free
    modbus_free(ctx);

    return 0;
}

        2)CGI通用接口

        在CGI中,首先要判断要解析的功能,这里我用到了get(获取寄存器的数据)和set(改变线圈的状态)。

        当在网页发送get时,CGI接收到以后,打开共享内存,获取共享内存中的数据,然后给服务器回复,回复内容按照http协议格式,最后把读取的内容追加进去,然后发送给网页。

        当在网页发送set时,可以设定一定的格式,比如set=00,可以把内容直接放入消息队列,在服务程序端可以通过下标来获得需要的参数,用于改变线圈的状态。

int parse_and_process(char *input)

{

    char val_buf[2048] = {0};


    // 创建共享内存和消息队列

    // 1)创建key值  ftok

    key_t key = 12138;


    if (0 == strncmp(input, "get", 3))

    {

        // 2)创建或打开共享内存 shmget

        int shmid;

        shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);

        if (shmid < 0)

        {

            if (17 == errno)

            {

                shmid = shmget(key, 128, 0666);

            }

            else

            {

                perror("shmget err");

                return -1;

            }

        }


        // 3)映射共享内存到用户空间 shmat

        char *p = shmat(shmid, NULL, 0);

        if (p == (char *)-1)

        {

            perror("shmat err");

            return -1;

        }


        strcpy(val_buf, p);


        //数据处理完成后,需要给服务器回复,回复内容按照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);


        // 4)撤销映射 shmdt

        shmdt(p);

    }

    else if (0 == strncmp(input, "set", 3))

    {

        // 2))创建或打开消息队列 msgget

        int msgid;

        msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);

        if (msgid <= 0)

        {

            if (17 == errno)

            {

                msgid = msgget(key, 0666);

            }

            else

            {

                perror("msgget err");

                return -1;

            }

        }


        // 3))添加消息:按照类型将消息添加到已打开的消息队列末尾 msgsnd

        struct msgbuf val;

        val.mtype = 1;

        strcpy(val.msg_buf, input);


        log_console("msg_buf = %s\n", val.msg_buf);


        msgsnd(msgid, &val, sizeof(val) - sizeof(long), 0);


        strcpy(val_buf, input);


        // 判断并给网页回复消息(可不写)
        if ('0' == val_buf[4])

        {

            if ('1' == val_buf[5])

            {

                strcpy(val_buf, "LED开");

            }

            else

            {

                strcpy(val_buf, "LED关");

            }

        }

        else

        {

            if ('1' == val_buf[5])

            {

                strcpy(val_buf, "蜂鸣器开");

            }

            else

            {

                strcpy(val_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);

    }

        3)网页的制作

        制作要求:

        1.一个文本框,可以用来接收读取到的寄存器的数据,在网页中输出出来。

        2.一个按钮,通过点击按钮,实时刷新文本框中的数据。

        3.四个可单选的开关,控制线圈的状态。

        4.一个文本框,用来接收通过开关改变线圈后,显示线圈状态。

        通过下方代码<body></body>内的内容可以完成页面的制作要求:

         通过下方代码<body></body>内input中的onclick链接子函数,然后通过子函数执行相应的操作。

<!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() {
            // 获取标签为result的标签,赋值给v
            var v = document.getElementsByName("result");
            // v[0].value = "hello";   // 测试一下


            // 向服务器请求数据
            // 参数url:地址
            // data:请求数据正文
            // callback:回调函数,当服务器有数据回复时可以在回调函数中处理

            XHR.post('/cgi-bin/web.cgi', "get", function (x, info) {
                // console.log(info);   // info表示服务器回复的数据
                v[0].value = info;
            })
        }

        function func1(obj) {
            // 获取标签为bite1的标签,赋值给v
            var v = document.getElementsByName("result");

            // 向服务器请求数据
            // 参数url:地址
            // data:请求数据正文
            // callback:回调函数,当服务器有数据回复时可以在回调函数中处理
            if ('open' == obj) {
                XHR.post('/cgi-bin/web.cgi', "set=01", function (x, info) {
                    console.log(info);   // info表示服务器回复的数据
                    v[1].value = info;
                })
            }
            else {
                XHR.post('/cgi-bin/web.cgi', "set=00", function (x, info) {
                    console.log(info);   // info表示服务器回复的数据
                    v[1].value = info;
                })
            }
        }


        function func2(obj) {
            // 获取标签为bite1的标签,赋值给v
            var v = document.getElementsByName("result");

            // 向服务器请求数据
            // 参数url:地址
            // data:请求数据正文
            // callback:回调函数,当服务器有数据回复时可以在回调函数中处理
            if ('open' == obj) {
                XHR.post('/cgi-bin/web.cgi', "set=11", function (x, info) {
                    console.log(info);   // info表示服务器回复的数据
                    v[1].value = info;
                })
            }
            else {
                XHR.post('/cgi-bin/web.cgi', "set=10", function (x, info) {
                    console.log(info);   // info表示服务器回复的数据
                    v[1].value = info;
                })
            }
        }


    </script>
</head>

<body>
    <h1>基于WebServer的工业数据采集项目</h1>
    <br>
    数据:
    <input type="text" name="result" style="width: 300px" value="">
    <input type="button" name="command" value="get" onclick="get_info()">
    <br>
    <br>
    LED灯:
    开:<input type="radio" name="bit1" id="open" onclick="func1(id)">
    关:<input type="radio" name="bit1" id="close" onclick="func1(id)">
    <br>
    蜂鸣器:
    开:<input type="radio" name="bit2" id="open" onclick="func2(id)">
    关:<input type="radio" name="bit2" id="close" onclick="func2(id)">
    <br>
    结果:
    <input type="text" name="result" value="">
</body>

</html>

 

三.结果展示

        1.点击get按钮获取实时数据

        

         2.通过点击LED开的开关打开LED灯

        

         3.通过点击蜂鸣器开的开关打开蜂鸣器

        

         4.通过点击LED关的开关关闭LED灯

        

 

需要注意的问题:

        1.key值的确定,一定要认真的检查key值,确保在共享内存或者消息队列的key是相对应的,不然无法进行进程间通信。

        2.网页端给CGI发送的数据是一个字符串,比如字符串set=00,所以在改变线圈的状态时,需要考虑在传参时对字符串中的数据处理。

        3.网页制作的过程中,要注意LED灯和蜂鸣器的开和关两个开关的name相对应,不然就会产生可以同时选择的错误。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值