学号20145332 《信息安全系统设计基础》实验五 简单嵌入式WEB服务器实验

时间:2022-11-20 15:42:10

实验目的

掌握在 ARM 开发板实现一个简单 WEB 服务器的过程。
学习在 ARM 开发板上的 SOCKET 网络编程。
学习 Linux 下的 signal()函数的使用。

实验内容

学习使用 socket 进行通讯编程的过程,了解一个实际的网络通讯应用程序整体设计, 阅读 HTTP 协议的相关内容,学习几个重要的网络函数的使用方法。 读懂 HTTPD.C 源代码。在此基础上增加一些其他功能。在 PC 计算机上使用浏览器测试 嵌入式 WEB 服务器的功能。

连接arm开发板

将 arm 开发板电源线接好,保持开发板开关处于闭合状态。再分别将串口线、并口线和网线与 pc 机连接好。

建立超级终端

运行WindowsXP中:开始→所有程序→附件→通讯→超级终端(HyperTerminal)。
新建一个通信终端。要求输入区号、电话号码等信息请随意输入,为所建超级终端取名为 arm,随意为其选一个图标。
在属性对话框中,将波特率设为 115200,数据位设为 8,无奇偶校验,停止位为 1,无数据流控制。将其另存为在桌面。

打开超级终端

打开超级终端,打开 arm 机电源开关。等待一分钟,arm 机的信息会显示在超级终端的窗口中。输入“boot”后会引导kernel,启动linux系统。在应用程序目录下,可以通过“ls”查看。

学号20145332 《信息安全系统设计基础》实验五 简单嵌入式WEB服务器实验

实验步骤

1、阅读理解源码

进入/arm2410cl/exp/basic/07_httpd目录,使用 vi 编辑器或其他编辑器阅读理解源代码

2、编译应用程序

运行 make 产生可执行文件httpd

但是当我们运行make时发现出现问题,无法运行成功(忘记截图),于是查找上一届的博客发现问题是没有修改makefile:

学号20145332 《信息安全系统设计基础》实验五 简单嵌入式WEB服务器实验

make编译成功。

3、下载调试

使用 NFS 服务方式将 HTTPD 下载到开发板上,并拷贝测试用的网页进行调试。

学号20145332 《信息安全系统设计基础》实验五 简单嵌入式WEB服务器实验

4、本机测试

在台式机的浏览器中输入 http://192.168.0.121,观察在客户机的浏览器中的连接请求结果和在开发板上的服务器的打印信息。

学号20145332 《信息安全系统设计基础》实验五 简单嵌入式WEB服务器实验

实验代码分析:

 / * 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; #define 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;该语句不会执行到;
  
}