北京電子科技學院(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實驗箱一個 |
配置實驗環境:同實驗一。若不能熟練掌握,可點選如下連結檢視詳細步驟:
http://www.cnblogs.com/zhengwei0712/p/4960130.html <<exp1實驗報告
一、實驗步驟
1.閱讀了解源代碼
進入/07_httpd目錄,使用編輯器閱讀了解源代碼。
2.編譯應用程式
運作make産生可執行檔案httpd。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5SMxQTMwcjM1cTMtMzMygDN0MjMyMjMxETNxAjMtgjN4QDN38CXxETNxAjMvwFO2gDN0czLcd2bsJ2Lc12bj5ycn9Gbi52YuUTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
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 <[email protected]>
* Copyright (C) 2000 Lineo, Inc. (www.lineo.com)
* Copyright (c) 1997-1999 D. Jeff Dionne <[email protected]>
* Copyright (c) 1998 Kenneth Albanowski <[email protected]>
* Copyright (c) 1999 Nick Brok <[email protected]>
*
* 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
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, "<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;該語句不會執行到;
}
三、exp4學習摘要
同實驗四,當make出現問題時,可嘗試打開makefile檔案檢視編譯程式的所在路徑是否正确,是否能夠成功連結,若不能,則應該加以修改。這次的路徑修改無法類比實驗四中的改動,多次嘗試之後隻能求助老師,在老師的修改下,終于能夠make通過。詳情見上圖。這個技能是這次實驗最大的收獲。今後實驗中,在使用make指令時如果出現類似錯誤,最先想到的方法就是修改Makefile檔案中的路徑。如果實在無法修改号,就使用gcc編譯,避開make操作。
四、實驗體會
經過這次實驗,我們發現,在做實驗之前,好好看看代碼,這樣就可以在運作的過程中及早地發現錯誤去修改路徑。通過看代碼,學習了很多知識,如一些接口的設計,通過看資料流圖,了解了用戶端請求擷取伺服器資源的過程。在實踐中,不僅體驗到了利用試驗箱實作一個簡單WEB伺服器的過程,也體驗到了代碼結構的神奇。
另外,實驗中應該用于嘗試,學會積累經驗,學習總結,這樣才能在今後的學習中更加高效地解決類似地問題,融會貫通,學科内交叉學習,這樣就能夠舉一反三,更好地學習。
這次實驗,我和鄭偉兩人也是一人操作,一人指導,比對。提高效率,減少錯誤。在遇到問題的時候一人查找解決方法,一人嘗試。經過四次實驗,我們已經有了很高的默契。能夠明白自己的工作,配合搭檔。也知道搭檔的缺陷所在,能夠應急補缺。
五、搭檔部落格傳送門
http://www.cnblogs.com/zhengwei0712/20135322鄭偉