2023.6.26 学习了整体的流程和框架的功能,理解了如何通过网页来控制从机
说明
涉及技术点:CGI,modbus,线程,共享内存,消息队列
实现平台:linux
缺陷:服务器没有自己写,CGi和modbus都是框架下面添加功能,没有纯手撸。
效果展示:linux打开服务器,win打开从机。实现效果是点击获取温度,能获取到从机设备保持寄存器,网页点击蜂鸣器开关,从机实现线圈改变。
- 网页浏览器:用户的操作界面,输入和得到相关直接消息
- 服务器:响应浏览器的请求,回复数据给浏览器,收发消息给CGI
- CGI:收发消息给服务器,通讯输入输出服务程序
- 服务程序:读取控制modbus设备,输入输出数据给CGI
- modbus设备:接受与发送数据给服务程序
上代码
1.服务程序
struct msgbuf
{
long mtype;
char mtext[128];
}msg;
char buf[32]="";
modbus_t* ctx; //同一个句柄
//线程函数
void *handler(void *arg)
{
uint16_t dest[32]={0};
key_t key;
//1.创建一个唯一的key值
key = ftok("/a.c", 'A');
if (key < 0)
{
perror("ftok err");
return 0;
}
printf("key=%#x\n", key);
//2.创建共享内存或打开,返回一个共享内存的id
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0777);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 128, 0777);
}
else
{
perror("shmget err");
return 0;
}
}
char *p = (char *)shmat(shmid, NULL, 0);
printf("shmid=%d\n", shmid);
//3.建立共享内存和虚拟地址得映射关系
if (p == (char *)-1)
{
perror("shmat err");
return 0;
}
// 将数据存入共享内存
while(1)
{
sleep(1);
modbus_read_registers(ctx, 0, 2, dest);
sprintf(buf, "%d %d %d %d\n",dest[0],dest[1],dest[2],dest[3]);
//printf("%d %d %d %d",dest[0],dest[1],dest[2],dest[3]);
strcpy(p,buf); //通过共享内存p来通信CGI
}
}
int main(int argc, char const *argv[])
{
ctx = modbus_new_tcp("192.168.50.151",502);
modbus_set_slave(ctx,1);
modbus_connect(ctx);
pthread_t tid;
if(pthread_create(&tid,NULL,handler,NULL) != 0)
{
printf("create thread err\n");
modbus_free(ctx);
return -1;
}
key_t key = ftok("/a.c", 'B');
if (key < 0)
{
perror("ftok err.");
return -1;
}
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0777);
if (msgid < 0)
{
if (errno == EEXIST)
{
msgid = msgget(key, 0777);
}
else
{
perror("msgget err.");
return -1;
}
}
while (1)
{
msgrcv(msgid,&msg,sizeof(msg),'B',0);//等待CGI传来消息,在执行操作
if(strncmp(msg.mtext,"0 1",3) == 0)
{
modbus_write_bit(ctx, 0, 1);
}
else if(strncmp(msg.mtext,"0 0",3) == 0)
{
modbus_write_bit(ctx, 0, 0);
}
else if(strncmp(msg.mtext,"1 1",3) == 0)
{
modbus_write_bit(ctx, 1, 1);
}
else if(strncmp(msg.mtext,"1 0",3) == 0)
{
modbus_write_bit(ctx, 1, 0);
}
}
modbus_close(ctx);
modbus_free(ctx);
pthread_join(tid, NULL);
return 0;
}
2.CGI程序核心代码
struct msgbuf
{
long mtype;
char mtext[128];
};
int parse_and_process(char *input) //这个input是服务器输入的
{
char val_buf[2048] = {0};
strcpy(val_buf, input);
// 这里可以根据接收的数据请求进行处理
// 判断input的值
if (strncmp(val_buf, "get", 3) == 0) //判断是不是get命令
{
// 创建唯一的key值
key_t key = ftok("/a.c", 'A');
if (key < 0)
{
perror("ftok err");
return -1;
}
// 创建共享内存
int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0777);
if (shmid < 0)
{
if (errno == EEXIST)
{
shmid = shmget(key, 128, 0777);
}
else
{
perror("shmget err");
return -1;
}
}
// 建立共享内存和虚拟地址的映射关系
char *p = (char *)shmat(shmid, NULL, 0);
if (p == (char *)-1)
{
perror("shmat err");
return -1;
}
// 将数据写入stdout
strcpy(val_buf, p); //将服务程序拿到的数值给val_buf,这样传给服务器
log_console("%s\n", val_buf); // 打印语句判断是否写入成功
}
else
{
// 消息队列
key_t key = ftok("/a.c", 'B');
if (key < 0)
{
perror("ftok err");
return -1;
}
// 创建消息队列
int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0777);
if (msgid < 0)
{
if (errno == EEXIST)
{
msgid = msgget(key, 0777);
}
else
{
perror("msgget err");
return -1;
}
}
// 添加消息
if (strncmp(val_buf, "1 1", 3) == 0)
{
struct msgbuf msg1 = {'B', "1 1"};
msgsnd(msgid, &msg1, sizeof(msg1), 0);
strcpy(val_buf, "打开灯");
}
else if (strncmp(val_buf, "1 0", 3) == 0)
{
struct msgbuf msg2 = {'B', "1 0"};
msgsnd(msgid, &msg2, sizeof(msg2), 0);
strcpy(val_buf, "关闭灯");
}
else if (strncmp(val_buf, "0 1", 3) == 0)
{
struct msgbuf msg3 = {'B', "0 1"};
msgsnd(msgid, &msg3, sizeof(msg3), 0);
strcpy(val_buf, "打开蜂鸣器");
}
else if (strncmp(val_buf, "0 0", 3) == 0)
{
struct msgbuf msg4 = {'B', "0 0"};
msgsnd(msgid, &msg4, sizeof(msg4), 0);
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); //输出到服务器
return 0;
}
3.网页代码核心代码
<!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>数据采集系统</title>
</head>
<!--指定文件路径-->
<script src="js/xhr.js"></script>
<script>
function fun(obj){
//console.log(obj);
//调用post接口 obj浏览器发送得数据 info服务器回复得数据
XHR.post('/cgi-bin/web.cgi',obj,function(x,info){
console.log("info:",info);
});
}
function fun1(obj){
//console.log(obj);
//调用post接口 obj浏览器发送得数据 info服务器回复得数据
XHR.post
('/cgi-bin/web.cgi',obj,function(x,info)
{
console.log("info:",info);
var v1 = document.getElementById("tem");
console.log(v1);
var arr = info.split(" ");
var buf = arr[0].split(":");
v1.value = buf[0];
}
);
}
function fun2(obj){
//console.log(obj);
//调用post接口 obj浏览器发送得数据 info服务器回复得数据
XHR.post('/cgi-bin/web.cgi',obj,function(x,info){
console.log("info:",info);
var v2 = document.getElementById("hum");
console.log(v2);
var arr = info.split(" ");
var buf = arr[1].split(":");
v2.value = buf[1];
});
}
</script>
<body>
<div style="color: bisque;background-color: burlywood;">
<!-- input标签类型为文本输入框,name:控件名称、value:默认值、id:标识 -->
温度
<input type="text" name="template" value="" id="tem">
<input type="button" name="tem" value="获取温度" checked="checked" id="get" onclick="fun1(id)">
<br>
湿度
<input type="text" name="humhum" value="" id="hum">
<input type="button" name="hum" value="获取湿度" checked="checked" id="get" onclick="fun2(id)">
<!-- input标签类型为单选按钮,name:控件名称、checked表示默认选中 -->
<!-- label标签将文本和按钮进行绑定,当点击文本时自动聚焦到按钮上
for属性值要和input标签中的id值一致 -->
<br>
<label for="LED 1">LED灯 开:</label>
<input type="radio" name="LED" value="1" checked="checked" id="LED 1" onclick="fun(id)">
<label for="LED 0"> 关:</label>
<input type="radio" name="LED" value="0" checked="checked" id="LED 0" onclick="fun(id)">
<br>
<label for="BEEP 1"> 蜂鸣器 开: </label>
<input type="radio" name="BEEP" value="1" id="BEEP 1" onclick="fun(id)">
<label for="BEEP 0"> 关:</label>
<input type="radio" name="BEEP" value="0" id="BEEP 0" onclick="fun(id)">
</div>
</body>
</html>
总结
用户看到的内容是一个网页,得到效果是点击获取温度,就会得到从机检测的温度,选择LED灯,从机就会打开或者关闭。
那么当用户按下获取温度
按键后,执行了这些操作:
<input type="button" name="tem" value="获取温度" checked="checked" id="get"
onclick="fun1(id)">
在html文件中,这条代码被执行,然后进入到fun1(id)中。
function fun1(obj){
//console.log(obj);
//调用post接口 obj浏览器发送得数据 info服务器回复得数据
XHR.post
('/cgi-bin/web.cgi',obj,function(x,info)
{
console.log("info:",info);
var v1 = document.getElementById("tem");
console.log(v1);
var arr = info.split(" ");
var buf = arr[0].split(":");
v1.value = buf[0];
}
);
在这里面,调用了XHR函数,可以看到第一个为URL,传递参数为dataobjid(也就是get),然后回调函数,其中info为服务器回复数据
XHR = function()
{
this.post = function(url, data, callback)
{}
}
于是当给了post接口,给了参数get,就是服务器写入了get参数
//其中stdin已经重定向了,为服务器写入
ret = fread(request_content + len, 1, content_length - len, stdin);
ret = parse_and_process(request_content);//写入的值作为参数传入到这个函数内
上面就可以得到request_content
的现在已经写入了get这个参数
int parse_and_process(char *input)
{
char val_buf[2048] = {0};
strcpy(val_buf, input);
// 这里可以根据接收的数据请求进行处理
// 判断input的值
if (strncmp(val_buf, "get", 3) == 0)
{
}
}
然后传入的值get就能作为判断执行操作
// 向标准输出写内容(标准输出服务器已做重定向)
fputs(reply_buf, stdout);
操作完毕就向服务器输出拿到的结果。
XHR.post
('/cgi-bin/web.cgi',obj,function(x,info)
{
console.log("info:",info);
var v1 = document.getElementById("tem");
console.log(v1);
var arr = info.split(" ");
var buf = arr[0].split(":");
v1.value = buf[0];
}
);
info为服务器回复的内容,现在就已经收到了具体的内容,然后用变量进行接收,接受前也进行了一下数据的处理
温度
<input type="text" name="template" value="" id="tem">
<input type="button" name="tem" value="获取温度" checked="checked" id="get" onclick="fun1(id)">
<br>
对应上面的 document.getElementById("tem");
找到了value就赋值了,id为tem,所以这个时候上代码中的value=“” 变为了value=“buf[0]”
然后我们才在网页上看到了显示的值。
这其中经历了,网页给服务器传值,服务器给CGI传值,CGI拿用户程序的值,用户查询一直在拿modbus设备的值。