Android搭建WEB Server—boa(二)
2017/2/17 14:03:43
上一篇只是对于移植boa的基本讲解,在移植过程中,会出现很多问题。上文已经说明了如何去修改源码和boa.conf文件,而使我们通过编译。目前,我们要做的事情是:执行CGI程序。这个CGI程序有点特殊,它要调用system()函数,来运行一个脚本文件。这就要求它所需要的执行权限必须为root,因为如果不是root用户,调用system()执行脚本是不通过的。即使这个脚本文件的权限为777,仍旧会提示Permission denied。
1 CGI程序
CGI(Common Gateway Interface)公共网关接口。这里不详细介绍CGI,有兴趣的可以搜索一下CGI和fastCGI,资料很多,不怕找不到。推荐一个CGI的资料,很详细,写的很好。http://www.jdon.com/idea/cgi.html
简单说下我对CGI的理解:CGI不能算一段程序,在B/S架构中,它运行在Server端,接收来自WEB服务器的数据,将处理结果返还给WEB服务器。它可以使用任何一种语言开发,在嵌入式领域,C/C++无疑是最快的、最合适的。下面是我的CGI程序。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int main()
{
char msg[100];
memset(msg,0,100);
fgets(msg,sizeof(msg),stdin);
system("sh ./test.sh"); //test.sh仅仅是用来传建test.txt文件的脚本。
puts("<Content-type:text/html>\n");
puts("<html>");
puts("<head>");
puts("<title>test.cgi</title>");
puts("</head>");
puts("<body>");
printf("%s\n",msg);
puts("</body>");
puts("</html>");
return 0;
}
2 HTML主页
上文中需要一个index.html文件,充当主页。本文要比之前高深一点,但实际上,稍微懂点html知识,就能写一个简单的交互页面。主要是需要与CGI程序进行数据的交接。下面是我的index.html。
<Content-type:text/html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
</head>
<body>
<h1 align="center">Login</h1>
<hr><br>
<form action="http://192.168.43.1/cgi-bin/test.cgi" method="POST">
目的IP<input type="text" name="dstip"><br>
目的端口<input type="text" name="dstport"><br>
摄像机IP<input type="text" name="cameraip"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
3 调试
问题1 不能执行shell脚本
首先,进入Android系统shell。切换以root用户登录shell。在shell中启动boa服务进程。用浏览器访问时,可以显示主页index.html,但是cgi程序并不能顺利执行下去。上述中cgi程序调用了system()去执行test.sh脚本,创建test.txt文件。查看etc/boa/cgi-bin/目录可知,并没有生成test.txt。
搜索了网上的一些文章,大多与我的情况相悖。我在boa.conf中配置的是没有问题的,可以查看一下我上篇文章,这里就不加赘述了。最后才明白,出现这样的错误是因为Permission denied。Android系统中,我在shell里切换了root身份登录启动的boa,其实并没有真的以root身份去运行boa这个进程。导致了我不能在cgi程序中执行脚本。请教了前辈,才发现这个问题的症结所在。惭愧……
解决的方法:在init.rc中以root身份启动boa。
问题2 boa开机不能启动
首先来说说init.rc。init.rc是管理Android系统开启启动项的文件,init.rc是必须要在源码中更改的,它被做成ramdisk.img的一部分。更改init.rc源码,要重新用 mmm 命令编译,并重新刷ramdisk.img。详细的介绍请自行百度,不做赘述。我在init.rc文件追加(记住别tm写在第一行,不靠谱!!!)了boa的启动代码。
...... /* 开机启动boa */ service boa /system/bin/boa class main user root ......
编译后,刷入ramdisk.img,修改权限等等。根据前辈建议,把etc/boa更换了路径,改成/data/boa。相应的boa.conf文件中也更改一下配置路径。弄好之后,本以为万事大吉,搞定收工。可惜,bug总是不想我按时吃饭哪。
ps看了一下进程,发现boa压根就没启动。想着是启动出错了,于是更改了一下init.rc,调试调试。
...... service boa /system/bin/logwrapper /system/bin/boa class mian user root ......
这条代码是为了调试boa,他会将在启动过程中出现的log输出到logcat.log当中。
再次重启后,导出logcat.log,发现boa报错。
boa : Could not chdir to “etc/boa”:aborting
boa : boa terminated by exit(1)
原因很简单,上面我将boa的目录转到了data下,特别是boa.conf文件不在etc/boa/下,而是在data/boa/下。这就得改boa源码了。在boa/src/下,修改defines.h。
...... 29 #ifndef SERVER_ROOT 30 #define SERVER_ROOT "/etc/boa" —> "/data/boa" 31 #endif ......
交叉编译后,再次运行。嗯,至少不报前面的错误了。
问题3 boa启动异常
之前解决了boa不能启动,没想到boa启动异常了。具体的表现呢,就是logcat.log中没有boa的错误输出,但是boa的error_log中每隔五秒会写入一次启动的信息,包括进程号,端口等。进程号一直增大,端口号一直不变。ps查看时,却找不到boa的进程。初步判断是boa在init过程中出现了错误。boa处于启动过程,但可能系统认为boa已经退出了,所以又重新启动了boa,所以导致反复重启,却看不到进程。
这时,adb shell后,手动启动boa,ps看启动成功了,但是,error_log中有爆出了新错误。
boa : boa.c:194 - unable to bind: Address already in use
boa : boa.c:194 - unable to bind: Address already in use
boa : boa.c:194 - unable to bind: Address already in use
这玩意每隔5s出来一次,没什么特殊含义,就是端口复用了。可就是这端口复用,困扰了我两天。按照网上的说法,Android却是存在这种机制,端口可能要等待一段时间才能重新bind。使用setsockpot()函数可以实现端口的复用。听起来很简单哈,可惜,查看代码中却是已经使用了这个函数,仍然出现这个报错。
然后,更改了init.rc的内容如下:
...... service boa /system/bin/boa class main user root oneshot //表明此进程仅仅启动一次,即使失败也不会重启 ......
万万没想到,这样一来竟然丝毫没出现问题。boa开机正常启动,cgi程序正常运行,error_log也没有再报错。看到这里,估计有些人已经明白boa错误的原因了。原因就是,boa在init过程中确实启动了,但是出于某种原因,一号进程认为这个boa已经退出了,我要重新启动它,所以造成了不断地重启。这时adb shell来启动boa,就相当于有两个进程都是boa,都在bind同一个端口,造成了端口被占用的报错。
清楚了原因后,只能去查看源码了。在源码中有这么一段:
...... /* background ourself */ if (do_fork) { switch(fork()) { case -1: /* error */ perror("fork"); exit(1); break; case 0: /* child, success */ break; default: /* parent, success */ exit(0); break; } } ......
这段代码的意思呢就是fork()了一个子线程去执行下面的,而父线程直接退出了。虽然我也不知道为什么作者会加入这么一段代码,(时间问题,不深究,以后再说)但我想,这就是造成上面报错的根本原因。在boa的init过程中,boa父线程退出时给一号进程发送了一个信号,它退出的信号。于是一号进程认为boa退出了,所以重新又起了一次boa,如此周而复始。分析之后,将这段代码注释掉即可。同时将oneshot去掉。我可以直接用父进程完成接下来的任务,而不需要fork()子线程。
至此,圆满结束。