目录
一,项目框架
1.服务程序(代码)通过ModbusTCP协议实现Modbus设备之间的通信
2.要实现的是将Modbus设备上的数据采集信息到网页中,通过网页点击开关控制Modbus设备在网页中点击一个链接相当于给服务器发起请求,网页不能直接和程序进行通信,一定是放到服务器上,所以需要有一个Webserver服务器,通过http协议进行通信
3.服务器会去处理网页的请求,因为需要动态的去处理,所以当网页给Webserver发请求时,服务器创建一个子进程,启动CGI程序,Webserver和CGI之间是fork出来的。
4.CGI是一个进程,写的服务程序也是一个进程,两者可以通过进程间通信的方式进行通信,因为采集信息需要是最新的、实时的,所以两个进程间可以通过共享内存(最高效、实时)读取信息或者通过消息队列(控制设备)。
二,LigHttpd 服务器(常用,以此为例)
LigHttpd是一个开源的轻量级嵌入式Web server,是提供一个专门针对高性能网站,安全、快速、兼容性好并且灵活的web server环境。具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点。
1.服务器安装配置
1)解压
tar -xvf lighttpd-1.4.54.tar.gz
2)进入源码目录,创建文件夹web
cd lighttpd-1.4.54
mkdir web
执行configure脚本文件
./configure --prefix=$PWD/web
执行Makefile文件
make
make install
2.目录创建及文件移动
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
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
3.修改配置文件
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" #将此行注释打开
3) vi ~/work/web/config/conf.d/cgi.conf
$HTTP["url"] =~ "^/cgi-bin" {
cgi.assign = ( "" => "" )
} 将这三行注释打开28-30行
4.运行
运行
cd ~/work/web
sudo sbin/lighttpd -f config/lighttpd.conf -m lib/
(结束进程为:pkill lighttpd)
三,CGI
CGI特点
CGI是Web服务器和一个独立的进程之间的协议,它通过环境变量及标准输入/输出和服务器之间进行数据交互。
- 通过环境变量可以获得网页的请求方式、地址等 (头部信息)
- 通过标准输入可以获取网页的消息正文 (判断采集信息还是控制设备)
- 通过标准输出可以发送网页请求的数据 (共享内存采集数据,再写到 基于网络的标准输出进行通信)
CGI工作原理
源码分析
在main.c函数中通过handle_requst获取网页给服务器发送的数据中,请求头(环境变量)和请求正文(标准输入)信息,在函数中调用parse_and_process函数,在函数中根据请求正文判断网页需要执行什么操作(获取传感器数据还是控制硬件设备),根据请求完成数据采集和硬件控制,最终给网页回复(标准输出)数据(遵循http协议格式)
四,主要代码
1.modbus tcp 通信主程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <modbus.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char buf[128]; /* message data */
} msg;
int sockfd;
uint16_t dest[64] = {};
int main()
{
modbus_t *ctx;
ctx = modbus_new_tcp("192.168.50.112", 502);
if (ctx == NULL)
{
perror("error\n");
return -1;
}
int id = modbus_set_slave(ctx, 1);
if (id < 0)
{
perror("id err");
return -1;
}
int x = modbus_connect(ctx);
if (x < 0)
{
perror("connect err");
return -1;
}
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程
{
while (1)
{
//创建key值
key_t key = ftok("/home", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%#x\n", key);
//创建或打开共享内存
int 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 -1;
}
}
printf("shmid:%d\n", shmid);
//映射共享内存,NULL:由系统自动完成映射,0可读可写
char *p = (char *)shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
modbus_read_registers(ctx, 0, 4, dest);
sprintf(p, "光传感器:%#x 加速度传感器x:%#x 加速度传感器y:%#x 加速度传感器z:%#x\n", dest[0], dest[1], dest[2], dest[3]);
printf("%s\n", p);
sleep(5);
}
exit(0);
}
else
{
int a, b;
while (1)
{
key_t key = ftok("/home", '1');
if (key < 0)
{
perror("ftok err");
return -1;
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
}
msgrcv(msgid, &msg, sizeof(msg) - sizeof(long), 1, 0); //获取正文消息
printf("msgbuf:%s\n", msg.buf);
if (msg.buf[4] == '0' && msg.buf[6] == '1')
{
printf("led灯打开\n");
modbus_write_bit(ctx, 0, 1);
}
else if (msg.buf[4] == '0' && msg.buf[6] == '0')
{
printf("led灯关闭\n");
modbus_write_bit(ctx, 0, 0);
}
else if (msg.buf[4] == '1' && msg.buf[6] == '1')
{
printf("蜂鸣器开\n");
modbus_write_bit(ctx, 1, 1);
}
else if (msg.buf[4] == '1' && msg.buf[6] == '0')
{
printf("蜂鸣器关\n");
modbus_write_bit(ctx, 1, 0);
}
}
}
modbus_close(ctx);
modbus_free(ctx);
wait(NULL);
exit(0);
return 0;
}
2.CGI主程序(custom_handle.c)
#include "req_handle.h"
#include "log_console.h"
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.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 mtype; //消息类型
char buf[128]; //消息正文
} msg;
int parse_and_process(char *input)
{
char val_buf[2048] = {0};
//这里可以根据接收的数据请求进行处理
if (strcmp(input, "get") == 0)
{
//创建key值
key_t key = ftok("/home", 'a');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%#x\n", key);
//创建或打开共享内存
int 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 -1;
}
}
printf("shmid:%d\n", shmid);
//映射共享内存,NULL:由系统自动完成映射,0可读可写
char *p = (char *)shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
log_console("%s", p);
strcpy(val_buf, p); //读取共享内存的数据 //将数据写道标准输出val_buf
}
else
{
key_t key = ftok("/home", '1');
if (key < 0)
{
perror("ftok err");
return -1;
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return -1;
}
printf("msgid:%d\n", msgid);
msg.mtype = 1;
strcpy(msg.buf, input);
msgsnd(msgid, &msg, sizeof(msg) - sizeof(long), 0);
log_console("msgbuf:%s\n", msg.buf);
strcpy(val_buf, input);
}
}
//数据处理完成后,需要给服务器回复,回复内容按照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;
}
运行结果: