ZYNQ系列(八) ZYNQ下使用BOA
第一章 Petalinux创建工程
第二章 Petalinux-config配置说明
第三章 QSPI自启动工程
第四章 将文件打包进文件系统
第五章 将文件打包进文件系统
第六章 ZYNQ下使用CMAKE创建APP工程
第七章 ZYNQ下使用CMAKE创建QT工程
第八章 ZYNQ下使用BOA
文章目录
前言
服务器软件(BOA)始终在HTTP 端口守候客户的连接请求,当客户向服务器发起一个连接请求后,客户和服务器之间经过“三步握手”建立起连接。在接收到客户端的HTTP 请求消息后,服务器对消息进行解析,包括:读取请求URL、映射到对应的物理文件、区分客户端请求的资源是静态页面还是CGI 应用程序等。如果客户请求的是静态文件,那么服务器读取相应的磁盘文件,并将其作为HTTP响应消息中的实体返回给客户端,如果客户端请求的是CGI 应用程序,那么服务器将创建相应的CGI 应用程序进程,并将各种所需信息(客户端请求信息、服务器端相关信息等)按CGI 规范传递给CGI 应用程序进程,此后由CGI 应用程序接管控制。
CGI应用程序读取从Web 服务器传递来的各种信息,并对客户端的请求进行解释和处理,如使用SQL 语句来检索或者更新数据库,或者将从客户端获得的数据,按与被监控对象所定义的通信协议重新组帧,从UART 口发送到被监控对象。最后CGI 应用程序会将处理结果按照CGI 规范返回给Web 服务器,Web 服务器会对CGI 应用程序的处理结果进行解析,并在此基础上生成HTTP 响应信息返回给客户端。
嵌入式web服务器所要实现的功能是让客户端使用浏览器向服务器发送 HTTP请求,服务器响应客户端的请求后,并引导到指定的脚本程序,对命令进行解析,将信息交给后台——CGI去处理。CGI解析信息后,向远程设备发出控制信息。设备响应后,返回给CGI控制信息,CGI再将信息解析成变量输出到Web Server上,最后客户端得到WebServer发回的页面消息(HTML),就能得到现场设备的运行状态,实现对现场设备的远程监控。
服务器中主要包括Boa和CGI两部分,其中Boa管理着返回客户端的WEB页面,而CGI控制着客户端和服务器端的信息交换,所以我们可以通过编写相应的CGI程序来实现所需要的功能。Boa服务器的实现主要分为两步,boa服务器的移植和CGI程序的设计。CGI的程序编写包括两个部分:HTML代码和C代码;CGI程序与Boa Web服务器之间通过环境变量、命令行参数和标准输入等方式进行通信。
一、编译BOA
-
下载BOA源码:从www.boa.org下载boa-0.94.13.tar.gz源码
-
将源码放入虚拟机并解压:
$ tar zxvf boa-0.94.13.tar.gz
-
生成配置文件:
方式一:没用,不知道能不能行$ ./configure --host=arm-linux-gnueabihf --enable-static CC= arm-linux-gnueabihf-gcc AR= arm-linux-gnueabihf-ar LD= arm-linux-gnueabihf-ld RANLIB= arm-linux-gnueabihf-ranlib export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf --prefix=/home/workzc/alinx/3rdparty/libboa/
方式二:
修改Makefile文件中的交叉编译工具选项CC = arm-linux-gnueabihf-gcc
CPP = arm-linux-gnueabihf-gcc -E
LDFLAGS = -static -
修改boa.c文件
将boa.c文件中的以下内容注释
if ( setuid ( 0 ) != - 1 ) {
DIE ( “icky Linux kernel bug!” );
}
注释掉下面这段程序:
if (passwdbuf == NULL) {
DIE(”getpwuid”);
}
if(initgroups(passwdbuf->pw_name, passwdbuf->pw_gid) == -1) {
DIE(”initgroups”);
} -
修改compat.h文件
#define TIMEZONE_OFFSET(foo) foo##->tm_gmtoff
修改成
#define TIMEZONE_OFFSET(foo) (foo)->tm_gmtoff -
修改config.c文件
将if(!server_name){…}内容注释,位于266-286行 -
修改log.c文件
注释掉以下内容
if (dup2(error_log, STDERR_FILENO) == -1) {
DIE(“unable to dup2 the error log”);
}
不注释会报错:unable to dup2 the error log:bad file descriptor -
编译
$ make && make install
二、 BOA服务器配置
#1. 监听的端口
Port 8080
#2. 服务器运行的用户和组
User 0
Group 0
#3. 关闭错误日志文件
#ErrorLog /var/log/boa/error_log
#4. 关闭访问日志文件
#AccessLog /var/log/boa/access_log
#5. 设置服务器名字
ServerName www.config.rx
#6. 指明CGI脚本的虚拟路径对应的实际路径
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
三、 BOA服务器文件系统修改
- 拷贝boa
将boa拷贝到开发板根文件系统的/bin下 - 拷贝mime.types
从linux桌面系统的etc目录拷贝mime.types文件到开发板系统的etc目录 - 开发板增加如下目录:
(1)创建web服务器HTML文档的主目录/var/www
mkdir /var/www
(2)创建CGI脚本所在目录/var/www/cgi-bin/
mkdir /var/www/cgi-bin
(3)创建目录/etc/boa并且把boa.conf拷贝到这个目录下
mkdir /etc/boa
四、 编译minixml
-
将源码放入虚拟机并解压:
$ tar zxvf mxml-3.2.tar.gz
-
生成配置文件:
./configure --host=arm-linux-gnueabihf --prefix=/home/workzc/alinx/3rdparty/libxml/
-
编译
$ make && make install
五、编译CGIC
-
将源码放入虚拟机并解压:
$ unzip cgic-master.zip
-
修改Makefile文件
CC=arm-linux-gnueabihf-gcc
AR=arm-linux-gnueabihf-ar
RANLIB=arm-linux-gnueabihf-ranlib
CFLAGS=-g -Wall -static
cgictest.cgi: cgictest.o libcgic.a
$(CC) $(CFLAGS) cgictest.o -o cgictest.cgi ${LIBS}
capture: capture.o libcgic.a
$(CC) $(CFLAGS) capture.o -o capture ${LIBS}
clean:
rm -f *.o libcgic.a *.cgi capture cgicunittest
-
编译
$ make
编译得到的文件
libcgic.a:CGIC库
capture:调试辅助程序
cgictest.cgi:测试程序
-
安装CGIC
$ make install
CGIC安装路径为:
libcgic.a 安装在/usr/local/lib cgic.h 安装在/usr/local/include
CGIC库安装后就可以使用CGIC编程了或者直接取出 libcgic.a, cgic.h,直接编程。
-
CGIC文件的移植(CGIC自身的DEMO)
将capture和cgictest.cgi拷贝到开发板的/var/www/cgi-bin目录 -
运行cgi程序
在客户端浏览器运行http://192.168.x.xxx/cgi-bin/cgictest.cgi
如果正常显示网页内容,则BOA与CGIC可以正常工作
六、 BOA CGIC使用DEMO
- HTML网页制作
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Test Upload</title>
<meta name="author" content="scorpio">
<!-- Date: 2016-07-30 -->
</head>
<body>
<form action="cgi-bin/upload.cgi" method="post" enctype="multipart/form-data" target="_blank">
<input type="file" name="file" value="" />
<input type="submit" name="submit" value="OK">
</form>
</body>
</html>
- cgi程序编程
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "cgic.h"
#define BufferLen 1024
int cgiMain(void)
{
cgiFilePtr file;
int targetFile;
mode_t mode;
char name[128];
char fileNameOnServer[64];
char contentType[1024];
char buffer[BufferLen];
char *tmpStr=NULL;
int size;
int got,t;
cgiHeaderContentType("text/html");
//取得html页面中file元素的值,应该是文件在客户机上的路径名
if (cgiFormFileName("file", name, sizeof(name)) !=cgiFormSuccess)
{
fprintf(stderr,"could not retrieve filename/n");
goto FAIL;
}
cgiFormFileSize("file", &size);
//取得文件类型,不过本例中并未使用
cgiFormFileContentType("file", contentType, sizeof(contentType));
//目前文件存在于系统临时文件夹中,通常为/tmp,通过该命令打开临时文件。临时文件的名字与用户文件的名字不同,所以不能通过路径/tmp/userfilename的方式获得文件
if (cgiFormFileOpen("file", &file) != cgiFormSuccess)
{
fprintf(stderr,"could not open the file/n");
goto FAIL;
}
t=-1;
//从路径名解析出用户文件名
while(1)
{
tmpStr=strstr(name+t+1,"//");
if(NULL==tmpStr)
tmpStr=strstr(name+t+1,"/");//if "//" is not path separator, try "/"
if(NULL!=tmpStr)
t=(int)(tmpStr-name);
else
break;
}
strcpy(fileNameOnServer,name+t+1);
mode=S_IRWXU|S_IRGRP|S_IROTH;
//在当前目录下建立新的文件,第一个参数实际上是路径名,此处的含义是在cgi程序所在的目录(当前目录))建立新文件
targetFile=open(fileNameOnServer,O_RDWR|O_CREAT|O_TRUNC|O_APPEND,mode);
if(targetFile<0)
{
fprintf(stderr,"could not create the new file,%s/n",fileNameOnServer);
goto FAIL;
}
//从系统临时文件中读出文件内容,并放到刚创建的目标文件中
while (cgiFormFileRead(file, buffer, BufferLen, &got) ==cgiFormSuccess)
{
if(got>0)
write(targetFile,buffer,got);
}
cgiFormFileClose(file);
close(targetFile);
goto END;
FAIL:
fprintf(stderr,"Failed to upload");
return 1;
END:
printf("File %s has been uploaded",fileNameOnServer);
return 0;
}
将CGIC库的cgic.c、cgic.h文件拷贝到当前目录
编译程序:
arm-linux-gnueabihf-gcc -o upload.cgi upload.c cgic.c -static
arm-linux-gnueabihf-strip upload.cgi
将编译后的upload.cgi拷贝到开发板的/var/www/cgi-bin目录
将upload.html文件考内到开发板/var/www目录
在客户端浏览器访问:http://192.168.6.210/upload.html
选择要上传的文件,上传
查看开发板/var/www/cgi-bin目录,上传的文件已经存放在/var/www/cgi-bin目录
七、 需要放入开发板的文件列表
文件名称 | 来源 | 开发板路径 |
---|---|---|
boa | boa编译生成 | /bin |
mime.types | 虚拟机/etc | /etc |
boa.conf | 自己编写 | /etc/boa |
upload.html | 自己编写 | /var/www |
upload.cgi | 自己编写 | /var/www/cgi-bin |
八、CGIC移植过程中错误的解决
-
html网页可以运行,CGI程序运行报错
Boa服务器报错:cgi_header: unable to find LFLF
客户端浏览器报错:502 Bad Gateway The CGI was not CGI/1.1 compliant.
解决方法:静态编译cgi程序
arm-linux-gnueabihf-gcc -o hello.cgi hello.c -static
九、 使用minixml保存设备配置
- 编写config.html
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html ">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache,no-store,must-revalidate">
<meta http-equiv="expires" content="0">
<title>设备配置</title>
<style type="text/css" rel="stylesheet">
body {
line-height: 1.6;
color: rgba(0,0,0,.85);
font: 14px Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;
}
blockquote, body, button, dd, div, dl, dt, form, h1, h2, h3, h4, h5, h6, input, li, ol, p, pre, td, textarea, th, ul {
padding: 0;
}
.title{
margin: 20px 0px 30px 0px;
text-align: center;
font-size: 20px;
font-weight: 600;
}
.sub-title{
position: absolute;
top: -15px;
display: inline-block;
left: 20px;
padding: 0px 10px;
background-color: #fff;
font-size: 22px;
font-weight: 300;
z-index: 2;
}
.form-container{
position: relative;
}
.form{
position: absolute;
width: 1000px;
left: 50%;
margin-left: -500px;
padding: 40px 20px 5px 20px;
border: #eee 1px solid;
border-radius: 10px;
box-sizing: border-box;
}
@media (min-width: 768px) {
.form{width: 600px; margin-left: -300px;}
}
@media (min-width: 992px) {
.form{width: 800px; margin-left: -400px;}
}
@media (min-width: 1200px) {
.form{width: 1000px; margin-left: -500px;}
}
@media (max-width: 768px) {
.form{width: 100%; margin-left: 0px; left: 0;}
}
.form .form-item{
margin-bottom: 15px;
clear: both;
}
.form .form-item .form-label{
position: relative;
display:block;
float:left;
width: 160px;
padding: 8px 15px;
height: 38px;
line-height: 20px;
border-width: 1px;
border-style: solid;
border-color: #eee;
border-radius: 2px 0 0 2px;
text-align: left;
background-color: #FBFBFB;
overflow: hidden;
box-sizing: border-box;
}
.form .form-item .form-label[required]:before {
content: '*';
position: absolute;
top: 11px;
left: 7px;
color: red;
}
.form .form-item .form-control{
position: relative;
margin-left: 160px;
left: -1px;
}
.form-control input, select{
width: 100%;
height: 38px;
line-height: 1.3;
line-height: 38px\9;
border-width: 1px;
border-style: solid;
border-color: #eee;
outline: 0;
background-color: #fff;
border-radius: 2px;
box-sizing: border-box;
}
input {
padding-left: 10px;
}
button {
box-sizing: border-box;
outline: none;
border: none;
cursor: pointer;
padding: 9px 20px 10px 20px;
border-radius: 2px;
}
button[type=submit] {
background-color: #000000;
border: #000000 1px solid;
color: #ffffff;
}
button[type=reset] {
background-color: #FBFBFB;
border: #eee 1px solid;
}
</style>
<script language="javascript">
window.onload = function()
{
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
var stamp = new Date().getTime();
xmlhttp.open("GET","boa_book.xml?timestamp=" + stamp,false);
xmlhttp.send();
xmlDoc=xmlhttp.responseXML;
var x=xmlDoc.getElementsByTagName("item");
for (i=0;i<x.length;i++)
{
var name = x[i].getElementsByTagName("name")[0].childNodes[0].nodeValue;
var conval = x[i].getElementsByTagName("value")[0].childNodes[0].nodeValue;
if(name == "server_ip")
{
document.getElementById("server_ip").value = conval;
}
else if(name == "server_poot")
{
document.getElementById("server_poot").value = conval;
}
else if(name == "self_ip")
{
document.getElementById("self_ip").value = conval;
}
}
}
function confirm()
{
alert("设置成功,重启设备后新的设置才能生效!");
return true;
}
</script>
</head>
<body>
<div class="title">设置产品参数</div>
<div class="form-container">
<div class="form">
<div class="sub-title">
通信相关
</div>
<iframe name ="server" style ="display:none;" src=' '> </iframe>
<form enctype="multipart/form-data" onsubmit="return confirm()" action="cgi-bin/config.cgi" method="post" target ="server">
<div class="form-item">
<label for="" class="form-label" required>
Server IP地址:
</label>
<div class="form-control">
<input onkeyup="value=value.replace(/[^\d{1,}\.\d{1,}|\d{1,}]/g,'')" type="text" id="server_ip" name="server_ip" maxlength="15"/>
</div>
</div>
<div class="form-item">
<label for="" class="form-label" required>
Server 端口:
</label>
<div class="form-control">
<input onkeyup="value=value.replace(/[^\d]|^[0]/g,'')" type="text" id="server_poot" name="server_poot" maxlength="5"/>
</div>
</div>
<div class="form-item">
<label for="" class="form-label" required>
设备IP地址:
</label>
<div class="form-control">
<input onkeyup="value=value.replace(/[^\d{1,}\.\d{1,}|\d{1,}]/g,'')" type="text" id="self_ip" name="self_ip" maxlength="15"/>
</div>
</div>
<div class="form-item">
<div class="form-control">
<button style="margin-right: 10px;" type="reset">取消</button>
<button type="submit">确认</button>
</div>
</div>
</form>
</div>
</div>
</body>
- 编写main.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdlib.h>
#include "cgic.h"
#include "axi_xml.h"
int cgiMain(void)
{
char ipadd[32] = {0};
char *boa_book_file = BOA_BOOK_FILE;
printf("content-type:text/html;charset=utf-8\n\n");
if(cgiFormString(SERVER_IP_DEFAULT_NAME,boa_item_init[SERVER_IP].value, VALUE_STR_LEN) != cgiFormSuccess)
{
printf("cgiFormString function ipadd failed\n\n");
exit(-1);
}
printf("SERVER IP:%s\n\n",boa_item_init[SERVER_IP].value);
if(cgiFormString(SERVER_POOT_DEFAULT_NAME, boa_item_init[SERVER_POOT].value, VALUE_STR_LEN) != cgiFormSuccess)
{
printf("cgiFormString function SERVER_POOT_DEFAULT_NAME failed\n\n");
exit(-1);
}
printf("SERVER POOT:%s\n\n",boa_item_init[SERVER_POOT].value);
if(cgiFormString(SELF_IP_DEFAULT_NAME, (char *)&boa_item_init[SELF_IP].value, VALUE_STR_LEN) != cgiFormSuccess)
{
printf("cgiFormString function SELF_IP_DEFAULT_NAME failed\n\n");
exit(-1);
}
printf("SELF IP:%s\n\n",boa_item_init[SELF_IP].value);
change_boa_node(BOA_BOOK_FILE);
}
- 编写xml_config.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "mxml.h"
#include "axi_xml.h"
BOA_XML boa_item_init[] = {
{SERVER_IP_DEFAULT_NAME, SERVER_IP_DEFAULT_VALUE},
{SERVER_POOT_DEFAULT_NAME, SERVER_POOT_DEFAULT_VALUE},
{SELF_IP_DEFAULT_NAME, SELF_IP_DEFAULT_VALUE},
};
//回调函数,修改输出的格式
const char *whitespace_cb(mxml_node_t *node,int w)
{
char *node_name;
node_name = mxmlGetElement(node);
//printf(" curNode:%s; w: %d \n", node_name, w);
if(!strcmp(node_name, "configinfo"))
{
if( w == MXML_WS_BEFORE_OPEN || w == MXML_WS_BEFORE_CLOSE )
{
return("\n");
}
else
{
return NULL;
}
}
else if(!strcmp(node_name, "item"))
{
if( w == MXML_WS_BEFORE_OPEN || w == MXML_WS_BEFORE_CLOSE )
{
return("\n");
}
else if( w == MXML_WS_AFTER_OPEN )
{
return("\n\t");
}
else
{
return NULL;
}
}
else if(!strcmp(node_name, "name"))
{
if( w == MXML_WS_AFTER_CLOSE )
{
return("\n\t");
}
else
{
return NULL;
}
}
else
return NULL;
#if 0
//只在元素开始时换行
if (w == MXML_WS_BEFORE_OPEN //0 /* Callback for before open tag */
|| w == MXML_WS_AFTER_OPEN //1 /* Callback for after open tag */
|| w == MXML_WS_BEFORE_CLOSE //2 /* Callback for before close tag */
|| w == MXML_WS_AFTER_CLOSE //3 /* Callback for after close tag */
)
return ("\n");
else
return NULL;
#endif
}
int change_boa_node(const char *boa_book_file)
{
FILE *fp;
char cp_cmd[100] = {0};
mxml_node_t *xml = NULL; /* <?xml ... ?> */
mxml_node_t *item = NULL; /* <node> */
mxml_node_t *tree = NULL; /* <tree> */
mxml_node_t *data = NULL; /* <data> */
char msg_num;
assert(boa_book_file);
/* 创建一个新的文档的版本号 */
xml = mxmlNewXML("1.0");
tree = mxmlNewElement(xml, "configinfo");
for(msg_num = 0; msg_num < BOA_MSG_NUM;msg_num++)
{
item = mxmlNewElement(tree, "item");
data = mxmlNewElement(item, "name");
mxmlNewText(data, 0, boa_item_init[msg_num].name); //num节点创建文本内容
data = mxmlNewElement(item, "value");
mxmlNewText(data, 0, boa_item_init[msg_num].value);
}
fp = fopen(boa_book_file, "w");
mxmlSaveFile(xml, fp, whitespace_cb);
fclose(fp);
mxmlDelete(xml);
sprintf(cp_cmd,"cp -rf %s %s",BOA_BOOK_FILE,BOA_BOOK_FILE_DEFAULT);
system(cp_cmd);
return 0;
}
- 编写xml_config.h
#ifndef _AXI_XML_H_
#define _AXI_XML_H_
#define BOA_XML_NUM 2
#define NAME_STR_LEN 32
#define VALUE_STR_LEN 32
#define SERVER_IP_DEFAULT_NAME "server_ip"
#define SERVER_POOT_DEFAULT_NAME "server_poot"
#define SELF_IP_DEFAULT_NAME "self_ip"
#define SERVER_IP_DEFAULT_VALUE "192.168.1.105"
#define SERVER_POOT_DEFAULT_VALUE "8008"
#define SELF_IP_DEFAULT_VALUE "192.168.1.151"
#define BOA_BOOK_FILE "/xxx/boa/boa_book.xml"
#define BOA_BOOK_FILE_DEFAULT "/usr/bin/boa/www/boa_book.xml"
typedef struct
{
char name[NAME_STR_LEN];
char value[VALUE_STR_LEN];
}BOA_XML;
typedef enum
{
SERVER_IP,
SERVER_POOT,
SELF_IP,
BOA_MSG_NUM
}BOA_MSG;
int change_boa_node(const char *boa_book_file);
extern BOA_XML boa_item_init[];
#endif
- 加入libcgic.a libmxml.a cgic.h mxml.h进行编译。生成config.cgi
- 编写boa.conf文件
Port 8080
User 0
Group 0
ServerName www.config.rx
DocumentRoot /var/www
UserDir public_htm
DirectoryIndex index.html
DirectoryMaker /usr/lib/boa/boa_indexer
KeepAliveMax 1000
KeepAliveTimeout 10
MimeTypes /etc/mime.types
DefaultType text/plain
CGIPath /bin:/usr/bin:/usr/local/bin
Alias /doc /usr/doc
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
根据boa.conf的提示将文件放入开发板
cp files/boa /bin
chmod 777 /bin/boa
cp files/mime.types /etc
mkdir /var/www
cp files//config.html /var/www
cp files/boa_book.xml /var/www
mkdir /var/www/cgi-bin
cp boa_book/config.cgi /var/www/cgi-bin
chmod 777 /var/www/cgi-bin/config.cgi
mkdir /etc/boa
cp boa_book/boa.conf /etc/boa
/bin/boa &
客户端运行 http://192.168.1.100:8080/config.html,这里的IP地址就是开发板的IP地址。如果正常显示网页内容,在输入正确的数据后,点击“确定”后,查看boa_book.xml,其中存储的内容应该与用户输入的一致,则BOA与CGIC工作正常。
工程文件已经打包:boa_cgic