一、项目目标和框图
项目目标:实现通过网页控制信息采集和通过网页控制灯泡和蜂鸣器的亮灭
二、项目分析
1.服务器源码分析
- 初始化服务器
- 循环等待连接,连接后创建线程,调用线程函数msg_request,在函数中调用handler_msg函数分析请求
- 在handler_msg函数中,先查看请求协议内容,其次获取请求方法、URL、参数,判断请求方法,对need_handler赋值,确定请求资源路径,如果请求地址没有携带任何资源,则默认返回index.html文件,如果资源不存在,返回404,如果需要处理(get带参数、post)调用handle_request函数,如果不需要(get请求不带参数且资源存在),调用echo_www函数,直接返回资源
- handle_request函数主要获取post数据,调用parse_and_process函数处理正文内容
2. 结合Modbus部分整体流程分析
三、核心功能代码
1.采集传感器数据
modbus采集程序和webserver共同建立共享内存,modbus读取传感器得到的数据,将数据写入共享内存中;webserver将数据从共享内存中读出;
//modbus采集传感器
void *handler_thread(void *arg)
{
key_t key;
int shmid;
struct msp *p;
//创建key值
key = ftok("1.txt", 'x');
if (key < 0)
{
perror("ftok err");
return NULL;
}
printf("key: %#x\n", key);
//创建或打开共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //没有则创建共享内存,已有则返回-1
if (shmid <= 0)
{
if (errno == EEXIST) //如果已存在则直接打开共享内存
shmid = shmget(key, 128, 0666); //直接打开共享内存,返回共享内存id
else
{
perror("shmget err");
return NULL;
}
}
printf("shmid: %d\n", shmid);
//映射共享内存到用户空间
p = (struct msp *)shmat(shmid, NULL, 0);
if (p == (struct msp *)-1)
{
perror("shmat err");
return NULL;
}
//操作共享内存
while (1)
{
printf("handler_thread\n");
// uint16_t dest[10] = {};
PACK *msg = arg;
modbus_read_registers(msg->ctx, 0, 4, p->dest);
printf("%d %d %d %d\n", p->dest[0], p->dest[1], p->dest[2], p->dest[3]);
//让从线程不退出,进程状态l也就是多线程
sleep(2);
}
return NULL;
}
//webserver服务器
static int handler_get(int sock, const char *input)
{
key_t key;
int shmid;
struct msp *p;
//创建key值
key = ftok("1.txt", 'x');
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key: %#x\n", key);
//创建或打开共享内存
shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //没有则创建共享内存,已有则返回-1
if (shmid <= 0)
{
if (errno == EEXIST) //如果已存在则直接打开共享内存
shmid = shmget(key, 128, 0666); //直接打开共享内存,返回共享内存id
else
{
perror("shmget err");
return -1;
}
}
printf("shmid: %d\n", shmid);
//映射共享内存到用户空间
p = (struct msp *)shmat(shmid, NULL, 0);
if (p == (struct msp *)-1)
{
perror("shmat err");
return -1;
}
sscanf(input, "%d X%dY%dZ%d", p->dest[0], p->dest[1], p->dest[2], p->dest[3]);
printf("g:%d X:%dY:%dZ:%d\n", p->dest[0], p->dest[1], p->dest[2], p->dest[3]);
char reply_buf[HTML_SIZE] = {0};
printf("handler_get\n");
printf("%d %d%d%d\n", p->dest[0], p->dest[1], p->dest[2], p->dest[3]);
sprintf(reply_buf, "%d %d %d %d", p->dest[0], p->dest[1], p->dest[2], p->dest[3]);
printf("resp = %s\n", reply_buf);
send(sock, reply_buf, strlen(reply_buf), 0);
return 0;
}
2.控制灯泡和蜂鸣器
网页发出控制命令给webserver服务器控制灯和蜂鸣器的信息,webserver和modbus采集器用相同的条件建立两个程序间的消息队列,建立通信,将控制灯泡和蜂鸣器的信息传递给modbus采集器,modbus采集器用相关信息控制灯泡和蜂鸣器的开关
//modbus采集器
void *handler_thread2(void *arg)
{
key_t key;
int msgid;
key = ftok(".", 9);
if (key < 0)
{
perror("ftok err");
return NULL;
}
printf("key:%#x\n", key);
//打开消息队列
msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
if (msgid <= 0)
{
if (errno == EEXIST)
msgid = msgget(key, 0666);
else
{
perror("msgget err");
return NULL;
}
}
printf("msgid: %d\n", msgid);
//读取消息
struct msgbuf m;
while(1)
{
msgrcv(msgid, &m, sizeof(m) - sizeof(long), 10, 0); //20:表示读取消息的类型为20,0:代表阻塞,读取完消息才返回
printf("%d %d\n", m.num,m.num2);
PACK *msg = arg;
modbus_write_bit(msg->ctx2, m.num, m.num2);
// modbus_write_bit(msg->ctx2, 1, dest[1]);
}
// //让从线程不退出,进程状态l也就是多线程
return NULL;
}
//webserver服务器
static int handler_set(int sock, const char *input)
{
key_t key;
int msgid;
key = ftok(".", 9);
if (key < 0)
{
perror("ftok err");
return -1;
}
printf("key:%#x\n", key);
//打开消息队列
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);
int number1,number2;
//input必须是"data1=1data2=6"类似的格式,注意前端过来的字符串会有双引号
sscanf(input, "set=%dset2=%d", &number1,&number2);
printf("num1 = %d,num2=%d\n", number1,number2);
char reply_buf[HTML_SIZE] = {0};
struct msgbuf mng;
mng.type = 10;
mng.num = number1;
mng.num2 =number2;
mng.ch = 'a';
msgsnd(msgid, &mng, sizeof(mng) - sizeof(long), 0); //0:阻塞,发完消息才返回
printf("num = %d num=%d\n", number1 ,number2);
sprintf(reply_buf, "set=%dset2=%d", number1,number2);
printf("resp = %s\n", reply_buf);
send(sock, reply_buf, strlen(reply_buf), 0);
return 0;
}
3.http网页显示和控制
将虚拟机的IP地址在网页中打开,然后查询传感器得到的数据和控制灯泡和蜂鸣器
<!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>
<script>
function get_info()
{
var v = document.getElementsByName("username");
var xhr = new XMLHttpRequest();//创建对象
var url = "";
xhr.open("post", url, true);
xhr.onreadystatechange = function ()
{
if (xhr.readyState === 4 && xhr.status === 200)
{
console.log(xhr.responseText);
var str1 = xhr.responseText.split("", 2);
console.log(str1[0]);
console.log(str1[1]);
v[0].value = str1[0];
v[1].value = str1[1];
}
};
xhr.send("g:%#x X:%#xY:%#xZ:%#x");
}
function sendStatus(addr,status)
{
var xhr = new XMLHttpRequest();
var url = "";
xhr.open("post", url, true);
data = "set=" + addr + "set2=" + status;
console.log("req=" + data);
if (xhr.readyState === 4 && xhr.status === 200)
{
var str1 = xhr.responseTest;
console.log("resp=" + response);
};
xhr.send(data);
}
function set_status(obj)
{
if (obj == 'ledon')
{
sendStatus(0, 1);
}
else if (obj == 'ledoff')
{
sendStatus(0, 0);
}
else if (obj == 'buzzeron')
{
sendStatus(1, 1);
}
else if (obj == 'buzzeroff')
{
sendStatus(1, 0);
}
}
function get_info()
{
var v=document.getElementsByName("username");
// v[0].value="hello";
var xhr = new XMLHttpRequest;//创建对象
var url="";
xhr.open("post",url,ture);
xhr.onreadystatechange=function()
{
if(xhr.readyState==4&&xhr.status==200)
{
console.log(xhr.responseText);
v[0].value=xhr.responseText;
}
};
xhr.send("modbus_get");
}
</script>
</head>
<body>
<!--h1-h6是标题标签-->
<h1>what 阿尔 donging 呢?</h1>
yes,我吃饭嘞
<br>
<!--br表示换行-->
NO,我 dont eat
<div style="color:crimson;background:deepskyblue;">
<p>
<!--p段落标签-->
HTML(英文Hyper Text Markup Language的缩写)中文译为“超文本标记语言”。是用来描述网页的一种语言。
所谓超文本,因为它可以加入图片、声音、动画、多媒体等内容,不仅如此,它还可以从一个文件跳转到另一个文件,与世界各地主机的文件连接。
HTML 不是一种编程语言,而是一种标记语言 (markup language)
Web 浏览器的作用是读取 HTML 文档,并以网页的形式显示出它们。浏览器不会显示 HTML 标签,而是使用标签来解释页面的内容
</p>
</div>
用户名:
<!--input 表单标签,
type类型为特殊他表示文本输入框
value为默认值-->
<input type="test" name="username" value="lisi">
<input type="button" name="flash" onclick="get_info()">
<br>
<!-- type类型为radio表示单选框,
表示同样类型的单选框name 必须相同 ,
-->
<h1>设备控制</h1>
<br>
LED灯
on:<input type="radio" name="deng" id="ledon" checked="checked" onclick="set_status(id)">
off:<input type="radio" name="deng" id="ledoff" onclick="set_status(id)">
<br>
蜂鸣器
on:<input type="radio" name="qi" id="buzzeron" checked="checked" onclick="set_status(id)">
off:<input type="radio" name="qi" id="buzzeroff" onclick="set_status(id)">
</body>
</html>