实验五:通讯协议设计
北京电子科技学院(BESTI)
实 验 报 告
课程:信息安全系统设计基础 班级:1453班
姓名:彭垚 学号:20145317
成绩: 指导教师:娄嘉鹏 实验日期:2016.11.16
实验密级: 预习程度: 实验时间10:00-12:00
仪器组次: 必修/选修:必修 实验序号:五
实验名称:实验五:通讯协议设计
实验目的与要求:
1. 掌握在ARM开发板实现一个简单的WEB服务器的过程。
2. 学习在ARM开发板上的SOCKET网络编程。
3. 学习Linux下的signal()函数的使用。
实验仪器:
名称 |
型号 |
数量 |
PC机 |
Lenovo |
1 |
嵌入式实验平台 |
UP-TECH S2410 |
1 |
实验内容、步骤与体会:
一、实验内容与步骤
1、阅读理解源码
进入/arm2410cl/exp/basic/07_httpd 目录,使用 vi 编辑器或其他编辑器阅读理解源代码
1 httpd.c代码分析 2 3 / * httpd.c: A very simple http server 4 * Copyfight (C) 2003 Zou jian guo <ah_zou@163.com> 5 * Copyright (C) 2000 Lineo, Inc. (www.lineo.com) 6 * Copyright (c) 1997-1999 D. Jeff Dionne <jeff@lineo.ca> 7 * Copyright (c) 1998 Kenneth Albanowski <kjahds@kjahds.com> 8 * Copyright (c) 1999 Nick Brok <nick@nbrok.iaehv.nl> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 */ 16 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <fcntl.h> 20 #include <string.h> 21 #include <sys/types.h> 22 #include <sys/socket.h> 23 #include <netinet/in.h> 24 #include <errno.h> 25 #include <sys/stat.h> 26 #include <dirent.h> 27 #include <signal.h> 28 #include <unistd.h> 29 #include <ctype.h> 30 #include "pthread.h" 31 32 #define DEBUG 33 34 int KEY_QUIT=0; 35 int TIMEOUT=30; //设置闹钟秒数; 36 37 #ifndef O_BINARY 38 #define O_BINARY 0 39 #endif 40 41 char referrer[128]; 42 int content_length; 43 44 #define SERVER_PORT 80 45 46 int PrintHeader(FILE *f, int content_type) //发送HTTP协议数据头 47 { 48 alarm(TIMEOUT); 49 fprintf(f,"HTTP/1.0 200 OKn"); //服务器回应http协议数据头的状态行;发送请求成功; 50 switch (content_type) 51 { 52 case 't': 53 fprintf(f,"Content-type: text/plainn"); //发送纯文本文件信息; 54 break; 55 case 'g': 56 fprintf(f,"Content-type: image/gifn"); //发送gif格式图片信息; 57 break; 58 case 'j': 59 fprintf(f,"Content-type: image/jpegn"); //发送gpeg格式图片信息; 60 break; 61 case 'h': 62 fprintf(f,"Content-type: text/htmln"); //发送html信息; 63 break; 64 } 65 fprintf(f,"Server: uClinux-httpd 0.2.2n"); //发送服务器版本信息; 66 fprintf(f,"Expires: 0n"); //发送文件永不过期信息; 67 fprintf(f,"n"); //打印换行符; 68 alarm(0); 69 return(0); 70 } 71 72 int DoJpeg(FILE *f, char *name) //对jpeg格式的文件进行处理; 73 { 74 char *buf; 75 FILE * infile; 76 int count; 77 78 if (!(infile = fopen(name, "r"))) { //通过文件名打开一个文件,只读属性; 79 alarm(TIMEOUT); 80 fprintf(stderr, "Unable to open JPEG file %s, %dn", name, errno); 81 fflush(f); 82 alarm(0); 83 return -1; 84 } 85 86 PrintHeader(f,'j');//发送j类型的http协议数据头信息; 87 88 89 copy(infile,f); /* prints the page */ 90 91 alarm(TIMEOUT); 92 fclose(infile); 93 alarm(0); 94 95 return 0; 96 } 97 98 int DoGif(FILE *f, char *name) //对gif格式的文件进行处理; 99 { 100 char *buf; 101 FILE * infile; 102 int count; 103 104 if (!(infile = fopen(name, "r"))) { //通过文件名打开一个文件,只读属性; 105 alarm(TIMEOUT); 106 fprintf(stderr, "Unable to open GIF file %s, %dn", name, errno); 107 fflush(f); 108 alarm(0); 109 return -1; 110 } 111 112 PrintHeader(f,'g'); //发送g类型的http协议数据头信息 113 114 copy(infile,f); /* prints the page */ 115 116 alarm(TIMEOUT); 117 fclose(infile); 118 alarm(0); 119 120 return 0; 121 } 122 123 int DoDir(FILE *f, char *name) //对目录进行处理; 124 { 125 char *buf; 126 DIR * dir; 127 struct dirent * dirent; //dirent不仅仅指向目录,还指向目录中的具体文件,dirent结构体存储的关于文件的信息很少,所以dirent起着一个索引的作用 128 129 if ((dir = opendir(name))== 0) { //打开一个目录; 130 fprintf(stderr, "Unable to open directory %s, %dn", name, errno); 131 fflush(f); 132 return -1; 133 } 134 135 PrintHeader(f,'h'); //发送h类型的http协议数据头信息 136 137 alarm(TIMEOUT); 138 fprintf(f, "<H1>Index of %s</H1>nn",name); 139 alarm(0); 140 141 if (name[strlen(name)-1] != '/') { //若名字的后面没有/则默认加上 /; 142 strcat(name, "/"); 143 } 144 145 while(dirent = readdir(dir)) { //读取目录; 146 alarm(TIMEOUT); 147 148 fprintf(f, "<p><a href="/%s%s">%s</p>n", name, dirent->d_name, dirent->d_name); 149 alarm(0); //发送目录信息; 150 } 151 152 closedir(dir); 153 return 0; 154 } 155 156 int DoHTML(FILE *f, char *name) 157 { 158 char *buf; 159 FILE *infile; //定义文件流指针 160 int count; 161 char * dir = 0; 162 163 if (!(infile = fopen(name,"r"))) { //通过文件名打开一个文件,只读属性; 164 alarm(TIMEOUT); 165 fprintf(stderr, "Unable to open HTML file %s, %dn", name, errno); //打印打开文件失败信息; 166 fflush(f); 167 alarm(0); 168 return -1; 169 } 170 171 PrintHeader(f,'h'); //发送http协议数据报;f表示客户连接的文件流指针用于写入http协议数据头信息; 172 copy(infile,f); /* prints the page */ //将打开的文件内容通过发送回客户端; 173 174 alarm(TIMEOUT); 175 fclose(infile); 176 alarm(0); 177 178 return 0; 179 } 180 181 int DoText(FILE *f, char *name) //纯文本文件的处理; 182 { 183 char *buf; 184 FILE *infile; //定义文件流指针; 185 int count; 186 187 if (!(infile = fopen(name,"r"))) { //通过文件名打开一个文件,只读属性 188 alarm(TIMEOUT); 189 fprintf(stderr, "Unable to open text file %s, %dn", name, errno); 190 fflush(f); 191 alarm(0); 192 return -1; 193 } 194 195 PrintHeader(f,'t'); //发送t类型的http协议数据头信息; 196 copy(infile,f); /* prints the page */ 197 198 alarm(TIMEOUT); 199 fclose(infile); 200 alarm(0); 201 202 return 0; 203 } 204 205 int ParseReq(FILE *f, char *r) 206 { 207 char *bp; //定义指针bp; 208 struct stat stbuf; 209 char * arg; //参数指针; 210 char * c; 211 int e; 212 int raw; 213 214 #ifdef DEBUG 215 printf("req is '%s'n", r); //打印请求命令;例如:GET /img/baidu_sylogo1.gif HTTP/1.1rn 216 #endif 217 218 while(*(++r) != ' '); /*skip non-white space*/ //判断buf中的内容是否为空跳过非空白; 219 while(isspace(*r)) //判断r所在位置的字符是否为空格若为空格则r指向下一个字符; 220 r++; 221 222 while (*r == '/') //判断r所在位置的字符是否为/若为空格则r指向下一个字符; 223 r++; 224 bp = r; //将r所指向的内容赋值给bp bp指向/之后的内容;img/baidu_sylogo1.gif HTTP/1.1rn 225 226 while(*r && (*(r) != ' ') && (*(r) != '?')) 227 r++;//当r不为空,并求 r不为?时r指向下一个字符 228 229 #ifdef DEBUG 230 printf("bp='%s' %x, r='%s' n", bp, *bp,r); //打印 r和bp的值; 231 #endif 232 233 if (*r == '?') //判断 r是否为 ?若为?则执行以下语句; 234 { 235 char * e; //定义指针变量; 236 *r = 0; //将r所在位置处的字符设为; 的ASCII码值是0 237 arg = r+1; //arg指向下一个参数; 238 if (e = strchr(arg,' ')) 239 { 240 *e = ''; //如果arg为空则将arg所在位置置为复制给e; 241 } 242 } else 243 { // 如果当前r指向字符不为 '?', 将r指向字符置为 '', 244 arg = 0; 245 *r = 0; // r处设为; 246 } 247 248 c = bp;//将bp赋值给c; 249 250 /*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/ 251 if (c[0] == 0x20){ //判断c中的字符内容是否为空格;若为空格 252 c[0]='.'; //将.和放入c数组中; 253 c[1]=''; 254 } 255 /*zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz*/ 256 if(c[0] == '') strcat(c,"."); //若 c中为则将.链接在c后; 257 258 if (c && !stat(c, &stbuf)) //通过文件名c获取文件信息,并保存在stbuf中 259 //返回值: 执行成功则返回0,失败返回-1,错误代码存于errno 260 261 { 262 if (S_ISDIR(stbuf.st_mode))//判断结果是否为特定的值 263 { 264 char * end = c + strlen(c); //end指向c的末尾; 265 strcat(c, "/index.html"); //将/index.html加到c后,后面追加; 266 if (!stat(c, &stbuf)) //通过文件名c获取文件信息,并保存在stbuf中 ;成功返回0; 267 { 268 DoHTML(f, c); //对html文件进行处理; 269 } 270 else 271 { 272 *end = ''; //将end指向; 273 DoDir(f,c); //若c中没有"/index.html" 则跳到目录处理目录代码处去执行; 274 } 275 } 276 else if (!strcmp(r - 4, ".gif")) //判断r中的后四个字符,即判断文件类型; 277 DoGif(f,c); //若是 gif格式的文件则跳转到DoGif对其进行处理; 278 else if (!strcmp(r - 4, ".jpg") || !strcmp(r - 5, ".jpeg")) 279 DoJpeg(f,c); //若是 jpg或jpeg格式的文件则跳转到DoJpeg对其进行处理; 280 else if (!strcmp(r - 4, ".htm") || !strcmp(r - 5, ".html")) 281 DoHTML(f,c); //若是 htm格式的文件则跳转到DoHTML处对其进行处理; 282 else 283 DoText(f,c);//若是 纯文本格式的文件则跳转到DoText对其进行处理 284 } 285 else{ 286 PrintHeader(f,'h'); //发送h类型的http协议数据头 287 alarm(TIMEOUT); 288 fprintf(f, "<html><head><title>404 File Not Found</title></head>n"); //打印出错信息 289 fprintf(f, "<body>The requested URL was not found on this server</body></html>n"); 290 alarm(0); 291 } 292 return 0; 293 } 294 295 void sigalrm(int signo) //定时器终止时发送给进程的信号; 296 { 297 /* got an alarm, exit & recycle */ 298 exit(0); 299 } 300 301 int HandleConnect(int fd) 302 { 303 FILE *f;//定义文件流FILE结构体指针用来表示与客户连接的文件流指针; 304 305 char buf[160]; //定义缓冲区buf用来存放客户端的请求命令; 306 char buf1[160]; //定义缓冲区buf用来存放客户端的各字段信息; 307 308 f = fdopen(fd,"a+"); //以文件描述符的形式打开文件; a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 309 if (!f) {//若文件打开失败则打印出错信息; 310 fprintf(stderr, "httpd: Unable to open httpd input fd, error %dn", errno); 311 alarm(TIMEOUT); // 闹钟函数成功则返回上一个闹钟时间的剩余时间,否则返回0。 出错返回-1 312 close(fd);//关闭文件描述符; 313 alarm(0); //将闹钟时间清0; 314 return 0; 315 } 316 setbuf(f, 0); //将关闭缓冲区; 317 318 alarm(TIMEOUT); //启用闹钟; 319 320 if (!fgets(buf, 150, f)) { //直接通过f读取150个字符放入以buf为起始地址中,不成功时返回0则打印出错信息;否则fgets成功返回函数指针打印buf的内容; 321 fprintf(stderr, "httpd: Error reading connection, error %dn", errno); 322 fclose(f); //关闭文件描述符; 323 alarm(0); 324 return 0; 325 } 326 #ifdef DEBUG 327 printf("buf = '%s'n", buf); //打印客户机发出的请求命令; 328 #endif 329 330 alarm(0); //将闹钟时间清0; 331 332 referrer[0] = '';//初始化referrer数组; 333 content_length = -1; //将信息长度初始化为-1; 334 335 alarm(TIMEOUT); //设置定时器; 336 //read other line to parse Rrferrer and content_length infomation 337 while (fgets(buf1, 150, f) && (strlen(buf1) > 2)) { //直接通过f读取150个字符放入以buf1为起始地址的空间中; 338 alarm(TIMEOUT); 339 #ifdef DEBUG 340 printf("Got buf1 '%s'n", buf1); //打印buf1中的信息; 341 #endif 342 if (!strncasecmp(buf1, "Referer:", 8)) { //将buf1中的前八个字符与字符串Referer:若相等则将将指针指向buf1中的Referer:之后; 343 char * c = buf1+8; 344 while (isspace(*c)) //判断c处是否为空格若为空格则c指向下一个字符; 345 c++; 346 strcpy(referrer, c); //将c所指的内存单元的内容复制到referrer数组中; 347 } 348 else if (!strncasecmp(buf1, "Referrer:", 9)) { //将buf1中的前九个字符与字符串Referrer:若相等则将将指针指向buf1中的Referrer:之后; 349 char * c = buf1+8; 350 char * c = buf1+9; 351 while (isspace(*c)) //判断c处是否���空格若为空格则c指向下一个字符; 352 c++; 353 strcpy(referrer, c); //将c所指的内存单元的内容复制到referrer数组中; 354 } 355 else if (!strncasecmp(buf1, "Content-length:", 15)) { )) { //将buf1中的前15个字符与字符串Content-length:若相等则将将指针指向buf1中的Content-length:之后; 356 357 content_length = atoi(buf1+15); //atoi类型转换将buf1中的内容转换为整型赋值给content_length; 358 } 359 } 360 alarm(0); 361 362 if (ferror(f)) { //错误信息输出; 363 fprintf(stderr, "http: Error continuing reading connection, error %dn", errno); 364 fclose(f); 365 return 0; 366 } 367 368 ParseReq(f, buf); //解析客户请求函数; 369 370 alarm(TIMEOUT); //打开计时器; 371 fflush(f); //刷新流; 372 fclose(f); //关闭文件流; 373 alarm(0); 374 return 1; 375 } 376 377 378 379 void* key(void* data) 380 { 381 int c; 382 for(;;){ 383 c=getchar(); //从键盘输入一个字符 384 if(c == 'q' || c == 'Q'){ 385 KEY_QUIT=1; 386 exit(10); //若输入q则退出程序; 387 break; 388 } 389 } 390 391 } 392 393 int main(int argc, char *argv[]) 394 { 395 int fd, s; //定义套接字文件描述符作为客户机和服务器之间的通道; 396 int len; 397 volatile int true = 1; //定义volatile类型的变量用来作为指向缓冲区的指针变量; 398 struct sockaddr_in ec; 399 struct sockaddr_in server_sockaddr; //定义结构体变量; 400 401 pthread_t th_key;//定义线程号; 402 void * retval; //用来存储被等待线程的返回值。 403 404 405 signal(SIGCHLD, SIG_IGN); //忽略信号量; 406 signal(SIGPIPE, SIG_IGN); 407 signal(SIGALRM, sigalrm); //设置时钟信号的对应动作; 408 409 chroot(HTTPD_DOCUMENT_ROOT); //改变根目录;在makefile文件中指定; 410 printf("starting httpd...n"); //打印启用服务器程序信息; 411 printf("press q to quit.n"); 412 // chdir("/"); 413 414 if (argc > 1 && !strcmp(argv[1], "-i")) {// 若argv【1】等于-i strcmp返回0 并且 argc大于1 执行if下的语句快即关闭文件描述符; 415 /* I'm running from inetd, handle the request on stdin */ 416 fclose(stderr); 417 HandleConnect(0); //向HandleConnect函数传入0文件描述符即标准输入; 418 exit(0); 419 } 420 421 if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { //若获取套接字出错则将错误信息输出到标准设备; 422 perror("Unable to obtain network"); 423 exit(1); 424 } 425 426 if((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&true, //此函数用于设置套接口,若成功返回0,否则返回错误 427 sizeof(true))) == -1) { 428 perror("setsockopt failed"); //输出错误信息; 429 exit(1); 430 } 431 432 server_sockaddr.sin_family = AF_INET; //设置ip地址类型; 433 server_sockaddr.sin_port = htons(SERVER_PORT); //设置网络端口; 434 server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY表示本地任意ip; 435 436 if(bind(s, (struct sockaddr *)&server_sockaddr, //将所监听的端口号与服务器的地址、端口绑定; 437 438 sizeof(server_sockaddr)) == -1) { 439 perror("Unable to bind socket");//若绑定失败则打印出错信息; 440 exit(1); 441 } 442 443 if(listen(s, 8*3) == -1) { //listen()声明服务器处于监听状态,并且最多允许有24个客户端处于连接待状态; 444 perror("Unable to listen"); 445 exit(4); 446 } 447 448 449 pthread_create(&th_key, NULL, key, 0); //创建线程; 450 /* Wait until producer and consumer finish. */ 451 printf("wait for connection.n"); //打印服务器等待链接信息; 452 while (1) { 453 454 len = sizeof(ec);//ec结构体变量的长度; 455 if((fd = accept(s, (void *)&ec, &len)) == -1) { //接受客户机的请求,与客户机建立链接; 456 exit(5); 457 close(s); 458 } 459 HandleConnect(fd); //处理链接函数调用fd 为客户连接文件描述符;; 460 461 } 462 pthread_join(th_key, &retval); //以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。成功返回0;该语句不会执行到; 463 464 }
2、编译应用程序
gcc编译,运行 make 产生可执行文件 httpd
3、下载调试
使用 NFS 服务方式将 HTTPD 下载到开发板上,并拷贝测试用的网页进行调试,本例中用的是 index 测试网页。下载结果如图:
4、本机测试
在台式机的浏览器中输入 http://192.168.0.111(111 为 UP-CUP S2410 实验板的 IP地址),观察在客户机的浏览器中的连接请求结果和在开发板上的服务器的打印信息。实验结果如下: