北京电子科技学院(BESTI) | ||
实 验 报 告 | ||
课程:信息安全系统设计基础 | 班级:1353 | 姓名:郑伟、吴子怡 |
学号:20135322、20135313 | 指导教师: 娄嘉鹏 | 实验日期:2015年11月17日 |
必修/选修:必修 | 实验序号:exp5 | 实验时间:15:30-18:00 |
实验名称: exp5_通讯协议设计 | ||
实验内容 | 学习使用socket进行通讯编程的过程,了解一个实际的网络通讯应用程序整体设计,阅读HTTP协议的相关内容,学习几个重要的网络的使用方法。 读懂HTTPD.C源代码。在此基础上增加一些其他功能。在PC计算机上使用浏览器测试嵌入式WEB服务器的功能。 | |
实验目的与要求 | 1.掌握在ARM开发板实现一个简单WEB服务器的过程。 | |
2.学习在ARM开发板上的SOCKET网络编程。 | ||
3.学习Linux下的signal()函数的使用 | ||
实验器材 | 1、Lenovo计算机一台 | |
2、ARM实验箱一个 |
配置实验环境:同实验一。若不能熟练掌握,可点击如下链接查看详细步骤:
<<exp1实验报告
一、实验步骤
1.阅读理解源代码
进入/07_httpd目录,使用编辑器阅读理解源代码。
2.编译应用程序
运行make产生可执行文件httpd。
3.下载调试
使用NFS服务方式将HTTPD下载到开发板上,并拷贝测试用的网页进行调试。
4.本机测试
在台式机的浏览器中输入http://192.168.0.234(234为实验板的IP地址),观察在客户机的浏览器中的链接请求结果和在开发板上的服务器的打印信息。
二、遇到的问题及解决方法
在进行make编译的时候,出现问题:
经过研究,发现是Makefile中的路径有问题,经过如下图的修改即可:
附:代码分析
httpd.c代码分析 / * httpd.c: A very simple http server * Copyfight (C) 2003 Zou jian guo* Copyright (C) 2000 Lineo, Inc. (www.lineo.com) * Copyright (c) 1997-1999 D. Jeff Dionne * Copyright (c) 1998 Kenneth Albanowski * Copyright (c) 1999 Nick Brok * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 int PrintHeader(FILE *f, int content_type) //发送HTTP协议数据头{ alarm(TIMEOUT); fprintf(f,"HTTP/1.0 200 OKn"); //服务器回应http协议数据头的状态行;发送请求成功; switch (content_type) { case 't':fprintf(f,"Content-type: text/plainn"); //发送纯文本文件信息;break; case 'g':fprintf(f,"Content-type: image/gifn"); //发送gif格式图片信息;break; case 'j':fprintf(f,"Content-type: image/jpegn"); //发送gpeg格式图片信息;break; case 'h':fprintf(f,"Content-type: text/htmln"); //发送html信息;break; } fprintf(f,"Server: uClinux-httpd 0.2.2n"); //发送服务器版本信息; fprintf(f,"Expires: 0n"); //发送文件永不过期信息; fprintf(f,"n"); //打印换行符; alarm(0); return(0);} int DoJpeg(FILE *f, char *name) //对jpeg格式的文件进行处理;{ char *buf; FILE * infile; int count; if (!(infile = fopen(name, "r"))) { //通过文件名打开一个文件,只读属性;alarm(TIMEOUT);fprintf(stderr, "Unable to open JPEG file %s, %dn", name, errno);fflush(f);alarm(0);return -1; } PrintHeader(f,'j');//发送j类型的http协议数据头信息; copy(infile,f); /* prints the page */ alarm(TIMEOUT); fclose(infile); alarm(0); return 0;} int DoGif(FILE *f, char *name) //对gif格式的文件进行处理;{ 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'); //发送g类型的http协议数据头信息 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'); //发送h类型的http协议数据头信息 alarm(TIMEOUT); fprintf(f, " Index of %s
nn",name); alarm(0); if (name[strlen(name)-1] != '/') { //若名字的后面没有/则默认加上 /;strcat(name, "/"); } while(dirent = readdir(dir)) { //读取目录;alarm(TIMEOUT); fprintf(f, " 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, "404 File Not Found n"); //打印出错信息fprintf(f, "The requested URL was not found on this servern"); 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 infomationwhile (fgets(buf1, 150, f) && (strlen(buf1) > 2)) { //直接通过f读取150个字符放入以buf1为起始地址的空间中; alarm(TIMEOUT);#ifdef DEBUGprintf("Got buf1 '%s'n", buf1); //打印buf1中的信息;#endifif (!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;该语句不会执行到; }
三、exp4学习摘要
同实验四,当make出现问题时,可尝试打开makefile文件查看编译程序的所在路径是否正确,是否能够成功链接,若不能,则应该加以修改。这次的路径修改无法类比实验四中的改动,多次尝试之后只能求助老师,在老师的修改下,终于能够make通过。详情见上图。这个技能是这次实验最大的收获。今后实验中,在使用make命令时如果出现类似错误,最先想到的方法就是修改Makefile文件中的路径。如果实在无法修改号,就使用gcc编译,避开make操作。
四、实验体会
经过这次实验,我们发现,在做实验之前,好好看看代码,这样就可以在运行的过程中及早地发现错误去修改路径。通过看代码,学习了很多知识,如一些接口的设计,通过看数据流图,了解了客户端请求获取服务器资源的过程。在实践中,不仅体验到了利用试验箱实现一个简单WEB服务器的过程,也体验到了代码结构的神奇。
另外,实验中应该用于尝试,学会积累经验,学习总结,这样才能在今后的学习中更加高效地解决类似地问题,融会贯通,学科内交叉学习,这样就能够举一反三,更好地学习。
这次实验,我和郑伟两人也是一人操作,一人指导,比对。提高效率,减少错误。在遇到问题的时候一人查找解决方法,一人尝试。经过四次实验,我们已经有了很高的默契。能够明白自己的工作,配合搭档。也知道搭档的缺陷所在,能够应急补缺。
五、搭档博客传送门
20135322郑伟