小型服务器_公司小型存服务器 - CSDN
精华内容
参与话题
  • 不想买域名买空间怎么办? 手把手教你如何让自己的电脑成为小型服务器,让外网的电脑可以访问你的网址(项目) 利用花生壳搭建小型服务器
  • 基于HTTP实现的小型web服务器

    千次阅读 2018-09-12 22:26:09
    主要流程为:服务器获得请求–>响应并处理请求–>返回结果。 完整的HTTP过渡到TCP实现客户端与服务器的交互过程 1.当客户端执行网络请求的时候,从url中解析出url的主机名,并将主机地址转换成ip 2....

    主要流程为:服务器获得请求–>响应并处理请求–>返回结果。

    完整的HTTP过渡到TCP实现客户端与服务器的交互过程
    1.当客户端执行网络请求的时候,从url中解析出url的主机名,并将主机地址转换成ip
    2.从url解析出服务器的所用端口号
    3.客户端用TCP连接服务器
    4.连接成功后 获取输出流,将数据以报文的形式传递给服务器
    5.当服务器接收到数据之后,进行判断和解析码,并回应一条响应报文
    6.客户端从输入流中获取报文,然后进行解析
    7.关闭网络连接

    HTTP的特点


    1、支持客户端/服务器的模式
    2、简单快捷 客户向服务器发送请求服务时,只需要传送请求方法和路径,每种方法规定了客户与服务器联系的类型的不同,由于HTTP协议简单,使得HTTP服务器的规模小,因此通信速度很快.
    3、灵活  允许传送各种类型的数据,数据类型用Content-Type标记
    4、无连接:限制每次连接只处理一个请求,服务器处理完客户的请求,收到客户的应答后,随即断开连接,这种方式节省传输时间,请求应答机制会断开
    5、无状态  HTTP协议是无状态的协议,即对事务处理没有记忆功能

    关于URL


    即统一资源定位符,每个网页都对应一个URL地址(俗称网址),具有全球唯一性。它包含的信息指出文件的位置以及浏览器应该怎么处理它。 一个完整的URL包括协议类型、主机类型、路径和文件名。 
    http协议的URL格式: http: //host[:port][abs_path] ,http表示使用http协议来进行资源定位;host是主机域名;port是端口号,一般有默认的;abs_path代表资源的路径。 
    这里我主要介绍项目中涉及的URL的两种格式—URL带参数和不带参数的。

    HTTP的请求与响应格式


    响应报头中的状态码和状态码描述,例如:当请求的资源不存在时,会收到“404 NotFound”的页面,404就是状态码,“NotFound”就是状态码描述,即请求的文件不存在。

    1.实现支持GET和POST方法的小型http服务器
    GET方法:如果GET方法只是简单的请求一份资源,而不传递参数的话则由服务器直接将资源返回即可。如果GET方法的url中带有参数的话,则就要使用CGI模式进行处理。 
    POST方法:POST方法要使用CGI模式进行处理,POST的参数在消息中文中出现。
    使用GET方法使用的是带参数的url,传递的参数会使用?连接在资源后面POST方法使用的是不带参数的url 它的参数是通过http请求正文传递给服务器的,http的请求和响应模式


    响应报头中的状态码和状态码描述,举个例子,当请求的资源不存在的时,会收到"404 NotFound"的页面,404就是状态码,

    "NotFound"就是状态码描述,既请求的文件不存在

    状态码表示响应类型

    1×× 保留

    2×× 表示请求成功地接收

    3×× 为完成请求客户需进一步细化请求

    4×× 客户错误

    5×× 服务器错误 

    响应头的信息包括:服务程序名,通知客户请求的URL需要认证,请求的资源何时能使用

     

    HTTP服务器实现框架


    1.面向链接:http协议是基于TCP通信协议,因此实现web服务器的第一步至少要能实现两个主机不同进程之间的TCP通信,并且需要解决高并发问题所以这里推荐使用多线程服务器来构建,每次创建出来一个新线程出来的时候将线程分离,然后让这个新线程去处理这个请求.
    2.分析出请求行: 当服务器接收到请求后,首先知道的是HTTP服务器版本号,和请求方法。web服务器是要支持cgi模式: 请求的方法不同,cgi可能也不同,我们实现的知识比较简单单的处理GET和POST方法
    3.判断cgi模式
    //    1)当我们判断出来是GET请求时候,并且url中没有参数的话,就用非CGI模式,非CGI模式处理//起来比较简单,首先解析出来请求路径,判断是不是合法资源,如果是就直接返回这个资源。
    //    2)当是CGI模式处理请求的时候,我们要fork一个子进程,对子进程exec替换CGI程序,这个
    //过程中使用pipe进行父子进程之间的通信。所有需要的参数在exec之前,都将这些参数导出为环境变//量,就算exec的话,子进程还是能够通过环境变量获取所需的参数。
    4.响应客户端:此时我们已经知道了方法以及是否为cgi模式,然后开始读取URL,这里有一个细节非cgi模式 请求参数会跟在url当中,如果cgi模式的话,参数在消息正文中,然后我们读取到路径,判断路径当中资源是否存在,如果存在判断这个资源是一个目录,普通文件还是一个可执行程序

    这里分情况分析
        1)如果是cgi模式,直接进入cgi内部运行;只要是POST方法就需要支持cgi,直接进入cgi函数内部运行.
        2)如果是非cgi模式时一定是GET方法并且没有参数,此时进入wwwroot()函数内部即可,该函数会将所请求的资源以html的格式返回给浏览器.

     

    接下来是解释运行cgi模式,首先服务器要从浏览器读取参数,然后创建出来一个子进程去执行cgi部分的可执行资源,父进程通过环境变量的方式传递给子进程,子进程运行完成之后呢,将结果交给父进程,父进程再将数据输出给浏览器. 所以父进程在这个例子当中就向是一个中介,只进行参数和结果的转交实际上并不会执行任何资源,因此将子进程的输入输出文件描述符重定向,就可以让子进程直接与浏览器"联系".



    父进程做的事情

    1. 1.创建两个管道,并关闭相应的文件描述符 
    2. 2.POST方法:继续读取数据,直到读完POST的参数部分GET方法:直接从子进程读取结果
    3. 3.将数据和方法全部交给子进程后等待子进程的结果

    子进程做的事情

    1. 1.关闭管道适当的文件描述符
    2. 2.对标准输入输出进行重定向
    3. 3.通过环境变量传递参数
    4. 4.进行exec程序替换

     

    一次完整的http请求的流程

    项目文件

    目录: 
    python:爬取小说和招聘信息的代码

    sql_connect:存放mysql需要的lib库   连接mysql程序文件
    wwwroot:web服务器工作的根目录,包含各种资源页面(例如默认的index.html页面,差错处理的404页面),以及执行cgi的可执行程序

    文件: 

    makefile:编译整个项目
    httpd.h:服务器的方法声明 
    httpd.c:方法实现 
    main.c:服务器的主逻辑

     

    数据库中的操作
    没有索引的时候会进行整个表的扫描
    添加索引 索引会形成一颗二叉树  利用二分查找的方法。

    遇到的问题:
    1.运行cgi后不能显示在页面上,便尝试着写一个简单的CGI程序看自己的CGI是否真的能跑完,结果CGI没有问题,后来尝试用telnet工具模拟一次http,看看是否真的收到了网页回复,后来分析结果,对比之后发现返回的东西不能显示,之后给html加了一些p标签,便可以显示出来。
    2. 经常会出现类似于:undefined reference to `sql_connecter::~sql_connecter()'的问题,文件编译的路径不对。
    3.调用数据库的数据显示到html文件中出现乱码的问题,最初以为自己编码格式有问题,后来发现是数据库编码格式和浏览器的编码格式不一样,数据库使用utf8编码方式,浏览器是GB2312,后来浏览器使用utf8编码格式,能够正常显示。

    4.代码中会需要int *和 void*的转换,用到C++强制转换形如  int*  data = reinterpret_cast<int*>(arg);

    5.本地环回测试ok,Linux下的浏览器测试也可以,但不能接外部的浏览器访问(没有设置桥接模式) 在外部浏览器测试的话千万别忘记关闭防火墙 

    6.运行程序时会提醒挺行下载页面,因为在响应报头有问题中。而浏览器对于不能识别或解析的实体,都会提醒用户下载。

    展开全文
  • 小型服务器

    2020-07-21 09:56:40
    本地服务器是一个轻量级的小型服务器EasyWebserver,可以直接把需要的文件放在服务器的主目录就ok,不需要添加其它PHP代码,也不用安装WAMP软件,更不需要做其它各种繁琐配置, 使用起来极其简单,非常推荐使用
  • 小型web服务器

    2018-10-01 13:26:35
    小型web服务器 一、项目平台: centos 6.5 二、实现功能 :网站的后台程序 三、基本要求: 1.基于HTTP/1.0版本的web服务器,客户端可以通过GET、POST方法进行资源请求 2.服务器将客户请求的资源以html页面的...

                                       小型web服务器

    一、项目平台: centos 6.5

    二、实现功能 :网站的后台程序

    三、基本要求:

    1.基于HTTP/1.0版本的web服务器,客户端可以通过GET、POST方法进行资源请求

    2.服务器将客户请求的资源以html页面的形式呈现,并且能够进行差错处理。

    3.服务器能运行简单的cgi

    四、项目的背景知识

    1.了解HTTP协议

        是超文本传输,是应用层的协议,他是基于TCP协议的。他的工作过程:客户端通过浏览器向服务器发送请求,浏览器将请求的资源在传给浏览器,在关闭连接。

    2.了解url

      是统一资源定位符,也是我们俗称的网址。

    http://www.example.jp:80/dir/index.html?uid=1

    如上一个url包括协议方案名,服务器的地址和端口号,请求资源的路径,查询字符串。

    在这里的查询字符串是根据请求的方法来确定有还是没有。若为GET方法就会有查询字符串。若为POST方法就没有,他是通过http请求报文中的body发送的。

    3.http请求及响应的格式

     五、项目的基本思路

      1.通过socket来建立通信

         a) 创建socket

         b)  绑定地址端口

         c) 监听

         d) 进入事件循环

      2.服务器接收浏览器的请求并且进行解析

            a)解析请求的首行,获取方法和url(在这里只考虑GET、POST的请求方法)

            b)再去解析出url获取path和query_string

           c)  读取并解析header(这里只获取到content-length)其余信息丢弃

           d)body暂时不进行解析,根据后面的情况再去判断是否需要解析

      3.根据解析好的HTTP请求来进行计算

         a ) 静态页面:服务器上的某个固定位置的html文件,文件内容若没有人去修改,就会一直不变,浏览器的页面就一样。

                也就是进入到一个非cgi模式

               (1)拼接目录

               /index.html---->不是一个绝对路径,只是和服务器上某个路径匹配的

               是一个相对于HTTP的根目录--->允许对外访问的文件集中到某个目录下面就是HTTP的根目录

                若url_path是一个文件,直接进行拼接

               若url_path是一个目录,就会默认构造一个文件路径,尝试取目录下的index.html文件

               (2)打开文件

                   读取文件中的内容,根据内容构造http响应,文件中的内容就是响应中body的部分

         b)动态页面:服务器会根据用户输入参数来决定生成什么样的页面。

                进入到一个cgi模式

          (1)HTTP服务器需要创建子进程

          (2)子进程进行程序替换,替换成磁盘上某个可执行程序

    父进程执行父进程的相关逻辑:

        1.将body写入管道

        2.父进程尝试读取子进程构造的结果

        3.父进程构造HTTP响应,写回客户端

     子进程传递给父进程的信息

         1.设置环境变量(方法、query_string、content_length)

         2.重定向

         3.根据url_path构造路径

         4.进行替换

    六、测试

      用一个简单版的计算器来测试CGI

            1.基于CGI协议获取到需要的参数

            2.根据业务逻辑(计算器相关的逻辑),进行计算

            3.把结果构造成HTML写回到标准输出中

    代码实现:

    1.创建socket连接,采用多线程来进行处理清楚

    #define SIZE (1024*10)
     17 typedef struct HttpRequest
     18 {
     19     char first_line[SIZE];
     20     char *method;
     21     char *url;
     22     char *url_path;
     23     char *query_string;
     24     int content_length;
     25 }HttpRequest;
     
    //线程的入口函数
     void* ThreadEntry(void* arg)
     {
         int32_t new_sock=(int32_t)arg;
         HandlerRequest(new_sock);
         return NULL;
     }
     typedef struct sockaddr_in sockaddr_in;
     typedef struct sockaddr sockaddr;
     //服务器的入口函数
     void HttpServerStart(const char* ip,short port)
     {
         //0.忽略信号
         signal(SIGCHLD,SIG_IGN);
         //1.创建socket
        int listen_sock=socket(AF_INET,SOCK_STREAM,0);
         //失败原因:文件描述符达到上限
        if(listen_sock<0)
        {
             perror("socket");
            return;
         }
         //2.绑定地址端口
         sockaddr_in addr;
         addr.sin_family=AF_INET;
         addr.sin_port=htons(port);
         addr.sin_addr.s_addr=inet_addr(ip);
         int ret=bind(listen_sock,(sockaddr*)&addr,sizeof(addr));
         if(ret<0)
             //失败的原因:该端口可能被别的进程绑定
         {
             perror("bind");
             return;
         }
         //3.监听
        ret=listen(listen_sock,5);
         if(ret<0)
         {
             perror("listen");
             return;
         }
     printf("HttpServerStart ok!\n");
         //4.进入事件循环
         while(1)
         {
             //printf("进入事件循环\n");
             sockaddr_in peer;
             socklen_t len = sizeof(peer);
            int32_t new_sock= accept(listen_sock,(sockaddr*)&peer,&len);
             if(new_sock<0)
            {
                perror("accept");
                 continue;
             }
             printf("accept\n");
             //6.创建线程,由新线程完成具体的HTTP服务器后续的操作
             //线程创建块,且占用资源少,切换块
            //1.不可以传递指针,是因为此时可能new_sock生命周期结束,才去调用ThreadEntry,变成野指针
            //2.那么试着加上static,也是不可以的,因为用static修饰的只有一份。
      //3.那么每产生一个new_fd都申请一块空间,在函数调用完成之后再去释放,这样可以是可以,但是不好。
             int32_t new_sock= accept(listen_sock,(sockaddr*)&peer,&len);
            if(new_sock<0)
             {
                perror("accept");
                 continue;
             }
             printf("accept\n");
             //6.创建线程,由新线程完成具体的HTTP服务器后续的操作
             //线程创建块,且占用资源少,切换块
           //1.不可以传递指针,是因为此时可能new_sock生命周期结束,才去调用ThreadEntry,变成野指针
            //2.那么试着加上static,也是不可以的,因为用static修饰的只有一份。
             //3.那么每产生一个new_fd都申请一块空间,在函数调用完成之后再去释放,这样可以是可以,但是不好。
             //4.因此采用传值的方式,进入函数,就会进行拷贝,在栈上保存一份属于自己的。函数一旦退出,会自动释放
             pthread_t tid;
             pthread_create(&tid,NULL,ThreadEntry,(void*)new_sock);
            //采用detach,不关注结果,这样才能保证accpet快速被调用
             pthread_detach(tid);
         }
     }
    //通过命令行参数,把需要绑定的ip和port传进来
     int main(int argc,char* argv[])
     {
        if(argc!=3)
        {
             printf("Usage: ./http_server ip port\n");
             return -1;
         }
         printf("输入正确\n");
         //http服务器启动入口函数
         HttpServerStart(argv[1],atoi(argv[2]));
         return 0;
     }

    2. 基本的处理流程

    //请求的处理
     void HandlerRequest(int new_sock)
     {
         printf("Rev Request\n");
         int err_code=200;
         HttpRequest req;
         memset(&req,0,sizeof(req));
         //1.解析请求(按照http协议的格式进行解析)
         // a)按行读socket,读出HTTP请求的首行
        printf("解析首行之前\n");
         if(ReadLine(new_sock,req.first_line)<0)
        {
             printf("ReadLine first_line failed\n");
             err_code=404;
             //构造404响应的代码
             goto END;
         }
         printf("first_line=%s\n",req.first_line);
         //b)解析首行,获取到方法和url
         if(ParseFirstLine(req.first_line,&req.method,&req.url)<0)
         {
             printf("ParseFirstLine failed\n");
             err_code=404;
             //此时对错误作统一处理使用404这个错误码
            //构造404响应的代码
             goto END;
         }
         printf("method=%s url=%s\n",req.method,req.url);
        // c)解析url,获取url_path和query_string
        if(ParseUrl(req.url,&req.url_path,&req.query_string)<0)
         {
             printf("ParseUrl failed\n");
             err_code=404;
             //构造404响应的代码
             goto END;
         }
         printf("method=%s,url_path=%s,query_string=%s\n",req.method,req.url_path,req.query_string);
         // d)读取并解析header部分,这里只保留Content_Lenght,
         // 其他的header内容直接丢弃
         if(ParseHeadler(new_sock,&req.content_length)<0)
        {
             printf("ParseHeadler failed\n");
             err_code=404;
            //构造404响应的代码
             goto END;
         }
         //body暂时不解析,交给后面的逻辑根据方法来决定是否需要解析body
         //2.根据收到的请求进行计算,生成响应,把响应写回客户端
         if(strcmp(req.method,"GET")==0 && req.query_string==NULL)
         {
            // a)静态页面:如果是GET方法且没有query_string
             err_code=HandlerStaticFile(&req,new_sock);
         }
         // b)动态页面:若方法为GET,有query_string
         else if (strcmp(req.method,"GET")==0 && req.query_string!=NULL)
         {
             // b)动态页面:若方法为GET,有query_string
             err_code=HandlerCGI(&req,new_sock);
         }
         else if(strcmp(req.method,"POST")==0 )
         {
            //  c)动态页面: 方法为POST
            }
         else
        {
             printf("method not suport! method=%s\n",req.method);
            err_code=404;
             goto END;
        }
     END:
         if(err_code!=200)
         {
             Handler404(new_sock);
         }
         //在这里响应写完之后,服务器主动断开连接,进入到一个TIME_WAIT的状态。
         //由于服务器可能在短时间内接受的大量的连接,服务器出现了大量的TIME_WAIT
         //可能会导致下一次连接不上。因此需要设置setsocketopt REUSEADDR重用TIME_WAIT状态的连接
     
         close(new_sock);
     }
    

    3.按行读取

    //从socket中读取一行
      int ReadLine(int new_sock,char line[])
      {
         //按行读取数据,浏览器发送数据中的行分割符可能不一样
         //\r  \n    \r\n
         //将不同的行分割符都转化为\n
         //1.从socket中读取字符,一次读一个
        char c='\0';
         int output_index=0;//描述当前读到字符应该放到缓冲区的哪个下标上
          while(1)
         {
              ssize_t read_size=recv(new_sock,&c,1,0);//从sock中读数据
             if(read_size<=0)
             {
                 return -1;
             }
             //2.判断当前读到的字符是不是 \r
              if(c=='\r')
              {
                 //3.如果c是\r,尝试读取下一个
                //MSG_PEEK只是看一下里面的内容,并不从缓冲区中删除
                 recv(new_sock,&c,1,MSG_PEEK);
                  if(c=='\n'){
                      //4.如果下一个字符是\n,说明行分隔符就是\r\n 
                      //就把行分隔符改为\n
                      //此处在进行一次recv,是为了把缓冲区中的\n拿出来
                      recv(new_sock,&c,1,0);
                 }else
                  {
                     // 5.若下一个字符是其他的字符,说明行分隔符就是\r
                      // 就把\r修改为\n
                      c='\n';
                  }
              }
              //经过上面的if,不论行分隔符是\r还是\r\n,c都已经变成了\n
              //6.如果是其他的字符,就直接放到缓冲区中
              line[output_index++]=c;
              //7.再来判断当前字符是不是\n,如果当前字符是\n
              //说明这一行读完了,就退出循环,
             if(c=='\n')  //将这个if写到这里,读出的行后面就包含\n
              {
                  break;
              }
          }
          //这次读了多少个字节返回去了
          return output_index;
      }
    

    4.解析首行

      //字符串分割
      int Split(char first_line[],const char* split_char,char* output[])
      {
          char* tmp=NULL;
          int output_index=0;
          //不可以用strtok这个函数,因为这个函数存在线程不安全的问题。其内部是用static
          //进行了修饰,也就是在静态全局区中,变量只存了一份,存在竞争
          //因此用strtok_r这个函数进行替换。其内部没有用static进行修饰,因此需要手动传参
          //来记录上次切分的位置
          char* p=strtok_r(first_line,split_char,&tmp);
          while(p!=NULL)
          {
              output[output_index++]=p;
              p=strtok_r(NULL,split_char,&tmp);
          }
          return output_index;
      } 
      //解析首行 解析出需要的url和方法
      //当前只考虑不带域名的简单情况
      //GET /index.html?a=10 HTTP/1.1
     //按空格进行字符串切分,切分出三部分
      int ParseFirstLine(char first_line[],char** p_method,char** p_url)
      {
          char* tok[100]={NULL};
         //tok_size 描述字符串切分出的部分有几个
         int tok_size=Split(first_line," ",tok);
         if(tok_size!=3)
         {
             printf("first_line Split failed ! n=%d",tok_size);
             return -1;
         }
         //将切割的结果返回
         *p_method=tok[0];
        *p_url=tok[1];
         return 0;
     }

       5.解析url

    //解析url
     //url形如
     // /index.html?a=10&b=20
     // /index.thml
     // 先不考虑带有域名的
     int ParseUrl(char url[],char** p_url_path,char**p_query_string)
     {
         *p_url_path=url;
         char*p=url;
         for(;*p!='\0';++p)
       {
            if(*p=='?')
            {
                //若找到?说明url中带有query_string
                 //将?设为\0
                 *p='\0';
                 //p+1就是query_string的位置
                 *p_query_string=p+1;
                 return 0;
             }
         }
         //如果循环退出没有找?,那么此时url中不存在query_string
         //就让query_string指向null
         *p_query_string=NULL;
         return 0;
     }

    6.解析头部

     //解析头部
     int ParseHeadler(int new_sock,int* content_length_str)
     {
         //由于header部分是按行组织的数据
         //循环尝试读取header,每次读一行
         while(1)
        {
            char line[SIZE]={0};
             int read_size=ReadLine(new_sock,line);
            if(read_size<0)
             {
                 printf("ReadLine failed!\n");
                 return -1;
             }
            //此处需要考虑比较 Readline的实现细节
             //若Readline返回结果中不包含 \n
            //此代码就需要和空串对比
             if(strcmp(line,"\n")==0)
             {
                 //读到了空行,说明header结束
                 return 0;
             }
             //针对当前读到的行进行判断,看这一行是不是Conten_Length:
             //例如读到了一行形如:
             ///content_length:10\n
             const char* Content_length_str="Content_Length: ";
             if(strncmp(line,Content_length_str,strlen(Content_length_str)==0))
             {
                 *content_length_str=atoi(line+strlen(Content_length_str));
                //此处不可以直接返回,因为要将缓冲区的所有数据都拿出来。防止黏包
            }
         }
     }
    

    7.错误处理

     void Handler404(int new_sock)
     {
         printf("Handler404~~~\n");
         //构造404错误页面 严格按照HTTP响应格式
         const char* first_line="HTTP/1.1 404 Not Fonud\n";
         const char* blank_line="\n";
         //body部分的内容就是HTML
         const char* body="<head> <meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">"
             "</head><body><h1>您的页面去偷偷去玩了!!</h1></body>";
         char header[SIZE]={0};
         sprintf(header,"Content-Length:%lu\n",strlen(body));
     
         send(new_sock,first_line,strlen(first_line),0);
         send(new_sock,header,strlen(header),0);
         send(new_sock,blank_line,strlen(blank_line),0);
         send(new_sock,body,strlen(body),0);
         return ;
     }

    8.静态页面的处理

     int IsDir(const char* file_path)
     {
        struct stat st;
         int ret=stat(file_path,&st);//查看文件属性
        if(ret<0)
         {
             perror("stat");
             return -1;
        }
         if(S_ISDIR(st.st_mode))//此宏可以查看文件类型是否为目录文件
         {
             return 1;
         }
         return 0;
     }
    //获取路径
     void GetFilePath( const char* url_path,char file_path[])
     {
         //根据HTTP服务器的根目录进行拼接
         sprintf(file_path,"./wwwroot%s",url_path);
         //如果用户传的url_path如下:/image
         //假设这个url_path是一个目录,那就要构造一个默认的文件路径,尝试取目录下的/index.html
         //如果用户传的url_path是:/image,实际就为:/image/index.html
         //因此需要判断file_path是普通文件还是目录文件  
         if(IsDir(file_path))
         {
           //file_path可能存在的情况:
            //1./image/
            //2./image
             if(file_path[strlen(file_path)-1]=='/'){
                 strcat(file_path,"index.html");
             }else{
                 strcat(file_path,"/index.html");
             }
         }
     }
     
     int GetFileSize(const char* file_path)
     {
         struct stat st;
         int ret=stat(file_path,&st);
         if(ret<0)
         {
             return 0;
         }
         return st.st_size;
     }
     int WriteStaticFile(const char* file_path,int new_sock)
     {
         //1.打开文件
         //2.读取文件内容
         //3.根据文件内容构造http响应
         //4.将响应内容写到sock中
         int fd=open(file_path,O_RDONLY);
         if(fd<0)
         {
             printf("file open failed! file_path=%s\n",file_path);
            return 404;
         }
         const char* first_line="HTTP/1.1 200 OK\n";
         int size=GetFileSize(file_path);
         char header[SIZE]={0};
         sprintf(header,"Content-Length: %d\n",size);
         const char* blank_line="\n";
         send(new_sock,first_line,strlen(first_line),0);
         send(new_sock,header,strlen(header),0);
         send(new_sock,blank_line,strlen(blank_line),0);
         sendfile(new_sock,fd,NULL,size);
         //关闭文件
         close(fd);
         return 200;
     }
     int HandlerStaticFile(const HttpRequest* req,int new_sock)
     {
         //1.拼接目录(根据url_path构造出当前文件系统上的真实目录)
         //2.打开文件,读取文件内容,根据文件内容构造HTTP响应
         //其中文件的内容就作为HTTP响应中body的内容
         char file_path[SIZE]={0};
         GetFilePath(req->url_path,file_path);
         int err_code=WriteStaticFile(file_path,new_sock);
         return err_code;
    } 

    9.动态页面的处理

     //3.父进程执行父进程的相关逻辑
     int  HandlerCGIFather(const HttpRequest* req,int new_sock,int father_read,int father_write)
     {
         printf("父进程逻辑\n");
         //a) 父进程把HTTP请求的body部分写到管道
             if(strcmp(req->method,"POST")==0){
             int content_length=req->content_length;
             int i=0;
             for(;i<content_length;++i)
             {
                 char c='\0';
                 recv(new_sock,&c,1,0);
                 //printf("ccc\n");
                write(father_write,&c,1);
     
             }
         }
         //b)  父进程尝试取读取子进程构造的结果
         //c) 父进程构造HTTP响应,写回客户端
         const char* first_line="HTTP/1.1 200 OK\n";
         const char* header="Content-Type: text/html\n";
         const char* blank_line="\n";
         send(new_sock,first_line,strlen(first_line),0);
         send(new_sock,header,strlen(header),0);
         send(new_sock,blank_line,strlen(blank_line),0);
         char c='\0';
         while(read(father_read,&c,1)>0){
             //从管道中读数据,如果所有的写段关闭,read将读到EOF,从而返回0
             send(new_sock,&c,1,0);
         }
         //d) 父进程回收子进程
         //进程等待可以,但更简单的是忽略SIGCHLD信号
         return 0; 
     } 
     //子进程执行子进程的相关逻辑
    int  HandlerCGIChild(const HttpRequest* req,int child_read,int child_write){
         // a)设置环境变量 
         char request_method_env[SIZE]={0};
         sprintf(request_method_env,"REQUEST_METHOD=%s",req->method);
         putenv(request_method_env);
         if(strcmp(req->method,"GET")==0)
         {
             char query_string_env[SIZE]={0};
             sprintf(query_string_env,"QUERY_STRING=%s",req->query_string);
             putenv(query_string_env);
         }else {
             char content_length_env[SIZE]={0};
             sprintf(content_length_env,"CONTENT_LENGTH=%d",req->content_length);
             putenv(content_length_env);
         }
         // b)  重定向,把标准输入输出重定向到管道
         dup2(child_read,0);
         dup2(child_write,1);
         // c)  根据url_path构造出CGI路径
         char file_path[SIZE]={0};
         GetFilePath(req->url_path,file_path);
         // d)  进行程序替换(也就进入到了CGI程序内部)
         //l:通过边长参数列表来传输参数  lp:从path中获取  le:手动构造环境变量 
         //v通过数组 vp ve
         execl(file_path,file_path,NULL);
         exit(0);
         return 0;
     }
     int HandlerCGI(const HttpRequest* req,int new_sock)
     {
        //1创建一对匿名管道
          int fd1[2];
          int fd2[2];
          pipe(fd1);
          pipe(fd2);
          int father_read=fd1[0];
          int child_write=fd1[1];
          int child_read =fd2[0];
          int father_write=fd2[1];
          printf("进入动态\n");
          //2创建子进程
          int ret=fork();
          if(ret>0){
              close(child_write);
              close(child_read);
              //3.父进程执行父进程的相关逻辑
              printf("父进程\n");
            HandlerCGIFather(req,new_sock,father_read,father_write);
                close(father_read);
                close(father_write);
          }
          else if(ret==0)
          {
              close(father_read);
              close(father_write);
              printf("子程序\n");
              //4.子进程执行子进程的相关逻辑
               HandlerCGIChild(req,child_read,child_write);
          }
          else{
          perror("fork");
          }
          return 200;
     }

    当写完大体框架时我们先进行验证,此时将动态静态页面全部返回404

    静态页面:

     <html>
       <head>
       <meta http-equiv="content-type" content="text/html;charset=utf-8">
       </head>
       <body>
           <h1>小喵的世界欢迎你~</h1>
           <img src="/image/1.jpg">
       </body>
       </html>
    

     

    动态页面如下:

      <html>
       <form action="/calc/calc">
       a:<br>
       <input type="text" name="a">
       <br>
       b:<br>
       <input type="text" name="b">
       <br><br>
       <input type="submit" value="Submit">
      </form>
      <body background="/calc/image/1.jpg">
      </body>
      </html>
    void GetQueryString(char output[])
       {
           //按照CGI的协议实现此处协议
          //1.先获取方法
          char* method=getenv("REQUEST_METHOD");
          if(method==NULL)
          {
              //没有获取到 环境变量
              fprintf(stderr,"REQUEST_METHOD failed\n");
              return;
          }
          if(strcmp(method,"GET")==0)
          {
             char* query_string=getenv("QUERY_STRING");
              if(query_string==0)
              {
                  fprintf(stderr,"QUERY_STRING failde\n");
                  return;
              }
              strcpy(output,query_string);
          }
          else{
              //POST
         //获取CONTENT_LENGTH
          char* content_length_env=getenv("CONTENT_LENGTH");
         if(content_length_env==NULL)
             {
             fprintf(stderr,"CONTENT_LENGTH filed\n");
             return;
         }
        //根据CONTENT_LENGTH读取body内容
         int content_length=atoi(content_length_env);
          int i=0;
         for(;i<content_length;++i)
         {
              char c='\0';
              read(0,&c,1);
            output[i]=c;
          }
         return;
      }
      }
      int main()
      {
        //1.基于CGI协议获取到需要的参数
        char query_string[SIZE]={0};
        GetQueryString(query_string);
        //2.根据业务逻辑(计算器相关的逻辑),进行计算
        //此时获取到的QUERY_STRING形如:
        //   a=10&b=20
        int a=0;
        int b=0;
        sscanf(query_string,"a=%d&b=%d\n",&a,&b);
        int sum=a+b;
        //3.把结果构造成HTML写回到标准输出中
        printf("<html><h1>sum=%d</h1></html>",sum);
        return 0;
      }
    

     

     

     

    遇到的问题:

    1.乱码问题   是因为编码格式不对

    2. 在进行字符串切割的时候,不能使用strtok,线程不安全

    3.在拼接目录要考虑到客户端请求的是目录还是文件

    4.CGI要在程序替换之前进行重定向

    展开全文
  • 记录一次搭建家用小型服务器的经历 原文链接:https://www.cnblogs.com/blog5277/p/11261049.html 原文作者:博客园--曲高终和寡 *******************如果你看到这一行,说明爬虫在本人还没有发布完成的时候就抓走了...

    记录一次搭建家用小型服务器的经历

    原文链接:https://www.cnblogs.com/blog5277/p/11261049.html

    原文作者:博客园--曲高终和寡

    *******************如果你看到这一行,说明爬虫在本人还没有发布完成的时候就抓走了我的文章,导致内容不完整,请去上述的原文链接查看原文****************

     

    0. 前景提要

    由于一直有个想法,就是搞一套自己的云端开发环境。

     

    0.1 希望不管是在家里,还是公司,还是任何一个地方,甚至是手机、平板。只要能联网,就能连接上自己熟悉的开发环境开始开发。

     

    0.2 由于阿里服务器太贵了(阿里云香港1H2G1M,3年1800+),并且在国内的服务器、网站一定要备案(我玩腻了com域名,在搞花里胡哨的后缀域名,不能备案),就很麻烦(虽然我也不搞法外之事)。而国外的服务器呢,很便宜,但是可用性要持怀疑态度,并且可能经常换服务器(被墙了、IDC跑路了、又发现了一家新的更便宜更好的IDC)等原因,来回配环境、备份迁移数据库,太麻烦了。

     

    所以萌生了一个念头---->组建自己的家用小型服务器

    这样就可以:

     

    0.3 省钱,硬件不坏就可以一直用,哪个硬件坏了更换哪个

     

    0.4 稳定,数据库的数据在自己的硬盘上,写好定时任务定期同步至onedrive和googledrive,稳定性99.99999%

     

    1. 硬件准备

     

    家用小型服务器嘛,我的原则是【小、安静、功耗低、性能强劲】。一开始在这几项里面纠结,我相信有组建家用服务器、NAS、HTPC的人都考虑过下面几款:

    【新创云等超迷你无风扇主机】优点:小,安静,功耗低。缺点:J1900性能不太够(600价位),当小服务器跑项目还行,我还想搭建开发环境就不够了,没有硬盘位没法当NAS,视频解码能力相当差,当HTPC够呛。而I3/I5/I7低压u的价格都要到1000+去了,真的贵,不划算,最后被我排除。(如果没打算搭建开发环境或者当NAS、HTPC,这款非常好)

    【星际蜗牛】优点:价格便宜(普通C款300,皇帝C款450),功耗低,硬盘位多,C款皇帝版13SATA位,双网口可做软路由。缺点:看起来很好,但是问题有点多。电源一般都要换,风扇也要换,换了声音也有点大。体积不小有点占地方。这么添来添去一换下来价格就上去了,并且我其实没有做NAS的需求,硬盘太贵我买不起。。。J1900性能同样不够我用。除了机箱最好,其他的硬件品质都一般般。所以这款也被我PASS了(如果拿来做NAS的,我觉得C款皇帝版是首选,到手后换个靠谱电源扔储物室里完全OK)

    【酷播云2】优点:功耗低,性能比上面俩好(J3455),用料比较扎实。缺点:价格相比上面两个有点贵了(720无硬盘内存),只有两个SATA口(如果组件家用HTPC,小服务器,8T需求以下的NAS,这个首选)。但是这个性能仍然是。。。我感觉有些许欠缺。

    最后我了解到了【ITX主机】,缺点是价格贵,功耗高了一点。优点可太多了:比星际蜗牛、酷播云体积小的多,可扩展性强得多,性能强几倍,安静程度仅次于无风扇的服务器,离开20厘米就听不见任何声音,硬件都是大牌可信赖稳定性高。

     

    组成了这样的配置:

    机箱:迎广肖邦

    散热:乔思伯HP400

    电源:台电150W

    主板:微星B250I PRO

    CPU:I7-7700T (ES版)

    内存:光威8GDDR4 2133+海力士8GDDR4 2133

    硬盘:西部数码240G NVME 

    这一套下来分别收到手总和是花了1650块钱。比上面的三种方案贵了一些,但是上面的方案要么是配不到16G内存,要么是改装配到了,价格不比这个便宜多少了。

    体积小,漂亮,安静,待机功耗不到10W,满载功耗40W,各硬件也基本是二三线牌子,可比上面说的强多了。

    后续扩展性的话,主板电阻短接支持到8代U,U也可以换标压的,内存最大支持32G2400,硬盘还有4个SATA口。

    整体而言非常满意。

    下面附几张偷来的图,机箱是一样的所以拍出来效果也是一样的,大家可以看一下大小

     

     

     

     

    2. 系统准备

     

    这台小服务器,一方面是装数据库当服务器,另一方面也要搭建云开发环境。所以非图形化界面的各种CentOS/Ubuntu Server之类的直接就不考虑,要装带桌面环境的

     

    2.1 【Manjaro】我对Manjaro这个系统可真是又爱又恨。

     

    2.1.1 爱就不谈了,arch系的Linux魅力,想必有所耳闻的都应该知道,AUR,滚动发版,强大的WIKI。

     

    2.1.2 恨的话得谈谈。我目前还只是个Linux小白,后面遇到的问题或许其实有非常简单的解决办法,但是我没法解决:

     

    2.1.2.1 最开始接触Manjaro的时候,我是在Windows上,装了个VMware虚拟机,在虚拟机里面装的Manjaro,然后就遇到了一个问题:不装VMware Tools没法Windows向Manjaro复制粘贴东西,装了VMware Tools,Manjaro没法全屏,窗口就只有600*480,这我谷歌百度都没法解决。

     

    2.1.2.2 Manjaro上装输入法,说实话在Windows上我觉得最好用的输入法是手心,但是在Mac和Linux里就只能选搜狗输入法了,谷歌拼音、小狼豪(RIME)我都不喜欢,没有搜狗拼音顺手。在Manjaro上的搜狗拼音的依赖库是有问题的,需要装很多老版的依赖库,然后软链接过去。可以弄成功,但是真的很麻烦,我这人最讨厌麻烦了,这种无谓的麻烦。

     

    2.1.2.3 在虚拟机里,Manjaro会睡死,没有办法解锁,点什么都没反应,甚至会带着虚拟机睡死,导致任务管理器都没办法强制关闭VMware。

     

    2.1.2.4 上面这些仍然没有浇灭我的热情,我觉得1,3都是虚拟机的BUG,我买了ITX主机以后一定可以解决的。然而上面的硬件都到了,拼好之后装了Manjaro,几天的时间死机了2次(长时间不操作后自动注销,登录进去之后卡在界面,什么都操作不了)【我每次安装都是从Manjaro的官网下的最新iso,非老版/第三方网站下的】,我搭建这小服务器肯定得稳定运行啊。Manjaro被我放弃了。

     

    2.2 【Windows Server、Windows】我本身也是为了学习练手一下Linux环境,这个尽管我最熟悉最喜欢,但是还是要往后稍一稍的,是最后的备选项

     

    2.3 【Ubuntu】之前在虚拟机里一直用的是Ubuntu,用的也习惯了,不美化的话做桌面版太丑了,想换换新的

     

    2.4 【Mint、MX Linux】后续备选(然而我估计要是Deepin遇到问题最后我不用了的话,最有可能还是拐回Ubuntu了,毕竟Linux桌面版实际的老大哥,参照这个下载量:https://snapcraft.io/chromium  ,还有谷歌趋势,其它的还是弟弟,除了丑也没别的缺点了)

     

    2.5 【Deepin】国产之光 之前一直听别人吹这个系统,但是我是嗤之以鼻的。。。我觉得没有商业巨头管理维护(如Red Hat系),没有大型企业大规模实际应用,社区活跃度一般般,这系统怎么能用嘛

    --------------------------------------真香--------------------------------------

    我非常喜欢Deepin的桌面,网上的各种Ubuntu/Manjaro所谓的美化,基本都是给美化成Mac的样子,何必呢???????我喜欢Mac的风格的话我为啥不用Mac???

    我不喜欢Mac,我家现在还有一个2016款15寸MacBook Pro,I7,16G,带Touch Bar,给对象用了。对Mac的不喜欢是一点一点累积起来的,除了全局可用的cmd+w/q,cmd的位置非常合理,用大拇指就可以很轻易的按到以外,对Mac没有任何留恋,反倒是想到Mac就头大。

    而deepin有显然异于Mac OS的界面(原版Manjaro也有),我非常喜欢。

    目前的话,用起来,Ubuntu怎么用Deepin也怎么用,遇到问题了直接搜Ubuntu的解决办法,在Deepin上99%都管用,开箱即用,非常舒服。

    所以以下都是基于Deepin的系统来说的,Ubuntu也可以参考着用。

     

    3 软件准备

     

    3.1 富强,这个在博客园不能多说,要被和谐,以后个人博客开起来了在里面补充。重点是不能用带R的,只能原版的,我也不知道为啥。记得开启系统网络代理那里,手动代理,S0CK转发那里填上127.0.0.1:端口,这里不建议用默认的1080,可能会被占用,改个不常用的就行。Chrome也不用按照网上下那个代理插件了,设置好直接用就行了,至少Deepin里面是这样的。

     

    3.2 一台有公网的服务器提供FRP。如果家庭宽带有公网也可以用,不过部分家庭宽带的公网没有80和443端口。有的有80和443端口,hostloc的老哥说也是有井茶叔叔邀请喝茶的风险,不仅仅是涉及到返洞,抱利,帆强什么的,诸如版权问题,交互性(比如论坛/留言),还有一些自己不经意间就违规的情况。所以保险起见还是不要用家庭宽带的端口放网站了,当然要是只放个数据库走数据库的端口这完全没问题。服务器尽量选择延迟低,带宽大的。国内的服务器,阿里,腾讯之类的,也就1-5M的小水管还特别贵。。。实在是扣啊。。。国外的IDC商自己找,建议找香港>日本>新加坡>台湾>美国西海岸圣何塞等有CN2的机房,只拿来做FRP转发,对机器配置无要求,可以买最低配的VPS,但是不能买虚拟空间。

     

    3.3 FRP,这部分重点说一下,FRP的作用是,将请求到FRP SERVER(FRP服务端)某端口的请求,转发至FRP CLIENT(FRP客户端),不论服务端所在的网络环境是否有公网IP。这样的话放在家里的服务器就可以打开一扇对外的大门,具体原理可以自行搜索。

     

    3.3.1 FRPS(FRP 服务端)配置

     

    在公网服务器上下载安装FRP,以Linux服务器为例,输入:

    arch

    可以查看服务器架构,如果输出x86_64就直接用我下面的代码,如果不是就自己去frp的github https://github.com/fatedier/frp/releases 找包替换地址

    如果提示没有安装wget就安装个wget,自行搜索自己服务器系统+安装wget

    cd /root
    # 下载
    wget --no-check-certificate https://github.com/fatedier/frp/releases/download/v0.27.1/frp_0.27.1_linux_amd64.tar.gz
    # 解压
    tar -xzvf frp_0.27.1_linux_amd64.tar.gz
    # 文件夹名改成 frp,不然目录太长了不方便
    mv frp_0.27.1_linux_amd64 frp
    cd frp
    # 确保 frps 程序具有可执行权限
    chmod +x frps

    运行一下

    ./frps --help

    如果打印出了很多提示信息,说明安装正确了。不正确的话自行确认安装的版本是否正确

    编辑frps.ini,如果没有vim就自行下载,或者用你们习惯的文本编辑方式

    vim frps.ini

    里面的东西都删掉(如果有的话),换成下面的内容,注意【】内的需要替换成你们自己的,连带【】括号一起删掉,其实各种端口,token,都可以改成自己的

    [common]
    bind_port = 7000
    token = 12345678
    dashboard_port = 7500
    dashboard_user = admin
    dashboard_pwd = admin
    vhost_http_port = 10080
    vhost_https_port = 10443
    subdomain_host = 【你自己的域名,好像也可以不设置这一条,在客户端直接连IP也行,不过我有域名就直接这么设置了,以后换服务器也方便】

    编辑/etc/rc.local,在exit 0 前加上下面这句话,开机自启

    nohup /root/frp/frps -c /root/frp/frps.ini &

    如果打开这个文件是空的,那就在文件的最后新起一行输入

    exit 0

    保险起见给文件加上权限

    sudo chmod +755 /etc/rc.local

    测试一下脚本

    sudo /etc/rc.local

    要是打印出来的信息没有报错,配置FRPS端的步骤就完成了,重启一下服务器

     

    3.3.2 FRPC(FRP客户端)配置

     

    根据ITX主机的架构,下载FRP

    cd /root
    # 下载
    wget --no-check-certificate https://github.com/fatedier/frp/releases/download/v0.27.1/frp_0.27.1_linux_amd64.tar.gz
    # 解压
    tar -xzvf frp_0.27.1_linux_amd64.tar.gz
    # 文件夹名改成 frp,不然目录太长了不方便
    mv frp_0.27.1_linux_amd64 frp
    cd frp
    # 确保 frpc 程序具有可执行权限
    chmod +x frpc

    编辑frpc.ini

    [common]
    server_addr = 【你服务器的ip或者解析到该ip的子域名,如:frp.domain.com】
    server_port = 7000
    token = 12345678
    http_proxy =
    
    [vnc]
    type = tcp
    local_ip = 127.0.0.1
    local_port = 5901
    remote_port = 5901

    编辑/etc/rc.local,在exit 0 前加上下面这句话,开机自启

    nohup /root/frp/frpc -c /root/frp/frpc.ini &

    如果打开这个文件是空的,那就在文件的最后新起一行输入

    exit 0

    保险起见给文件加上权限

    sudo chmod +755 /etc/rc.local

    测试一下脚本

    sudo /etc/rc.local

    要是打印出来的信息没有报错,配置FRPC端的步骤就完成了,重启一下本地的机器

     

    3.4 远程连接

     

    这里尝试了几种方案:XRDP,X11VNC,AnyDesk,经过个人的测试,连接效果都不能让我满意,最后用了VNC

     

    3.4.1 本地服务器端

    sudo apt-get install vnc4server

    修改连接密码,最低6位

    vncpasswd

    然后创建启动项

    sudo vim /etc/init.d/vncserver

    在里面输入

    #!/bin/sh
    ### BEGIN INIT INFO
    # Provides:          vncserver
    # Required-Start:    $local_fs
    # Required-Stop:     $local_fs
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 1 6
    # Short-Description: Start/stop vncserver
    ### END INIT INFO
     
    # More details see:
    # http://www.penguintutor.com/linux/vnc
     
    ### Customize this entry
    # Set the USER variable to the name of the user to start vncserver under
    export USER='【你登录的用户名】'
    ### End customization required
     
    eval cd ~$USER
     
    case "$1" in
      start)
        # 启动命令行。此处自定义分辨率、控制台号码或其它参数。
        su $USER -c 'vncserver -geometry 1920x1080 -alwaysshared -httpPort 5901 :1'
        echo "Starting VNC server for $USER "
        ;;
      stop)
        # 终止命令行。此处控制台号码与启动一致。
        su $USER -c 'vncserver -kill :1'
        echo "vncserver stopped"
        ;;
      *)
        echo "Usage: /etc/init.d/vncserver {start|stop}"
        exit 1
        ;;
    esac
    exit 0

    修改权限

    sudo chmod 755 /etc/init.d/vncserver

    添加开机启动项

    sudo update-rc.d vncserver defaults

    重启本地服务器,就好了

    3.4.2 任意一台联网的设备想远程控制服务器的设备上,去这里下载VNC VIEWER

    https://www.realvnc.com/en/connect/download/viewer/

    输入之前在FRPC中绑定的

    子域名:5901

    输入密码,就可以控制了

    效果受制于网速,经过我的测试这个是我的所有环境下,控制最快的,毕竟是远程环境。。。

    最终效果:

     

     

     

    3.5 NAS

    本身装的就是Linux系统,所以可选项很大,我现在暂时没有这个需求,就不折腾了,但是把解决方案先放在这

    owncloud

     

    3.6 HTPC

    Plex for Linux/Kodi for Linux

    转载于:https://www.cnblogs.com/blog5277/p/11261049.html

    展开全文
  • 基于HTTP协议实现的小型web服务器

    万次阅读 多人点赞 2017-08-12 08:44:43
    我们先了解一下这个项目最终能达到的一个目标,然后以这个来进行项目的分析: 1、实现最基本的HTTP/1.0版本的web服务器,客户端能够使用GET、POST方法...比如当客户在表单中输入数据后,服务器能够将运行结果返回个

    我们先了解一下这个项目最终能达到的一个目标,然后以这个来进行项目的分析:
    1、实现最基本的HTTP/1.0版本的web服务器,客户端能够使用GET、POST方法请求资源
    2、服务器将客户请求的资源以html页面的形似呈现,并能够进行差错处理(如:客户请求的资源不存在时,服务器能够返回一个404的页面)
    3、服务器能进行简单的cgi运行。比如当客户在表单中输入数据后,服务器能够将运行结果返回个客户
    4、能够通过页面对数据库进行操作,如增删查改等操作

    一、http服务器实现的基本框架

    • 关于HTTP协议
      即超文本传输协议,是互联网上应用最广泛的网络协议。它是应用层的协议,底层是基于TCP通信的。HTTP协议的工作过程:客户通过浏览器向服务器发送文档请求,浏览器将请求的资源回应给浏览器,然后关闭连接。即:连接->请求->响应->关闭连接。
    • 关于URL
      即统一资源定位符,每个网页都对应一个URL地址(俗称网址),具有全球唯一性。它包含的信息指出文件的位置以及浏览器应该怎么处理它。 一个完整的URL包括协议类型、主机类型、路径和文件名。
      http协议的URL格式: http: //host[:port][abs_path] ,http表示使用http协议来进行资源定位;host是主机域名;port是端口号,一般有默认的;abs_path代表资源的路径。
      这里我主要介绍项目中涉及的URL的两种格式—URL带参数和不带参数的。
      这里写图片描述
      GET方法使用的是带参数的URL,即传递的参数会使用?连接在资源路径后边;POST方法使用的是不带参数的URL,它的参数是通过http请求报头中的请求消息体传递给服务器的。
    • 关于HTTP的请求与响应格式
      这里写图片描述
      响应报头中的状态码和状态码描述,例如:当请求的资源不存在时,会收到“404 NotFound”的页面,404就是状态码,“NotFound”就是状态码描述,即请求的文件不存在。

    二、服务器实现的基本思路

    1、http协议是基于TCP通信的协议,因此,实现web服务器的第一步至少要能实现两个主机不同进程之间的TCP通信。
    2、接下来的部分就是比较主要的处理逻辑了,当服务器收到请求后,首先应该分析请求方法(因为web服务器是要支持cgi的,但请求方法不同处理cgi也不同,这里我们只处理GET和POST方法)。
    3、当方法确定后,应该拿到请求的URL,这一步是为了我们后边能处理GET和POST方法的cgi(GET和POST的参数位置不同,GET的参数在URL中,POST的参数在请求正文中)
    4、判断资源是否存在,如果存在,判断这个资源是一个目录、普通文件还是一个可执行程序。之前几步我们已经提取到URL以及参数。GET方法:如果没有参数,就直接将请求的资源返回(即进入非cgi模式运行);否则,进入cgi模式内部运行;只要是POST方法就需要支持cgi:直接进入cgi函数内部运行。

    非cgi模式:
    进入非cgi模式时一定是GET方法且没有参数,此时进入echo_www()函数内部即可,该函数会将所请求的资源以html的格式返回给浏览器。

    cgi模式:
    这里写图片描述
    上述这张图描述了运行cgi时的过程,首先服务器要从浏览器上读取参数,然后需要fork出一个子进程进行cgi部分的处理,父进程通过环境变量的方式将参数转交给子进程,子进程运行完成后,将结果交给父进程,父进程再将数据输出给浏览器。在这个过程中可以将父进程看作一个所谓的中间量,只进行了参数的转交,因此可以将子进程的输入输出文件描述符进行重定向,即子进程直接与浏览器“联系”。

    下面总结出父子进程内部各自需要干的事情:
    这里写图片描述

    三、错误处理

    错误处理这部分的实现可以参考echo_www()函数,但需要改变响应的消息报头的格式,即改变状态码,状态码描述,以及返回的页面。例如当请求的资源不存在时,服务器需要返回给浏览器一个默认的404页面,告诉客户请求的资源不存在。效果如图:
    这里写图片描述

    四、项目文件

    这里写图片描述
    目录:
    cgi:运行cgi部分的实现代码
    conf:配置文件,存放需要绑定的服务器的ip和port
    log:shell的日志文件以及http错误处理的日志文件
    lib:mysql需要的lib库
    sql_client:mysql部分的API及CGI实现
    wwwroot:web服务器工作的根目录,包含各种资源页面(例如默认的index.html页面,差错处理的404页面),以及执行cgi的可执行程序

    文件:
    configure.sh:sheel脚本,运行该shell脚本后需要自动生成Makefile文件
    http_ctl.sh:服务器控制脚本,需要实现服务器的启动、暂停以及重新启动
    httpd.pid:与http_ctl.sh配合使用。如果把服务器变成守护进程在后台运行,重新启动时就需要检测服务器是否启动,该文件存放服务器启动以后的进程id
    httpd.h:服务器的方法声明
    httpd.c:方法实现
    main.c:服务器的主逻辑

    五、实现结果

    请求资源存在:
    这里写图片描述

    运行cgi后:
    这里写图片描述

    六、源码:

    https://github.com/lybb/Linux/tree/master/httpd

    附:
    这里是我遇到的一些问题,粘出来,也可能是你遇到的问题:
    1、本地环回测试ok,Linux下的浏览器测试也可以,但不能接外部的浏览器访问(没有设置桥接模式)嗯~要是在外部浏览器测试的话千万别忘记关闭防火墙
    2、服务器应答时,没有将html格式的页面发送,而是将底层的实现代码展示在浏览器,并且在调试时将本来要打印的调试信息会打印到网页上(在回应空行时将send期望发送的数值写的太大,本来只需要发送两个字节的内容)
    解决:先检查代码,思路正确,在容易出现问题的地方加入调试信息,最后将问题定位在echo_www()函数内
    3、不能显示图片(这个问题是没有将所有发送的情况考虑完全,只考虑到目录、可执行程序,但没有考虑到如果请求的是一个路径明确的普通文件)
    解决:测试请求一个路径明确的test.html文件,加入调试信息 ,将问题定位在:如果请求的资源存在,应该如何处理。对于普通文件,找到后并回显给浏览器;如果是目录,应答的是默认页面;如果是可执行程序,执行后返回结果
    4、能显示图片后,但显示的不完整(原因:echo_www中,期望读取一行信息的line值太小,不能存下一张图片)
    5、运行cgi模式时,每次提交数据并进行submit后都会自动出现提醒下载的页面
    原因:在响应报头中,将Content-Type中的”text”写成”test”。而浏览器对于不能识别或解析的实体,都会提醒用户下载。

    展开全文
  • 服务器的分类

    万次阅读 2014-12-14 12:47:55
    服务器这块的知识是我的知识体系的一个盲区,花点时间对三类服务器(PC服务器,小型机,大型机)作一下梳理。下图为IBM服务器体系的划分,这虽然代表了IBM对自身产品线的定位,但也体现了三类服务器的市场定位。CPU大型机...
  • 1. 大型机又名大型主机,使用专用的处理器指令集、操作系统和应用软件。故此,大型机不仅仅是一个硬件上的概念,更是一个硬件和专属软件的有机整体。大型机是上世纪六十年代发展起来的计算机系统。...
  • 小型网站并不需要兴师动众的租用服务器来完成搭建,在此向大家推荐一款云服务器:SAE。 新浪云平台是分布式WEB服务的开发、运行平台。 非常适合小型网站的搭建,可以免费使用服务器,而且成本很低, 相关链接:...
  • 小型机和PC服务器差异分析

    千次阅读 2012-04-27 10:29:23
    但是他们并没有意识到,PC服务器不能满足需求的原因究竟在哪里,也没有认识到,看起来比PC服务器昂贵得多的小型机实际上能给他们带来怎样的投资保护。实际上两者之间的差距是非常大的,因为用一个比喻来形容二者:...
  • 戴尔poweredge r730服务器配置及系统安装详解教程

    万次阅读 多人点赞 2017-07-24 10:13:24
    第一次给服务器安装的是ubantu系统;   首先我们开机进入小型BIOS设置一下RAID,或者进入服务器管理系统,在系统的BIOS中进行RAID设置; 开机后当看到出现 时按Ctrl+r,进入小型BIOS 小型BIOS界面 先进入到VG ...
  • 服务器定义及发展史

    万次阅读 2018-03-18 20:27:24
    定义服务器是计算机的一种,它比普通计算机运行更快、负载更高、价格更贵。服务器在网络中为其它客户机(如PC机、智能手机、ATM等终端甚至是火车系统等大型设备)提供计算或者应用服务。服务器具有高速的CPU运算能力...
  • 市场上关于X86 和小型机的争论从来就没有停止过,在以往的印象当中,x86服务器在中低端形成了统治之势,而小型机则在关键性应用领域(金融、证券、政府等)享有王者地位。但是随着X86服务器的不断发展,这两年来“去...
  • 小型机、PC服务器、大型机常识

    千次阅读 2012-09-14 10:28:21
    小型机是指采用8-32颗处理器,性能和价格介于PC服务器和大型主机之间的一种高性能 64 位计算机。国外小型机对应英文名是minicomputer和midrange computer。midrange computer是相对于大型主机和微型机而言,该词汇...
  •  随着宽带接入的普及,越来越多的小型企业用户希望拥有自己企业的网站、搭建 Web 、 FTP 等小型服务器,从而能在因特网中与客户交流,更好地利用因特网展示企业,为客户提供服务。但是在因特网中只有具有固定 IP ...
  • 利用个人PC建设小型服务器

    千次阅读 2019-04-19 15:59:08
    IIS(Internet Information Services)是由微软公司提供的基于运行Microsoft Windows的互联网基本服务,它是一种Web(网页)服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文....
  • 小型机和服务器有何区别

    千次阅读 2018-11-08 14:51:49
    小型机是指采用精简指令集处理器,性能和价格介于PC服务器和大型主机之间的一种高性能 64 位计算机。国外小型机对应英文名是minicomputer和midrange computer。midrange computer是相对于大型主机和微型机而言,该...
  • 小企业怎样选择服务器和操作系统

    千次阅读 2018-08-01 19:31:05
    事实证明,选择小型商务服务器时,只需要考虑四件事情,即可选到适合您的业务的优质服务器。本文为您介绍小企业怎样选择服务器和操作系统,以便为您的业务做出正确的决定。 一、您需要哪种类型的服务器? 小企业...
  • 目录 一、实验目的 二、实验内容 三、实验器材 四、实验步骤 1、添加交换机 ...6、设置三台电脑的IP地址、子网掩码、网关和DNS服务器...研究小型局域网络的组建过程,学习掌握双绞线的制作方法,学习掌握计算机和...
  • CentOS 安装HTTP代理服务器Tinyproxy

    千次阅读 2018-12-29 11:23:43
    Tinyproxy是一个小型的基于GPL的HTTP/SSL代理程序,非常适合小型网络而且便于快速部署。这个代理程序最大的优点就是占用系统资源比较少。这里使用的系统为CentOS5.6,可以直接yum方式安装。 安装Tinyproxy yum ...
1 2 3 4 5 ... 20
收藏数 148,707
精华内容 59,482
关键字:

小型服务器