北京电子科技学院(BESTI)
实 验 报 告
课程:信息安全系统设计基础 班级:1452
姓名:(按贡献大小排名)鄢曼君 李子璇
学号:(按贡献大小排名)20145227 20145201
成绩: 指导教师:娄嘉鹏 实验日期:2016.11.3
实验密级:无 预习程度:已预习 实验时间:10:00-12:30
仪器组次: 必修/选修:必修 实验序号:05
实验名称:简单嵌入式WEB服务器实验
实验目的与要求:
1、掌握在ARM开发板实现一个简单的WEB服务器的过程。
2、看懂代码,在宿主机和实验箱中实现。
实验仪器:
名称 | 型号 | 数量 |
---|---|---|
arm | UP-TECH | 1 |
pc | Windows XP | 1 |
虚拟机 | redhat | 1 |
实验原理
1、在Linux PC上,利用arm-linux-gcc编译器,可编译出针对Linux ARM平台的可执行代码。
2、了解多线程程序设计的基本原理。
3、学习pthread库函数的使用。
实验内容与步骤
1、开发环境的配置同实验一。
2、将实验代码拷贝到共享文件夹中。
3、编译应用程序
进入07_httpd文件夹,运行 make 产生可执行文件httpd,然后分别输入armv4l-unknown-linux-gcc -o ../bin/httpd httpd.o copy.o -lpthread
armv4l-unknown-linux-gcc -o httpd httpd.o copy.o -lpthread
再次查看文件夹时就可以发现已经生成httpd可执行文件。
4、下载调试
使用 NFS 服务方式将HTTPD 下载到超级终端上,并拷贝测试用的网页进行调试,运行完之后是等待连接的界面,
5、本机测试
在台式机的浏览器中输入 http://192.168.0.121,观察在客户机的浏览器中的连接请求结果和在开发板上的服务器的打印信息。
- 客户机的浏览器中的连接请求结果
- 在开发板上的服务器的打印信息。
实验代码分析:
/ * httpd.c: A very simple http server
* Copyfight (C) 2003 Zou jian guo <ah_zou@163.com>
* Copyright (C) 2000 Lineo, Inc. (www.lineo.com)
* Copyright (c) 1997-1999 D. Jeff Dionne <jeff@lineo.ca>
* Copyright (c) 1998 Kenneth Albanowski <kjahds@kjahds.com>
* Copyright (c) 1999 Nick Brok <nick@nbrok.iaehv.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include <signal.h>
#include <unistd.h>
#include <ctype.h>
#include "pthread.h"
#define DEBUG
int KEY_QUIT=0;
int TIMEOUT=30;
#ifndef O_BINARY
#define O_BINARY 0
#endif
char referrer[128];
int content_length;
#define SERVER_PORT 80
/*发送HTTP协议数据头,服务器回应http协议数据头,发送纯文本文件信息、gif格式图片、gpeg格式图片信息、html信息、服务器版本信息、文件永不过期信息。*/
int PrintHeader(FILE *f, int content_type)
{
alarm(TIMEOUT);
fprintf(f,"HTTP/1.0 200 OKn");
switch (content_type)
{
case 't':
fprintf(f,"Content-type: text/plainn");
break;
case 'g':
fprintf(f,"Content-type: image/gifn");
break;
case 'j':
fprintf(f,"Content-type: image/jpegn");
break;
case 'h':
fprintf(f,"Content-type: text/htmln");
break;
}
fprintf(f,"Server: uClinux-httpd 0.2.2n");
fprintf(f,"Expires: 0n");
fprintf(f,"n");
alarm(0);
return(0);
}
//对jpeg格式的文件进行处理;
int DoJpeg(FILE *f, char *name)
{
char *buf;
FILE * infile;
int count;
if (!(infile = fopen(name, "r"))) {
fprintf(stderr, "Unable to open JPEG file %s, %dn", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'j');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
//处理gif格式文件;
int DoGif(FILE *f, char *name)
{
char *buf;
FILE * infile;
int count;
if (!(infile = fopen(name, "r"))) {
alarm(TIMEOUT);
fprintf(stderr, "Unable to open GIF file %s, %dn", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'g');
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
//处理目录;
int DoDir(FILE *f, char *name)
{
char *buf;
DIR * dir;
struct dirent * dirent; //此处dirent不仅仅指向目录,还指向目录中的具体文件,dirent结构体存储的关于文件的信息很少,所以dirent起着一个索引的作用
if ((dir = opendir(name))== 0) {
fprintf(stderr, "Unable to open directory %s, %dn", name, errno);
fflush(f);
return -1;
}
PrintHeader(f,'h');
alarm(TIMEOUT);
fprintf(f, "<H1>Index of %s</H1>nn",name);
alarm(0);
if (name[strlen(name)-1] != '/') {
strcat(name, "/");
}
while(dirent = readdir(dir)) {
alarm(TIMEOUT);
fprintf(f, "<p><a href="/%s%s">%s</p>n", name, dirent->d_name, dirent->d_name);
alarm(0); //发送目录信息;
}
closedir(dir);
return 0;
}
int DoHTML(FILE *f, char *name)
{
char *buf;
FILE *infile; //定义文件流指针
int count;
char * dir = 0;
if (!(infile = fopen(name,"r"))) { //通过文件名打开一个文件,只读属性;
alarm(TIMEOUT);
fprintf(stderr, "Unable to open HTML file %s, %dn", name, errno); //打印打开文件失败信息;
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'h'); //发送http协议数据报;f表示客户连接的文件流指针用于写入http协议数据头信息;
copy(infile,f); /* prints the page */ //将打开的文件内容通过发送回客户端;
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
int DoText(FILE *f, char *name) //纯文本文件的处理;
{
char *buf;
FILE *infile; //定义文件流指针;
int count;
if (!(infile = fopen(name,"r"))) { //通过文件名打开一个文件,只读属性
alarm(TIMEOUT);
fprintf(stderr, "Unable to open text file %s, %dn", name, errno);
fflush(f);
alarm(0);
return -1;
}
PrintHeader(f,'t'); //发送t类型的http协议数据头信息;
copy(infile,f); /* prints the page */
alarm(TIMEOUT);
fclose(infile);
alarm(0);
return 0;
}
int ParseReq(FILE *f, char *r)
{
char *bp; //定义指针bp;
struct stat stbuf;
char * arg; //参数指针;
char * c;
int e;
int raw;
#ifdef DEBUG
printf("req is '%s'n", r); //打印请求命令;例如:GET /img/baidu_sylogo1.gif HTTP/1.1rn
#endif
while(*(++r) != ' '); /*skip non-white space*/ //判断buf中的内容是否为空跳过非空白;
while(isspace(*r)) //判断r所在位置的字符是否为空格若为空格则r指向下一个字符;
r++;
while (*r == '/') //判断r所在位置的字符是否为/若为空格则r指向下一个字符;
r++;
bp = r; //将r所指向的内容赋值给bp bp指向/之后的内容;img/baidu_sylogo1.gif HTTP/1.1rn
while(*r && (*(r) != ' ') && (*(r) != '?'))
r++;//当r不为空,并求 r不为?时r指向下一个字符
#ifdef DEBUG
printf("bp='%s' %x, r='%s' n", bp, *bp,r); //打印 r和bp的值;
#endif
if (*r == '?') //判断 r是否为 ?若为?则执行以下语句;
{
char * e; //定义指针变量;
*r = 0; //将r所在位置处的字符设为; 的ASCII码值是0
arg = r+1; //arg指向下一个参数;
if (e = strchr(arg,' '))
{
*e = ''; //如果arg为空则将arg所在位置置为复制给e;
}
} else
{ // 如果当前r指向字符不为 '?', 将r指向字符置为 '',
arg = 0;
*r = 0; // r处设为;
}
c = bp;//将bp赋值给c;
/*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/
if (c[0] == 0x20){ //判断c中的字符内容是否为空格;若为空格
c[0]='.'; //将.和放入c数组中;
c[1]='';
}
/*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/
if(c[0] == '') strcat(c,"."); //若 c中为则将.链接在c后;
if (c && !stat(c, &stbuf)) //通过文件名c获取文件信息,并保存在stbuf中
//返回值: 执行成功则返回0,失败返回-1,错误代码存于errno
{
if (S_ISDIR(stbuf.st_mode))//判断结果是否为特定的值
{
char * end = c + strlen(c); //end指向c的末尾;
strcat(c, "/index.html"); //将/index.html加到c后,后面追加;
if (!stat(c, &stbuf)) //通过文件名c获取文件信息,并保存在stbuf中 ;成功返回0;
{
DoHTML(f, c); //对html文件进行处理;
}
else
{
*end = ''; //将end指向;
DoDir(f,c); //若c中没有"/index.html" 则跳到目录处理目录代码处去执行;
}
}
else if (!strcmp(r - 4, ".gif")) //判断r中的后四个字符,即判断文件类型;
DoGif(f,c); //若是 gif格式的文件则跳转到DoGif对其进行处理;
else if (!strcmp(r - 4, ".jpg") || !strcmp(r - 5, ".jpeg"))
DoJpeg(f,c); //若是 jpg或jpeg格式的文件则跳转到DoJpeg对其进行处理;
else if (!strcmp(r - 4, ".htm") || !strcmp(r - 5, ".html"))
DoHTML(f,c); //若是 htm格式的文件则跳转到DoHTML处对其进行处理;
else
DoText(f,c);//若是 纯文本格式的文件则跳转到DoText对其进行处理
}
else{
PrintHeader(f,'h'); //发送h类型的http协议数据头
alarm(TIMEOUT);
fprintf(f, "<html><head><title>404 File Not Found</title></head>n"); //打印出错信息
fprintf(f, "<body>The requested URL was not found on this server</body></html>n");
alarm(0);
}
return 0;
}
void sigalrm(int signo) //定时器终止时发送给进程的信号;
{
/* got an alarm, exit & recycle */
exit(0);
}
int HandleConnect(int fd)
{
FILE *f;//定义文件流FILE结构体指针用来表示与客户连接的文件流指针;
char buf[160]; //定义缓冲区buf用来存放客户端的请求命令;
char buf1[160]; //定义缓冲区buf用来存放客户端的各字段信息;
f = fdopen(fd,"a+"); //以文件描述符的形式打开文件; a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
if (!f) {//若文件打开失败则打印出错信息;
fprintf(stderr, "httpd: Unable to open httpd input fd, error %dn", errno);
alarm(TIMEOUT); // 闹钟函数成功则返回上一个闹钟时间的剩余时间,否则返回0。 出错返回-1
close(fd);//关闭文件描述符;
alarm(0); //将闹钟时间清0;
return 0;
}
setbuf(f, 0); //将关闭缓冲区;
alarm(TIMEOUT); //启用闹钟;
if (!fgets(buf, 150, f)) { //直接通过f读取150个字符放入以buf为起始地址中,不成功时返回0则打印出错信息;否则fgets成功返回函数指针打印buf的内容;
fprintf(stderr, "httpd: Error reading connection, error %dn", errno);
fclose(f); //关闭文件描述符;
alarm(0);
return 0;
}
#ifdef DEBUG
printf("buf = '%s'n", buf); //打印客户机发出的请求命令;
#endif
alarm(0); //将闹钟时间清0;
referrer[0] = '';//初始化referrer数组;
content_length = -1; //将信息长度初始化为-1;
alarm(TIMEOUT); //设置定时器;
//read other line to parse Rrferrer and content_length infomation
while (fgets(buf1, 150, f) && (strlen(buf1) > 2)) { //直接通过f读取150个字符放入以buf1为起始地址的空间中;
alarm(TIMEOUT);
#ifdef DEBUG
printf("Got buf1 '%s'n", buf1); //打印buf1中的信息;
#endif
if (!strncasecmp(buf1, "Referer:", 8)) { //将buf1中的前八个字符与字符串Referer:若相等则将将指针指向buf1中的Referer:之后;
char * c = buf1+8;
while (isspace(*c)) //判断c处是否为空格若为空格则c指向下一个字符;
c++;
strcpy(referrer, c); //将c所指的内存单元的内容复制到referrer数组中;
}
else if (!strncasecmp(buf1, "Referrer:", 9)) { //将buf1中的前九个字符与字符串Referrer:若相等则将将指针指向buf1中的Referrer:之后;
char * c = buf1+8;
char * c = buf1+9;
while (isspace(*c)) //判断c处是否���空格若为空格则c指向下一个字符;
c++;
strcpy(referrer, c); //将c所指的内存单元的内容复制到referrer数组中;
}
else if (!strncasecmp(buf1, "Content-length:", 15)) { )) { //将buf1中的前15个字符与字符串Content-length:若相等则将将指针指向buf1中的Content-length:之后;
content_length = atoi(buf1+15); //atoi类型转换将buf1中的内容转换为整型赋值给content_length;
}
}
alarm(0);
if (ferror(f)) { //错误信息输出;
fprintf(stderr, "http: Error continuing reading connection, error %dn", errno);
fclose(f);
return 0;
}
ParseReq(f, buf); //解析客户请求函数;
alarm(TIMEOUT); //打开计时器;
fflush(f); //刷新流;
fclose(f); //关闭文件流;
alarm(0);
return 1;
}
void* key(void* data)
{
int c;
for(;;){
c=getchar(); //从键盘输入一个字符
if(c == 'q' || c == 'Q'){
KEY_QUIT=1;
exit(10); //若输入q则退出程序;
break;
}
}
}
int main(int argc, char *argv[])
{
int fd, s; //定义套接字文件描述符作为客户机和服务器之间的通道;
int len;
volatile int true = 1; //定义volatile类型的变量用来作为指向缓冲区的指针变量;
struct sockaddr_in ec;
struct sockaddr_in server_sockaddr; //定义结构体变量;
pthread_t th_key;//定义线程号;
void * retval; //用来存储被等待线程的返回值。
signal(SIGCHLD, SIG_IGN); //忽略信号量;
signal(SIGPIPE, SIG_IGN);
signal(SIGALRM, sigalrm); //设置时钟信号的对应动作;
chroot(HTTPD_DOCUMENT_ROOT); //改变根目录;在makefile文件中指定;
printf("starting httpd...n"); //打印启用服务器程序信息;
printf("press q to quit.n");
// chdir("/");
if (argc > 1 && !strcmp(argv[1], "-i")) {// 若argv【1】等于-i strcmp返回0 并且 argc大于1 执行if下的语句快即关闭文件描述符;
/* I'm running from inetd, handle the request on stdin */
fclose(stderr);
HandleConnect(0); //向HandleConnect函数传入0文件描述符即标准输入;
exit(0);
}
if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { //若获取套接字出错则将错误信息输出到标准设备;
perror("Unable to obtain network");
exit(1);
}
if((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&true, //此函数用于设置套接口,若成功返回0,否则返回错误
sizeof(true))) == -1) {
perror("setsockopt failed"); //输出错误信息;
exit(1);
}
server_sockaddr.sin_family = AF_INET; //设置ip地址类型;
server_sockaddr.sin_port = htons(SERVER_PORT); //设置网络端口;
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY表示本地任意ip;
if(bind(s, (struct sockaddr *)&server_sockaddr, //将所监听的端口号与服务器的地址、端口绑定;
sizeof(server_sockaddr)) == -1) {
perror("Unable to bind socket");//若绑定失败则打印出错信息;
exit(1);
}
if(listen(s, 8*3) == -1) { //listen()声明服务器处于监听状态,并且最多允许有24个客户端处于连接待状态;
perror("Unable to listen");
exit(4);
}
pthread_create(&th_key, NULL, key, 0); //创建线程;
/* Wait until producer and consumer finish. */
printf("wait for connection.n"); //打印服务器等待链接信息;
while (1) {
len = sizeof(ec);//ec结构体变量的长度;
if((fd = accept(s, (void *)&ec, &len)) == -1) { //接受客户机的请求,与客户机建立链接;
exit(5);
close(s);
}
HandleConnect(fd); //处理链接函数调用fd 为客户连接文件描述符;;
}
pthread_join(th_key, &retval); //以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。成功返回0;该语句不会执行到;
}
代码流程图:
实验过程中遇到的问题以及解决方案:
Make编译问题及解决方案
问题:我们将07_httpd文件夹全部拷贝进了bc中,文件夹中拥有Makefile文件,按照实验指导书中使用make编译时,出现错误,程序无法编译。
解决:我们在07_httpd所在的ws文件夹中新建了一个bin文件夹,最终成功编译。
实验总结:
本次实验是我们在以实验一开发环境为背景的情况下最后一个实验,有了之前的基础,这次的比较简单易行。比起实验四面对make编译不成功时,我们的一筹莫展,现在我们掌握之前的经验方法,可以编译出来了。这次实验是简单嵌入式WEB服务器实验,通过对实验指导书的预习,我们明白了在ARM开发板上开发一个简单的WEB的过程,以及在ARM开发板上SOCKET网络编程和Linux下signal()函数的使用。我们通过这几次实验收获了很多实战经验,相信以后会用上它们。