北京電子科技學院(BESTI) | ||
實 驗 報 告 | ||
課程:信息安全系統設計基礎 | 班級:1353 | 姓名:鄭偉、吳子怡 |
學號:2013532二、20135313 | 指導教師: 婁嘉鵬 | 實驗日期:2015年11月17日 |
必修/選修:必修 | 實驗序號:exp5 | 實驗時間:15:30-18:00 |
實驗名稱: exp5_通信協議設計 | ||
實驗內容 | 學習使用socket進行通信編程的過程,瞭解一個實際的網絡通信應用程序總體設計,閱讀HTTP協議的相關內容,學習幾個重要的網絡的使用方法。html 讀懂HTTPD.C源代碼。在此基礎上增長一些其餘功能。在PC計算機上使用瀏覽器測試嵌入式WEB服務器的功能。 |
|
實驗目的與要求 | 1.掌握在ARM開發板實現一個簡單WEB服務器的過程。 | |
2.學習在ARM開發板上的SOCKET網絡編程。 | ||
3.學習Linux下的signal()函數的使用 | ||
實驗器材 | 一、Lenovo計算機一臺 | |
二、ARM實驗箱一個 |
http://www.cnblogs.com/zhengwei0712/p/4960130.html <<exp1實驗報告linux
進入/07_httpd目錄,使用編輯器閱讀理解源代碼。c++
運行make產生可執行文件httpd。redis
使用NFS服務方式將HTTPD下載到開發板上,並拷貝測試用的網頁進行調試。編程
在臺式機的瀏覽器中輸入http://192.168.0.234(234爲實驗板的IP地址),觀察在客戶機的瀏覽器中的連接請求結果和在開發板上的服務器的打印信息。數組
在進行make編譯的時候,出現問題:瀏覽器
通過研究,發現是Makefile中的路徑有問題,通過以下圖的修改便可:安全
附:代碼分析bash
httpd.c代碼分析 / * 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 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;該語句不會執行到; }
同實驗四,當make出現問題時,可嘗試打開makefile文件查看編譯程序的所在路徑是否正確,是否可以成功連接,若不能,則應該加以修改。此次的路徑修改沒法類比實驗四中的改動,屢次嘗試以後只能求助老師,在老師的修改下,終於可以make經過。詳情見上圖。這個技能是此次實驗最大的收穫。從此實驗中,在使用make命令時若是出現相似錯誤,最早想到的方法就是修改Makefile文件中的路徑。若是實在沒法修改號,就使用gcc編譯,避開make操做。服務器
通過此次實驗,咱們發現,在作實驗以前,好好看看代碼,這樣就能夠在運行的過程當中及早地發現錯誤去修改路徑。經過看代碼,學習了不少知識,如一些接口的設計,經過看數據流圖,瞭解了客戶端請求獲取服務器資源的過程。在實踐中,不只體驗到了利用試驗箱實現一個簡單WEB服務器的過程,也體驗到了代碼結構的神奇。
另外,實驗中應該用於嘗試,學會積累經驗,學習總結,這樣才能在從此的學習中更加高效地解決相似地問題,融會貫通,學科內交叉學習,這樣就可以觸類旁通,更好地學習。
此次實驗,我和鄭偉兩人也是一人操做,一人指導,比對。提升效率,減小錯誤。在遇到問題的時候一人查找解決方法,一人嘗試。通過四次實驗,咱們已經有了很高的默契。可以明白本身的工做,配合搭檔。也知道搭檔的缺陷所在,可以應急補缺。
http://www.cnblogs.com/zhengwei0712/20135322鄭偉