北京电子科技学院(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。

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

  

三、exp4学习摘要

同实验四,当make出现问题时,可尝试打开makefile文件查看编译程序的所在路径是否正确,是否能够成功链接,若不能,则应该加以修改。这次的路径修改无法类比实验四中的改动,多次尝试之后只能求助老师,在老师的修改下,终于能够make通过。详情见上图。这个技能是这次实验最大的收获。今后实验中,在使用make命令时如果出现类似错误,最先想到的方法就是修改Makefile文件中的路径。如果实在无法修改号,就使用gcc编译,避开make操作。

四、实验体会

经过这次实验,我们发现,在做实验之前,好好看看代码,这样就可以在运行的过程中及早地发现错误去修改路径。通过看代码,学习了很多知识,如一些接口的设计,通过看数据流图,了解了客户端请求获取服务器资源的过程。在实践中,不仅体验到了利用试验箱实现一个简单WEB服务器的过程,也体验到了代码结构的神奇。

另外,实验中应该用于尝试,学会积累经验,学习总结,这样才能在今后的学习中更加高效地解决类似地问题,融会贯通,学科内交叉学习,这样就能够举一反三,更好地学习。

这次实验,我和郑伟两人也是一人操作,一人指导,比对。提高效率,减少错误。在遇到问题的时候一人查找解决方法,一人尝试。经过四次实验,我们已经有了很高的默契。能够明白自己的工作,配合搭档。也知道搭档的缺陷所在,能够应急补缺。

五、搭档博客传送门

http://www.cnblogs.com/zhengwei0712/20135322郑伟

信息安全系统设计基础exp_5的更多相关文章

  1. 20145213《信息安全系统设计基础》实验一 Linux开发环境的配置

    北京电子科技学院(BESTI) 实 验 报 告 课程:信息安全系统设计基础 班级:1452 姓名: 黄亚奇 祁玮 学号:20145213 20145222 成绩: 指导教师:娄嘉鹏 实验日期:2016 ...

  2. 20145215&20145307《信息安全系统设计基础》实验二 固件设计

    20145215&20145307<信息安全系统设计基础>实验二 固件设计 实验目的与要求 了解多线程程序设计的基本原理,学习 pthread 库函数的使用. 了解在 linux ...

  3. 20145215&20145307《信息安全系统设计基础》实验五 网络通信

    小组成员:20145215卢肖明.20145307陈俊达 实验报告链接:信息安全系统设计基础--实验五实验报告

  4. 20145223《信息安全系统设计基础》 GDB调试汇编堆栈过程分析

    20145223<信息安全系统设计基础> GDB调试汇编堆栈过程分析 分析的c语言源码 生成汇编代码--命令:gcc -g example.c -o example -m32 进入gdb调 ...

  5. 20145216 20145330 《信息安全系统设计基础》 实验五 简单嵌入式WEB 服务器实验

    20145216 20145330 <信息安全系统设计基础> 实验五 简单嵌入式WEB 服务器实验 实验报告封面 实验步骤 1.阅读理解源码 进入/arm2410cl/exp/basic/ ...

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

    20145208<信息安全系统设计基础>实验五 简单嵌入式WEB 服务器实验 20145208<信息安全系统设计基础>实验五 简单嵌入式WEB 服务器实验

  7. 2016-2017-1 《信息安全系统设计基础》 学生博客及Git@OSC 链接

    2016-2017-1 <信息安全系统设计基础> 学生博客及Git@OSC 链接 博客 1452 20145201李子璇 20145202马 超 20145203盖泽双 20145204张 ...

  8. 20145215&20145307信息安全系统设计基础实验报告

    20145215&20145307信息安全系统设计基础实验报告 PART1 一.实验原理 交叉编译,简单地说,就是在一个平台上生成另一个平台上的可执行代码.同一个体系结构可以运行不同的操作系统 ...

  9. 20145315&20145307《信息安全系统设计基础》实验五

    20145315&20145307<信息安全系统设计基础>实验五 北京电子科技学院(BESTI) 实 验 报 告 课程:信息安全系统设计基础 班级:1453 1452 姓名:陈俊达 ...

随机推荐

  1. 使用hibernate时出现 org.hibernate.HibernateException: Unable to get the default Bean Validation factory

    hibernate 在使用junit测试报错: org.hibernate.HibernateException: Unable to get the default Bean Validation ...

  2. Effective Java 23 Don't use raw types in new code

    Generic types advantage Parameterized type can provide erroneous check in compile time. // Parameter ...

  3. JavaScript Patterns 2.9 Coding Conventions

    It’s important to establish and follow coding conventions—they make your code consistent, predictabl ...

  4. java web项目自动部署到Tomcat的原因

    关于eclipse中MAVEN WEB工程中编译问题 这几天是被java的环境搞疯了,我先是搭了一个spring+springmvc+mybatis的工程,在家里跑了一下,没有问题,把工程带到公司里用 ...

  5. PHP模拟发送POST请求之二、用PHP和JS处理URL信息

    明白了HTTP请求的头信息后,我们还需要对请求地址有所了解.再者,HTTP GET请求是靠URL实现的,所以了解URL的构造,处理URL的重要性不言而喻. 在PHP中我们用parse_url()函数来 ...

  6. shell 脚本关键字&符号

    shell概念 "shell"既是一种解释型编程语言,也是一个这种编程语言的解释器的名字 shell是解释型语言,就是解释器会一条一条的翻译每一条语句并执行,对比之下,C语言是编译 ...

  7. JavaScript生成GUID的算法

    全局唯一标识符(GUID,Globally Unique Identifier)也称作 UUID(Universally Unique IDentifier) . GUID是一种由算法生成的二进制长度 ...

  8. jsp 单机和双击事件

    公司要求给一个按钮加一个双击和单机事件  整理了一下   做个笔记 HTML  单机和双击事件 <a href="javascript:void(0)" ondblclick ...

  9. MAVEN整理(乘国庆还有时间,停下来整理一下)

    昨天写下了这篇博客(http://www.cnblogs.com/hzmark/p/131003Release.html),今天有时间,就这里一下第一篇内容. 换工作公司开发平台: WINDOWS+V ...

  10. 机器学习:logistic回归

    逻辑回归是一个形式是Y=1/(1+E(-X))的函数,它的特点是: 1, 当X>0,随着X增大,Y很快的接近1: 2,当x<0,随着X的减小,Y很快的接近0: 3,当X=0时,Y=1/2. ...