Chinaunix首页 | 论坛 | 博客
  • 博客访问: 168145
  • 博文数量: 109
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 147
  • 用 户 组: 普通用户
  • 注册时间: 2015-01-23 16:12
文章分类

全部博文(109)

文章存档

2015年(109)

我的朋友

分类: LINUX

2015-02-13 10:36:28

我把网页挂载到nfs 下面的文件中(需要新建一个文件www ),不过这样很方便!

安装过程

==========================================================

1 )在 下载boa-0.94.13.tar.gz 并解压

# tar -zxvf boa-0.94.13.tar.gz

2 )在src 目录下运行./configure

3 )生成Makefile 文件,修改

CC = arm-linux-gcc

CPP = arm-linux-gcc–E

(1) 将boa.c 文件中以下几行判断去掉即可。

if ( setuid ( 0 ) != - 1 ) { 
    DIE ( "icky Linux kernel bug!" ); 
}

(2)

修改文件compat.h   P120

#define TIMEZONE_OFFSET(foo) foo##->tm_gmtoff
修改成
#define TIMEZONE_OFFSET(foo) (foo)->tm_gmtoff

(3)

把src 文件夹下的config.c 里的if(!server_name){..........} (大概在266 行到286 行之间)注释掉

否则

Error :

./boa

gethostbyname:: Resource temporarily unavailable

 

(4) 修改 src/log.c

注释掉

if (dup2(error_log, STDERR_FILENO) == -1) {

DIE("unable to dup2 the error log");

}

否则会出现错误:

log.c:73 unable to dup2 the error log:bad file descriptor

 

改变板子的属性,为可写:执行一个命令就可以变成可写的 好象是chmod 777

自动启动boa :在/etc/profile 中启动boa 即可

 

4 )make

5 )执行arm-linux-strip boa

去掉调试信息,小很多,50 多k

可以编译出boa 可执行文件,下面是对文件系统的修改

(1 )建立/etc/boa/boa.conf 可以从boa 源码里拷贝boa.conf

(2 )修改boa.conf 文件,以下为转载

**********************************************************

# 监听的端口号,缺省都是80 ,一般无需修改
Port 80
# bind 调用的IP 地址,一般注释掉,表明绑定到INADDR_ANY ,通配于服务器的所有IP 地址
#Listen 192.68.0.5
User 0
Group 0
# 当服务器发生问题时发送报警的email 地址,目前未用,注释掉
#ServerAdmin  
# 错误日志文件。如果没有以/ 开始,则表示从服务器的根路径开始。如果不需要错误日志,则用#/dev/null 。在下面设置时,注意一定要建立/var/log/boa 目录
ErrorLog /mnt/log/boa/error_log
# 访问日志文件。如果没有以/ 开始,则表示从服务器的根路径开始。如果不需要错误日志,则用#/dev/null 或直接注释掉。在下面设置时,注意一定要建立/var/log/boa 目录
#AccessLog /var/log/boa/access_log
# 是否使用本地时间。如果没有注释掉,则使用本地时间。注释掉则使用UTC 时间
#UseLocaltime
# 是否记录CGI 运行信息,如果没有注释掉,则记录,注释掉则不记录
#VerboseCGILogs
# 服务器名字
ServerName  
# 是否启动虚拟主机功能,即设备可以有多个网络接口,每个接口都可以拥有一个虚拟的Web 服
# 务器。一般注释掉,即不需要启动
#VirtualHost
# 非常重要,HTML 文档的主目录。如果没有以/ 开始,则表示从服务器的根路径开始。
DocumentRoot /var/www
# 如果收到一个用户请求的话,在用户主目录后再增加的目录名
UserDir public_html
#HTML 目录索引的文件名,也是没有用户只指明访问目录时返回的文件名
DirectoryIndex index.html
# 当HTML 目录没有索引文件时,用户只指明访问目录时,boa 会调用该程序生成索引文件然后
# 返回给用户,因为该过程比较慢最好不执行,可以注释掉或者给每个HTML 目录加上#DirectoryIndex 指明的文件
#DirectoryMaker /usr/lib/boa/boa_indexer
# 如果DirectoryIndex 不存在,并且DirectoryMaker 被注释,那么就用Boa 自带的索引
# 生成程序来生成目录的索引文件并输出到下面目录,该目录必须是Boa 能读写
# DirectoryCache /var/spool/boa/dircache
# 一个连接所允许的HTTP 持续作用请求最大数目,注释或设为0 都将关闭HTTP 持续作用
KeepAliveMax 1000
#HTTP 持续作用中服务器在两次请求之间等待的时间数,以秒为单位,超时将关闭连接
KeepAliveTimeout 10
# 指明mime.types 文件位置。如果没有以/ 开始,则表示从服务器的根路径开始。可以注释掉
# 避免使用mime.types 文件,此时需要用AddType 在本文件里指明
MimeTypes /etc/mime.types
# 文件扩展名没有或未知的话,使用的缺省MIME 类型
DefaultType text/plain
# 提供CGI 程序的PATH 环境变量值
CGIPath /bin:/usr/bin:/usr/local/bin
# 将文件扩展名和MIME 类型关联起来,和mime.types 文件作用一样。如果用mime.types
# 文件,则注释掉,如果不使用mime.types 文件,则必须使用
#AddType application/x-httpd-cgi cgi
# 指明文档重定向路径
#Redirect /bar  
# 为路径加上别名
Alias /doc /usr/doc
# 非常重要,指明CGI 脚本的虚拟路径对应的实际路径。一般所有的CGI 脚本都要放在实际路径
# 里,用户访问执行时输入站点+ 虚拟路径+CGI 脚本名
ScriptAlias /cgi-bin/ /mnt/www/cgi-bin/

 


   用户可以根据自己需要,对boa.conf 进行修改,但必须要保证其他的辅助文件和设置必须和boa.conf 里的配置相符,不然Boa 就不能正常工作。 在上面的例子中,我们还需要创建日志文件所在目录/mnt/log/boa ,创建HTML 文档的主目录/mnt/www ,将mime.types 文件拷贝 到/etc 目录,创建CGI 脚本所在目录/var/mnt/cgi-bin/ 。mime.types 文件用来指明不同文件扩展名对应的MIME 类型,一般 可以直接从Linux 主机上拷贝一个,大部分也都是在主机的/etc 目录下。

**********************************************************

我做的修改

DocumentRoot /mnt/www        www 目录,需手动建立

ScriptAlias /cgi-bin/ /mnt/www/cgi-bin/      www/cgi-bin 目录,需手动建立

Group 0

(3 )etc 目录里还要有passwd group mime.types 等文件

www 目录放index.html 文件

我把passwd group mime.types 放在/etc 下面,boa.conf 放在/etc/boa 文件夹下, 可执行文件boa 放在/bin 下面,文件系统新添加了mnt ,把www 和cgi-bin 放进去,编译文件系统CRAMFS ,烧录…

(注:这样烧录的话,文件都是只读的,可以把1:   /bin/mkdir /tmp/fs

2:/bin/mount -t yaffs /dev/mtdblock/3 /tmp/fs;

这样就mount 上一个可读可写的文件,掉电还在;问题是:我mount 出错,有bad block )

(4 )在板子上运行boa

(5 )pc 机用IE 访问 

 

(2 )

编辑helloworld.c 程序测试cgi 的运行

#arm-linux-gcc -o helloworld.cgi helloworld.c
#cp helloworld.cgi 到开发板的/var/www/cgi-bin 目录下

在pc 机的浏览器地址栏输入 ,可以看到相关页面,CGI 脚本测试通过。

 

 

10. 从CGIC 的主站点 下载源码,将其解压并进入源码目录

    # tar -zxvf cgic205.tar.gz
    # cd cgic205

11. 修改Makefile 文件

a. 找到CC=gcc ,将其改成CC=arm-linux-gcc ,

b. 找到AR=ar ,将其改成AR=arm-linux-ar ,

c. 找到RANLIB=ranlib ,将其改成RANLIB=arm-linux-ranlib 。

e. 找到gcc cgictest.o -o cgictest.cgi ${LIBS} ,

  将其改成$(CC) $(CFLAGS) cgictest.o -o cgictest.cgi ${LIBS} ,

f. 找到gcc capture.o -o capture ${LIBS} ,

  将其改成$(CC) $(CFLAGS) capture.o -o capture ${LIBS} ,

保存退出。
12. 然后运行make 进行编译,得到的CGIC 库libcgic.a ,我们通过调试辅助程序capture 和测试程序cgictest.cgi ,来验证生成CGIC 库的正确性。
13. 将capture 和cgictest.cgi 拷贝到主机的/var/www/cgi-bin 目录下。
在工作站的浏览器地址栏输入 ,可以看到页面,CGIC 库和测试脚本都移植成功。  

 

将.cgi 文件拷贝至目标板上后,必须改变其权限

chmod 755 *

否则,上位机浏览时会提示

502 Bad Gateway
The CGI was not CGI / 1 . 1 compliant .

(2 )

不能上传的话,把GET 改成POST

把用户权限由 nobody 改成 root

 

用 C/C++ 写 CGI 程序 


其实用 C/C++ 写 CGI 程序非常简单,主要是要清楚什么是 CGI 。 

CGI 全称 Common Gateway Interface (共同编程接口),是一种编程接口,不论什么语言,只要按照该接口的标准编写出来的程序,即可叫做 CGI 程序。 CGI 程序的输入 / 输出是使用编程语言的标准输入 / 标准输出,所以用 C/C++ 来写 CGI 程序就好象写普通程序一样,不过还有几样东西要注意的。 

1 、 CGI 程序的通信方式 

当 有数据从浏览器传到 Web 服务器后,该服务器会根据传送的类型(基本有二类: GET/POST ),将这些接收到的数据传入 QUERY_STRING或 变量中, CGI 程序可以通过标准输入,在程序中接收这些数据。当要向浏览器发送信息时,只要向 Web 服务器发送特定的文件头信息,即可通过标准输出将信息发往 Web 服务器, Web 服务器处理完这些由 CGI 程序发来的信息后就会将这些信息发送给浏览器。这样就是 CGI 程序的通信方式了。 

2 、接收数据 

用 GET 方式接收到的数据保存在 Web 服务器的 QUERY_STRING 变量里,而通过 POST 方式接收到的数据是保存在 这个 Web 服务器变量里。它们的唯一区别就是:以 GET 方式接收的数据是有长度限制,而用 POST 方式接收的数据是没有长度限制的。并且,以 GET 方式发送数据,可以通过 URL 的形式来发送,但 POST 方式发送的数据必须要通过 Form 才到发送。 

  

好,现在让我们用 C 语言写一个神圣的 CGI 程序 -- Hello,World!

vi hello.c  # 编辑源文件 

//  Begin

#include
main() {
    printf("Content-type:text/html\n\n");
    printf("Hello,World!");
}

//  End

gcc -o hello hello.c  # 编译 

将该程序放在 Web 服务器的 cgi-bin 目录下,然后通过以下方式访问: 



这将在浏览器里打印出 Hello,World!

这 就算得上是一个 CGI 程序了,是不是很简单? ^_^ 第一句 printf() 是打印头信息,让 Web 浏览器知道以下打印的数据是什么类型的数据,本例子中指定了 text/html 类型,即 html 文档,所以下面的那句 printf() 打印的内容就会像我们写网页内容一样在浏览器上显示出来。 

用 C/C++ 写 CGI 的最难之处应算从浏览器接收数据!不过,借助现成的源程序,从浏览器接收数据也只不过是小菜一碟而已。 

我说的这个现成的源程序是用 FireBird 的 bbs2www 程序包里提取出来的。在这里下载: cgi.c - cgi.h

经 本人提取出来的源程序只有两个文件 cgi.c 和 cgi.h 。当要用它们来写 CGI 程序时,只需在程序中加入 #include "cgi.c" 即可,现以例子说明一下使用方法。假设要通过 GET 方式从浏览器接收用户的名字和 E-Mail 地址,源程序如下: 

vi test.c # 编辑源文件 

// Begin

#include
#include "cgi.c"

main() {
    char *name,*email;

    cgi_init();
    cgi_html_head();
    name = cgi_get("name");
    email = cgi_get("email");

    printf("name = %s",name);
    printf("
");
    printf("email = %s",email);

    cgi_quit();
}

// End

首 先定义两个指针,然后调用 cgi_init() 来初始化 CGI 环境, cgi_html_head() 打印 HTML 文件类型信息,和 printf("Content-type:text/html\n\n"); 基本一样,不过用 cgi_html_head() 打印的头信息会指定文件的字符编码为 gb2312 即中文字符。调用 cgi_get() 方法取得指定关键字 ( name 和 email ) 的值。当完成 CGI 部分的代码后,要通用调用 cgi_quit() 和释放 CGI 所点的系统资源。最后就像以住一样去编译程序, gcc -O6 -o test test.c

gcc -Wall test.c cgic.c -o test.cgi 然后将该程序放到 cgi-bin 目录,接着通过以下方式调用该程序。 

 

 

 

 

cgic: 为 C 语言编写 CGI 的 C 函数库 
由 Thomas Boutell 开发 
目录 

CGIC 介绍 
怎样写 CGIC 应用程序 
怎样产生图片在 CGIC 中 ? 
CGI 调试特征 : 利用捕获 
cgic 函数参考 
cgic 变量参考 
cgic 结果编码参考 
cgic 快速索引 

一般的 UNIX 系统都支持 ANSIC, 增加相应的库函数 ( 和相应的 h 文件 ) 就可以实现 CGI 。在此我向大家推荐一个用于 CGI 编程的 ANSIC 库 :cgic 。 

cgic 是用来生成基于 CGI 的 WWW 应用程序的 C 语言函数库 , 它有以下功能 : 

* 对数据进行语法分析 

* 接收以 GET 和 PSOT 两种方式发送的数据 

* 把 FORM 中的不同域连接成连续的串 

* 为检索 FORM 数据而提供字符串 , 整数 , 浮点以及单项和多项选择功能 

* 为数字字段提供边界检测 

* 把 CGI 环境变量加载到非空的 C 串中 

* 为调试而捕捉 CGI 状态 

* 提供相对安全的系统调用功能 

用一般 ANSI C 或 C++ 编译器就可以编译 cgic 程序 , 不过与通常 C 程序不同的是 , 用 cgic 写的源码其主函数是 cgiMain(), 而不是通常的 main() 。 cgic 的函数库会自动把 cgiMain 连接到相应的 main() 上去。 


-------------------------------------------------------------------------------- 


写 CGIC 程序 
Note: 所有的 cgic 应用程序必须连接 cgic.c. 
用 cgimain() 替代 main() 必须包含: #include"cgic.h." 

基本结构 cgictest.c: 


int cgiMain() { 
#if DEBUG 
/* Load a saved CGI scenario if we're debugging */ 
cgiReadEnvironment("/path/to/capcgi.dat"); 
#endif 
/* Important: we must indicate the type of document */ 
cgiHeaderContentType("text/html"); 
/* Now invoke other functions to handle each part of the form */ 
fprintf(cgiOut, "\n"); 
fprintf(cgiOut, "cgic test\n"): 
fprintf(cgiOut, "

cgic test

\n"); 
Name(); 
Address(); 
Hungry(); 
Temperature(); 
Frogs(); 
Color(); 
Flavors(); 
NonExButtons(); 
RadioButtons(); 
fprintf(cgiOut, " \n"); 
/* This value will be the exit code of the program; 0 
generally indicates success among Unix and DOS programs */ 
return 0; 


capture 
输出标头 
cgiHeaderContentType() 在输出文挡之前简要说明 MIME 内型 , 如 "text/html" 。 
cgiHeaderStatus() 代替输出错误代码 cgiHeaderLocation() 代替重新引导至其他页面。在一个独立的应用程序中只能有一个 cgiHeader 函数。 

重点 : 在 cgiHeader 函数组中, cgiHeaderContentType(), 在任何向浏览器输出之前被调用 . 否则将出错或浏览器不能识别。 cgiOut 

接着 , cgiMain() 调用不同的函数 . 当函数结束后 , 将返回 0 


处理输入文本 
void Name() { 
char name[81]; 
cgiFormStringNoNewlines("name", name, 81); 
fprintf(cgiOut, "Name: %s
\n", name); 


这个函数的功能就是取的并显示由用户输入的 name . 
处理输出 
Important: cgiOut 通常相当于 stdout 

cgiFormString 确保断航 

处理单一 Checkboxes 输入 
这个 Hungry() function 确定用户是否选择 "hungry" 这个 checkbox: 
void Hungry() { 
if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) { 
fprintf(cgiOut, "I'm Hungry!
\n"); 
} else { 
fprintf(cgiOut, "I'm Not Hungry!
\n"); 



这个函数依靠 cgiFormCheckboxSingle() 确定单一的 checkbox 被选择。 cgiFormCheckboxSingle() 接受 checkbox 名字的属性值,如果存在就返回 cgiFormSuccess ,否则返回 cgiFormNotFound 如果是多项 checkboxes ,就用 cgiFormCheckboxMultiple() 和 cgiFormStringMultiple() 函数 . 
处理数字输入 
Temperature() 返回浮点书的值确保在特定的返回内。 
void Temperature() { 
double temperature; 
cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6); 
fprintf(cgiOut, "My temperature is %f.
\n", temperature); 


依靠 cgiFormDoubleBounded() 得到数据 . 第一个数据是返回数据中输入域的名字。最后一个值是用户没有提交时的默认值。 
这个函数总是找回在特定返回内合适的值 ; cgiFormDoubleBounded 返回的值被检查确信用户输入的资料在规定范围内, 而不是其他无效的数据。查看 cgiFormDoubleBounded() 更多的资料 . 如果限度检查不理想,可以用 cgiFormDouble() 替代 . 

在整数输入 ,cgiFormInteger 和 cgiFormIntegerBounded 可以利用 . 这些函数的功能类似 . 

处理单一选择输入 
 在表但的列表 . cgiFormSelectSingle() 
cgiFormSelectSingle() 总是显示合理的选项值 . 

radio button 也可以用这个函数 . 另外还有 cgiFormRadio(), 也是一样的 


处理多项选择的输入 
NonExButtons() 
char *votes[] = { 
"A", 
"B", 
"C", 
"D" 
}; 

void NonExButtons() { 
int voteChoices[4]; 
int i; 
int result; 
int invalid; 

char **responses; 

/* Method #1: check for valid votes. This is a good idea, 
since votes for nonexistent candidates should probably 
be discounted... */ 
fprintf(cgiOut, "Votes (method 1):
\n"); 
result = cgiFormCheckboxMultiple("vote", votes, 4, 
voteChoices, &invalid); 
if (result == cgiFormNotFound) { 
fprintf(cgiOut, "I hate them all!

\n"); 
} else { 
fprintf(cgiOut, "My preferred candidates are:\n"); 
fprintf(cgiOut, "

    \n"); 
    for (i=0; (i < 4); i++) { 
    if (voteChoices[i]) { 
    fprintf(cgiOut, "
  • %s\n", votes[i]); 


    fprintf(cgiOut, "
\n"); 


参考 cgiFormCheckboxMultiple(), cgiFormSelectMultiple(). 
cgiFormCheckboxMultiple() cgiFormCheckboxMultiple 

NonExButtons() 函数在 cgictest.c: 

/* Method #2: get all the names voted for and trust them. 
This is good if the form will change more often 
than the code and invented responses are not a danger 
or can be checked in some other way. */ 
fprintf(cgiOut, "Votes (method 2):
\n"); 
result = cgiFormStringMultiple("vote", &responses); 
if (result == cgiFormNotFound) { 
fprintf(cgiOut, "I hate them all!

\n"); 
} else { 
int i = 0; 
fprintf(cgiOut, "My preferred candidates are:\n"); 
fprintf(cgiOut, "

    \n"); 
    while (responses[i]) { 
    fprintf(cgiOut, "
  • %s\n", responses[i]); 
    i++; 

    fprintf(cgiOut, "
\n"); 

/* We must be sure to free the string array or a memory 
leak will occur. Simply calling free() would free 
the array but not the individual strings. The 
function cgiStringArrayFree() does the job completely. */ 
cgiStringArrayFree(responses); 


参考 cgiFormStringMultiple() 
cgiFormStringMultiple() 

/* An array of strings; each C string is an array of characters */ 
char **responses; 

cgiFormStringMultiple("vote", &responses); 

检查 CGI 环境变量 
将用到的变量 这里 , 
产生图象 

#include "cgic.h" 
#include "gd.h" 

char *colors[] = { 
"red", "green", "blue" 
}; 

#define colorsTotal 3 

int cgiMain() { 
int colorChosen; 
gdImagePtr im; 
int r, g, b; 
/* Use gd to create an image */ 
im = gdImageCreate(64, 64); 
r = gdImageColorAllocate(im, 255, 0, 0); 
g = gdImageColorAllocate(im, 0, 255, 0); 
b = gdImageColorAllocate(im, 0, 0, 255); 
/* Now use cgic to find out what color the user requested */ 
cgiFormSelectSingle("color", 3, &colorChosen, 0); 
/* Now fill with the desired color */ 
switch(colorChosen) { 
case 0: 
gdImageFill(im, 32, 32, r); 
break; 
case 1: 
gdImageFill(im, 32, 32, g); 
break; 
case 2: 
gdImageFill(im, 32, 32, b); 
break; 

/* Now output the image. Note the content type! */ 
cgiHeaderContentType("image/gif"); 
/* Send the image to cgiOut */ 
gdImageGif(im, cgiOut); 
/* Free the gd image */ 
gdImageDestroy(im); 
return 0; 


为调试而捕捉CGI 状态 
cgic 函数参考 
cgiFormResultType cgiFormString( char *name, char *result, int max) 
用于从输入域中copy 字符串。他将域名max-1 字节中的字符copy 到缓冲区result 。若域不存在,则copy 一个空串到result 缓冲区。在此函数中所有的新行由换行符代表。 

cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max) 
它与cgiFormString 函数相似,只是所有的CR 和LF 都被去掉了。 

cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length) 
它返回指向name 的字符串的长度,并将长度放入length 中。 

cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray) 
若同一名字有多个输入域,或域中的字符串可以动态变化,那么你可以使用本函数。它把名为name 的所有输入域的值放在prtToStringArray 中。 

void cgiStringArrayFree(char **stringArray) 
它释放了分配给stringArray 的内存。 

cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV) 
从输入域中取出整数放入result 中。 

cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV) 
若输入域中的整数在界限内则取出并放入result 中。 

cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV) 
从输入域中取出浮点数放入result 中。 

cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV) 
若输入域中的浮点数在界限内则取出并放入result 中。 

cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV)
取出复选框( 跟在select 语句之后的) ,把选择的名字copy 到choicesText ,把选择的个数copy 到choicesTotal ,把当前的选择copy 到result 。 
cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid) 
与cgiFormSelectSingle 类似,只指向整型数组的result 代表了选择的项。 

cgiFormResultType cgiFormCheckboxSingle( char *name) 
若复选框被选中,则函数返回cgiFormSuccess ,否则返回cgiFormNotFound 。 

cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid)

与cgiFormCheckboxSingle 类似,但它处理同一名字有多个复选框的情况。name 指向复选框的名字;valuesText 指向包含有每 个复选框中参数的一个数组;valuesTotal 指向复选框的总数;result 是一个整型数组,每个复选框选中的用1 代表,没选中的用0 代表。 

cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV) 
与cgiFormCheckboxMultiple 相似,只是这里是单选按钮而不是复选框。 

void cgiHeaderLocation(char *redirectUrl) 
重定向到redirectUrl 指定的URL 。 

void cgiHeaderStatus(int status, char *statusMessage) 
输出状态代码status 和消息statusMessage 。 

void cgiHeaderContentType(char *mimeType) 
用于告知浏览器返回的是什么类型的文档。 

cgiEnvironmentResultType cgiWriteEnvironment(char *filename) 
本函数把当前CGI 环境写入filename 文件中以便以后调试时使用 

cgiEnvironmentResultType cgiReadEnvironment(char *filename) 
本函数从filename 文件中读取CGI 环境以便用来调试。 

int cgiMain() 
一个程序必须要写这个函数, 这是主程序开始之处。 
cgic 变量参考 
This section provides a reference guide to the various global variables provided by cgic for the program mer to utilize. These variables should always be used in preference to stdin, stdout, and calls to getenv() in order to ensure compatibility with the cgic CGI debugging features. 
大多数的变量相当于各种 CGI 变量,重要的是 VGIC 的变量不能为空 . 

char *cgiServerSoftware 
服务器软件名称 , 或者一个空的字符串 or to an empty string if unknown. 
char *cgiServerName 
返回服务器名称或空 
char *cgiGatewayInterface 
网关接口 ( 通常是 CGI/1.1), 或空 
char *cgiServerProtocol 
网络协议 (usually HTTP/1.0), 或空 
char *cgiServerPort 
服务器端口 (usually 80), 或空 
char *cgiRequestMethod 
请求方式 (usually GET or POST), 或空 
char *cgiPathInfo 
指出附加虚拟路径 
char *cgiPathTranslated 
指出附加虚拟路径并由服务器转为本地路径 
char *cgiScriptName 
调用程序的名字 
char *cgiQueryString 
包含 GET-method 请求或者  标签 . 这个信息不需要解吸,除非用  标签通常由 CGIC 函数库自动解析。 
char *cgiRemoteHost 
从浏览器返回客户主机的名字 
char *cgiRemoteAddr 
从浏览器返回客户的 IP 地址 
char *cgiAuthType 
返回用户授权信息 
char *cgiRemoteUser 
鉴别用户 cgiAuthType. 
char *cgiRemoteIdent 
返回用户的名字(用户通过用户坚定协议)这个消息是不安全的,特别是 Windows 系统。 
char *cgiContentType 
返回 MIME 内型 
char *cgiAccept 
参考 cgiHeaderContentType() cgiUserAgent 
char *cgiUserAgent 
取的用户浏览器信息 
char *cgiReferrer 
指向用户访问的 URL. 
int cgiContentLength 
表单或查询数据的字节被认为是标准的 . 
FILE *cgiOut 
CGI 输出 . cgiHeader 函数 , 象 cgiHeaderContentType, 首先被用于输出 mime 头 ; 用于 fprintf() 和 fwrite(). cgiOut 通常相当于 stdout 。 
FILE *cgiIn 
CGI 输入 . 在决大部分时间你都不会需要这个函数。 
cgic 结果编码参考 
在大量的按列中 , cgic 函数有计划的产生合理的结果,甚至浏览器和用户不合理时。无论如何 , 有时候知道不合理的事情发生,尤其赋予一个值或定义一个范围是一个不充分的解决方案。下面的这些结果编码有助更好了解。 

cgiFormSuccess 
提交信息成功 
cgiFormTruncated 
删除部分字节 . 
cgiFormBadType 
错误的输入信息(没有按要求) 
cgiFormEmpty 
提交信息为空 . 
cgiFormNotFound 
提交信息没有找到 . 
cgiFormConstrained 
数字属于某个特定的范围,被迫低于或高于适当范围。 
cgiFormNoSuchChoice 
单一选择提交的值是不被接受。通常说明表但和程序之间存在矛盾。 
cgiEnvironmentIO 
从 CGI 环境或获取的文件读或写的企图失败,报出 I/O 的错误。 
cgiEnvironmentMemory 
从 CGI 环境或获取的文件读或写的企图失败,报出 out-of-memory 的错误。 
cgiEnvironmentSuccess 
从 CGI 环境或获取的文件读或写的企图成功。 
cgic 快速索引 
cgiAccept | cgiAuthType | cgiContentLength | cgiContentType | cgiEnvironmentIO | cgiEnvironmentMemory | cgiEnvironmentSuccess | cgiFormBadType | cgiFormCheckboxMultiple() | cgiFormCheckboxSingle() | cgiFormConstrained | cgiFormDouble() | cgiFormDoubleBounded() | cgiFormEmpty | cgiFormInteger() | cgiFormIntegerBounded() | cgiFormNoSuchChoice | cgiFormNotFound | cgiFormRadio() | cgiFormSelectMultiple() | cgiFormSelectSingle() | cgiFormString() | cgiFormStringMultiple() | cgiFormStringNoNewlines() | cgiFormStringSpaceNeeded() | cgiFormSuccess | cgiFormTruncated | cgiGatewayInterface | cgiHeaderContentType() | cgiHeaderLocation() | cgiHeaderStatus() | cgiIn | cgiMain() cgiOut | cgiPathInfo | cgiPathTranslated | cgiQueryString | cgiReadEnvironment() | cgiReferrer() | cgiRemoteAddr | cgiRemoteHost | cgiRemoteIdent | cgiRemoteUser | cgiRequestMethod | cgiScriptName | cgiServerName | cgiServerPort | cgiServerProtocol | cgiServerSoftware | cgiStringArrayFree() | cgiUserAgent | cgiWriteEnvironment()

CGIC 简明教程 

本系列的目的是演示如何使用C 语言的CGI 库“CGIC” 完成Web 开发的各种要求。

       基础知识
    * 1: 使用CGIC 的基本思路
    * 2: 获取Get 请求字符串
    * 3: 反转义
    * 4: 获取请求中的参数值
     
进阶训练
    * 用CGIC 实现文件上传
 


CGIC
 简明教程1 :使用CGIC 的基本思路 

C 语言编程是一项复杂且容易出错的工作,所以在完成复杂任务时,一定要选择合适的库。对于用C 语言编写CGI 程序则更是如此。
CGIC 是非常优秀的C 语言CGI 库函数。 其下载地址为:,现在的版本号是2.05 。
本站从今天开始,将逐步介绍如何使用CGIC 完成各种操作,也可以说是一个Tutorial 。
(注:本系列涉及的编程环境都是Linux ,Windows 用户需要对用到的操作系统命令稍作修改)

本文纲要 :
CGIC 的安装、测试安装、使用CGIC 的基本思路;
1) CGIC 的下载安装

从上面提供的官方网址下载了CGIC 库之后,解开压缩包,里面有大约10 个文件,有用的是:
cgic.h :头文件;
cgic.c :CGIC 的源代码文件;
cgictest.c :CGIC 库的作者提供的一个CGI 程序例子;
capture.c :用于调试CGI 程序的工具;
Makefile :安装CGIC 的脚本文件;
可以看到,整个库实际上就是cgic.c 一个文件,可以说是非常的精炼。
我们可以把CGIC 安装为操作系统的一个动态链接库,这样我们每次编译的时候,就不需要有cgic.c 这个源文件了。
但是由于需要(以后将会看到),我们将修改cgic.c 代码,所以我们不把它安装进系统。每次编译的时候,只要把cgic.c 和cgic.h 放到当前文件夹就好了。
2) 测试安装

在开始编写你自己的CGI 程序之前,一定要先走通他的例子程序,免得后来程序出错的时候还不知道是配置有问题,还是你的程序代码有问题。
我们用他自带cgictest.c 来实现自己的第一个C 语言CGI 程序。
你可以新建一个工作目录,用于存放你的CGI 程序源代码,把cgic.h, cgic.c, cgictest.c 三个文件拷贝到这个目录,然后建立一个Makefile 文件,其内容为:

   1. test.cgi:cgictest.c cgic.h cgic.c
   2. gcc -wall cgictest.c cgic.c -o test.cgi

需要提醒的是,第二行开头一定是一个tab 键(且仅有一个),不能使用空格。
保存好Makefile 的内容之后,执行make 命令:
make

我们看到,当前目录下应该多了一个test.cgi 文件。

在 你的网站根目录下建立一个cgi-bin 目录(当然名字可以任意取,但作为习惯,一般叫做cgi-bin ),然后在Apache 的配置文件里赋予其执行 CGI代码的权限,权限修改完之后要重启Apache 。完成之后,把刚才生成的test.cgi 放到cgi-bin 目录中。此时我们可以在浏览器中输入以 下地址进行访问:


如果正常的话,应该看到一个网页被展示出来。这样,第一个C 语言的CGI 程序就运行起来了。
如果浏览器报错,那么多半是配置Apache 的时候有些操作没有正确完成。
3) 使用CGIC 的基本思路

从 cgic.c 的代码可以看出,它定义了main 函数,而在cgictest.c 中定义了一个cgiMain 函数。也就是说,对于使用CGIC 编写的CGI 程序,都是从cgic.c 中的代码进入,在库函数完成了一系列必要的操作(比如解析参数、获取系统环境变量)之后,它才会调用你的代码(从你定义的 cgiMain 进入)。

另外一点就是,cgi 程序输出HTML 页面的方式都是使用printf 把页面一行一行地打印出来,比如cgictest.c 中的这一段代码:
fprintf(cgiOut, "\n");

上面这段代码的运行结果就是在页面上输出一个textarea 。 第一个参数cgiOut 实际上就是stdin ,所以我们可以直接使用printf ,而不必使用fprintf 。不过在调试的时候会用到fprintf 来重定向输出。
这种方式与Java Servlet 非常类似,Servlet 也是通过调用打印语句System.out.println(…) 来输出一个页面。(不过后来Java 推出了JSP 来克服这种不便。)
但是与Servlet 不同的地方在于,使用C 语言的我们还要自己输出HTML 头部(声明文档类型):
cgiHeaderContentType("text/html");

这个语句的调用一定要在所有printf 语句之前。而这个语句执行的任务实际上就是:
void cgiHeaderContentType(char *mimeType) {
    fprintf(cgiOut, "Content-type: %s\r\n\r\n", mimeType);
}

这个语句告诉浏览器,这次传来的数据是什么类型,是一个HTML 文档,还是一个bin 文件… 如果是个HTML 文档,就通过浏览器窗口显示,如果是一个bin (二进制)文件,则打开下载窗口,让用户选择是否保存文件以及保存文件的路径。

理解了这几点之后,你就可以编写自己的CGIC 程序了。新建一个文件test.c 试试:
下载: test.c

   1. #include
   2. #include "cgic.h"
   3. #include
   4. #include
   5. int cgiMain() {
   6.     cgiHeaderContentType("text/html");
   7.     fprintf(cgiOut, "\n");
   8.     fprintf(cgiOut, "My First CGI\n");
   9.     fprintf(cgiOut, "

Hello CGIC

\n");
  10.     fprintf(cgiOut, "\n");
  11.     return 0;
  12. }

把Makefile 文件中的cgitest.c 全部换称test.c ,保存,再执行make 命令即可。
此时通过浏览器访问,会在页面上看到一个大大的“Hello CGIC” 。


CGIC
 简明教程2 :获取Get 请求字符串 


Get 请求就是我们在浏览器地址栏输入URL 时发送请求的方式,或者我们在HTML 中定义一个表单(form )时,把action 属性设为“Get” 时的工作方式;

Get 请求字符串就是跟在URL 后面以问号“?” 开始的字符串,但不包括问号。比如这样的一个请求:


在上面这个URL 中,“ThisIsTheGetString” 就是Get 请求字符串。

在进入我们自己编写的cgi 代码之前,CGIC 库已经事先把这个字符串取到了,我们可以在程序中直接获得,要做的仅仅是在你编写的cgiMain 方法前面加入以下声明:
extern char *cgiQueryString;

现在给出一个简单的例子,这个例子跟上一篇的测试程序非常相似,只不过程序的输出是使用者输入的Get 请求字符串。
下载: test.c

   1. #include
   2. #include "cgic.h"
   3. #include
   4. #include
   5.  
   6. extern char *cgiQueryString;
   7. int cgiMain() {
   8.     cgiHeaderContentType("text/html");
   9.     fprintf(cgiOut, "\n");
  10.     fprintf(cgiOut, "My CGIC\n");
  11.     fprintf(cgiOut, "");
  12.     fprintf(cgiOut, "

%s

",cgiQueryString);
  13.     fprintf(cgiOut, "\n");
  14.     fprintf(cgiOut, "\n");
  15.     return 0;
  16. }

假设把这个程序编译成out.cgi (编译方法参见上一篇),并部署到Web 服务器的cgi-bin 目录下,当用户在浏览器地址栏输入本文开头给出的URL 字符串时,浏览器页面上会显示:
ThisIsTheGetString

我们也可以编写一个用于测试的HTML 页面:
下载: test.html

   1.
   2.
   3.     Test
   4.
   5.
   6.    

   7.        
   8.        
   9.    

  10.
  11.

文件的部署结构应该为:
|test.html
|—-cgi-bin/out.cgi

大家可以试试,通过浏览器访问,在文本框内输入一些字符,并点击提交按钮,然后就可以看到cgi 程序的执行结果:把在文本框输入的字符原样显示在浏览器上。



CGIC
 简明教程3 :反转义 

浏览器在发送Get 请求时,会把请求字符串进行转义操作(英文术语为: escape ); 比如,我们在地址栏输入(注意最后”it’s me” 中的空格):
~Jack/cgi-bin/out.cgi?it's me

浏览器会把它转义为:
~Jack/cgi-bin/out.cgi?it's%20me


在上一篇最后给出的例子中,如果在文本框内输入
it's me

你会发现,浏览器最终发送的请求为
~Jack/cgi-bin/out.cgi?theText=it%27s+me

通过CGIC ,我们可以把这些被转义后的字符还原为我们本来的输入,这个过程就叫“ 反转义” (Unescape) 。
不过这个过程有点像hack 他的代码。

整个过程分三个步骤:
1 )打开cgic.c ,找到这一行语句:
static cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);

注意,我们要找的只是这个函数声明,不是函数定义;

2 )在这个函数声明语句的上方,你会看到一个结构体定义:

   1. typedef enum {
   2.     cgiUnescapeSuccess,
   3.     cgiUnescapeMemory
   4. } cgiUnescapeResultType;

把这几行语句复制到cgic.h 文件中,并在这里把它注释掉;
同时还要删除在第一步中找到的函数声明语句中的“static” 关键字。

3 )我们现在就可以使用反转义函数cgiUnescapeChars 了:
在你自己的代码(按照惯例,还是test.c )中,加入以下声明语句即可
extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);

接下来我们给出一段完整的test.c 代码
下载: test.c

   1. #include
   2. #include "cgic.h"
   3. #include
   4. #include
   5.  
   6. extern char *cgiQueryString;
   7. extern cgiUnescapeResultType cgiUnescapeChars(char **sp, char *cp, int len);
   8. int cgiMain() {
   9.     char * buffer;
  10.     cgiHeaderContentType("text/html");
  11.     fprintf(cgiOut, "\n");
  12.     fprintf(cgiOut, "My CGI\n");
  13.     fprintf(cgiOut, "");
  14.     cgiUnescapeChars(&buffer, cgiQueryString, strlen(cgiQueryString));
  15.     fprintf(cgiOut, "

%s

",buffer);
  16.     fprintf(cgiOut, "\n");
  17.     fprintf(cgiOut, "\n");
  18.     free(buffer);
  19.     return 0;
  20. }

值得注意的是,buffer 的存储空间是cgiUnescapeChars 帮你分配的,但最后要由你自己来释放(free ),这一点千万不可忘记。

下面你可以结合上一篇给出的测试用html 代码试试该cgi 程序的运行结果,也可以直接在浏览器地址栏输入一些带有特殊符号的字符串。

最后讲一下为什么不得不用这种hacker 的方式来完成该任务,而CGIC 不显式提供?
CGIC 的出发点是,我们平时只需要解析请求中的键值对,比如:”?q=nice&client=IE” ,当我们在服务端查询“q” 的值时,我们就能得到 “nice” 。CGIC 有一族函数帮助我们完成这个任务,比如cgiFormString (以后会讲到)。在解析这种请求格式的时候,如果我们提供的参数 值含有被转义的字符,那么CGIC 就会在内部调用cgiUnescapeChars 完成反转义。
但是,有时候我们会发送非常复杂的Get 请求字符串,但并不是“ 键-值” 对的格式。这就需要直接使用cgiUnescapeChars 进行反转义了。
例如:假设我们有个服务端cgi 程序chat.cgi ,这是个网络聊天机器人(也许你可以开发自己的Web 版MSN 机器人、QQ 机器人)。如果我们发送如下请求:
"this is a cgi user"

那么chat.cgi 就会把“this is a cgi user” 当做你对它说的话,经过处理,它会回复一段语句。为了方便,我们并没有写成“ 键-值” 对的形式。这个时候被我们hack 的cgiUnescapeChars 就能派上用场了。


CGIC 简明教程4 :获取请求中的参数值 

我们在提交一个表单(form) 时,怎样把表单内的值提取出来呢?
比如下面这个表单:

   
   
   


当out.cgi 收到请求时,需要把输入框”name” 和输入框”number” 内的值提取出来。而且不管form 中的action 是GET 还是POST ,都要有效。

下面给出示例代码:
下载: test.c

   1. #include
   2. #include "cgic.h"
   3. #include
   4. #include
   5.  
   6. int cgiMain() {
   7.     char name[241];
   8.     char number[241];
   9.     cgiHeaderContentType("text/html");
  10.     fprintf(cgiOut, "\n");
  11.     fprintf(cgiOut, "My CGI\n");
  12.     fprintf(cgiOut, "");
  13.     cgiFormString("name", name, 241);
  14.     cgiFormString("number", number, 241);
  15.     fprintf(cgiOut, "

%s

",name);
  16.     fprintf(cgiOut, "

%s

",number);
  17.     fprintf(cgiOut, "\n");
  18.     fprintf(cgiOut, "\n");
  19.     return 0;
  20. }

从上面的代码可以看出,第13 行和第14 行获取了输入框的值。

获取输入参数值在CGIC 中其实有一族函数,cgiFormString 是其中最常用的一个。
cgiFormStringNoNewlines 用来去掉换行符(如果用户是在一个TextArea 里输入字符的话);
cgiFormStringSpaceNeeded 用于测试输入值的长度,可以以此为依据,然后按需精确分配缓冲区。

用C 语言库(CGIC) 编写CGI ,实现文件上传


用C 语言编写cgi 程序的话,多半会用到CGIC 。 这是个非常流行的库,遇到文件上传之类的应用更是离不开它。官方页面及下载地址为:

不少网站都有文件上传的功能,本文展示如何用CGIC 库编写文件上传的服务端程序,最后给出一段简单的HTML 代码,供大家测试使用 。
下载: upload.c

   1. #include
   2. #include
   3. #include
   4. #include
   5. #include
   6. #include"cgic.h"
   7. #define BufferLen 1024
   8. int cgiMain(void){
   9.     cgiFilePtr file;
  10.     int targetFile;
  11.     mode_t mode;
  12.     char name[128];
  13.     char fileNameOnServer[64];
  14.     char contentType[1024];
  15.     char buffer[BufferLen];
  16.     char *tmpStr=NULL;
  17.     int size;
  18.     int got,t;
  19.     cgiHeaderContentType("text/html");
  20.     // 取得html 页面中file 元素的值,应该是文件在客户机上的路径名
  21.     if (cgiFormFileName("file", name, sizeof(name)) !=cgiFormSuccess) {
  22.         fprintf(stderr,"could not retrieve filename\n");
  23.         goto FAIL;
  24.     }
  25.     cgiFormFileSize("file", &size);
  26.     // 取得文件类型,不过本例中并未使用
  27.     cgiFormFileContentType("file", contentType, sizeof(contentType));
  28.     // 目前文件存在于系统临时文件夹中,通常为/tmp ,通过该命令打开临时文件。临时文件的名字与用户文件的名字不同,所以不能通过路径/tmp/userfilename 的方式获得文件
  29.     if (cgiFormFileOpen("file", &file) != cgiFormSuccess) {
  30.         fprintf(stderr,"could not open the file\n");
  31.         goto FAIL;
  32.     }
  33.     t=-1;
  34.     // 从路径名解析出用户文件名
  35.     while(1){
  36.         tmpStr=strstr(name+t+1,"\\");
  37.         if(NULL==tmpStr)
  38.         tmpStr=strstr(name+t+1,"/");//if "\\" is not path separator, try "/"
  39.         if(NULL!=tmpStr)
  40.             t=(int)(tmpStr-name);
  41.         else
  42.             break;
  43.     }
  44.     strcpy(fileNameOnServer,name+t+1);
  45.     mode=S_IRWXU|S_IRGRP|S_IROTH;
  46.     // 在当前目录下建立新的文件,第一个参数实际上是路径名,此处的含义是在cgi 程序所在的目录(当前目录))建立新文件
  47.     targetFile=open(fileNameOnServer,O_RDWR|O_CREAT|O_TRUNC|O_APPEND,mode);
  48.     if(targetFile<0){
  49.         fprintf(stderr,"could not create the new file,%s\n",fileNameOnServer);
  50.         goto FAIL;
  51.     }
  52.     // 从系统临时文件中读出文件内容,并放到刚创建的目标文件中
  53.     while (cgiFormFileRead(file, buffer, BufferLen, &got) ==cgiFormSuccess){
  54.         if(got>0)
  55.             write(targetFile,buffer,got);
  56.     }
  57.     cgiFormFileClose(file);
  58.     close(targetFile);
  59.     goto END;
  60. FAIL:
  61.     fprintf(stderr,"Failed to upload");
  62.     return 1;
  63. END:
  64.     printf("File \"%s\" has been uploaded",fileNameOnServer);
  65.     return 0;
  66. }

假设该文件存储为upload.c ,则使用如下命令编辑:
gcc -Wall upload.c cgic.c -o upload.cgi

编译完成后把upload.cgi 复制到你部署cgi 程序的目录(通常命名为cgi-bin )。
正式部署时,请务必修改用open 创建新文件那一行代码。把open 的第一个参数设置为目标文件在服务器上存储的绝对路径,或者相对于cgi 程序的相对路径。本例中,出于简单考虑,在cgi 程序所在目录下创建新文件。

测试用HTML 代码:
下载: upload.html

   1.

 
   2.      
   3.


最后的文件目录结构为
/MyWebRoot
|—/upload.html
|—/cgi-bin
|——/upload.cgi
当然,你必须配置能够cgi-bin ,并且程序要有权限在cgi-bin 目录下创建文件(因为此例把文件上传到cgi-bin 目录下)。

那么如何控制上传文件的大小呢?因为你有时会不允许用户上传太大的文件。
通过分析cgic.c 的源代码,我们发现它定义了一个变量cgiContentLength ,表示请求的长度。但我们需要首先判断这是一个上传文件的请求,然后才能根据cgiContentLength 来检查用户是否要上传一个太大的文件。
cgic.c 的main 函数中进行了一系列if-else 判断来检查请求的类型,首先确定这是一个post 请求,然后确定数据的编码方式为 “multipart/form-data” ,这个判断通过之后,就要开始准备接收数据了。所以我们要在接收数据开始之前使用 cgiContentLength 判断大小,如果超过标准,就立即返回,不允许继续操作。
下面贴出修改后代码片段(直接修改cgic.c 的源代码即可):

   1. else if (cgiStrEqNc(cgiContentType, "multipart/form-data")) {
   2. #ifdef CGICDEBUG
   3. CGICDEBUGSTART
   4.     fprintf(dout, "Calling PostMultipartInput\n");
   5. CGICDEBUGEND
   6. #endif /* CGICDEBUG */
   7. // 我的代码
   8. //UpSize: 文件长度上限值,以byte 为单位,UpSize 是一个int 变量,因为cgiContentLength 的类型为int
   9.     if(cgiContentLength>UpSize){
  10.         cgiHeaderContentType("text/html");
  11.         printf("File too large!\n");
  12.         cgiFreeResources();
  13.         return -1;
  14.     }
  15. // 我的代码结束
  16.     if (cgiParsePostMultipartInput() != cgiParseSuccess) {
  17. #ifdef CGICDEBUG
  18. CGICDEBUGSTART
  19.         fprintf(dout, "PostMultipartInput failed\n");
  20. CGICDEBUGEND
  21. #endif /* CGICDEBUG */
  22.         cgiFreeResources();
  23.         return -1;
  24.     }
  25. #ifdef CGICDEBUG
  26. CGICDEBUGSTART
  27.     fprintf(dout, "PostMultipartInput succeeded\n");
  28. CGICDEBUGEND
  29.     #endif /* CGICDEBUG */
  30. }
  31. }

变量UpSize 表示文件大小的上限。在cgic.c 的main 中找到相关代码,并修改成上面这样即可。你可以在cgic.c 中定义UpSize ,也可以在刚才完成的upload.c 中定义,然后在cgic.c 中用extern 方式引用。

原文链接 : http://blog.csdn.net/hurtmanzc/archive/2007/08/24/1757747.aspx

阅读(1644) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~