myhttp

2023-11-10

     HTTP 是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。 

实现一个服务器程序,支持HTTP协议的服务器,浏览器进行访问

 

从请求角度上,支持GET和POST方法;从相应角度上,支持静态页面也支持动态页面

   静态页面:GET方法且query_string为空。

                    根据url_path获取到文件的真实路径,打开文件,根据文件内容构造HTTP响应报文。首行+header+空行+文件内容

                    此处使用sendfile,更高效的把文件中的内容传回给客户端。

   动态页面:基于CGI,创建一对匿名管道,fork出子进程。

                     父进程流程:

                                         a)如果方法是POST,就把body中的数据写到管道中

                                          b) 父进程构造HTTP响应报文。

                                          c) 父进程尝试从管道中读取子进程的计算结果作为HTTP相应中的body

                                          d)父进程进行进程等待。waitpid。。。现在有多个进程,每个进程又创建了子进程,waitpid明确等待回收自己创建的进程

                       (1)GET方法且query_string为空

                         (2)POST方法。

                  子进程流程:a)设置环境变量。REQUENCE_METHOD 、QUERY_STRING 、CONTENT_LENGTH

                                        b)重定向。标准输入输出重定向到管道上

                                        c)根据url_path获取到CGI程序真实路径

                                        d)进程程序替换

                                        e)错误处理

 

          CGI:获取到请求的参数,对参数进行处理

                         a)从环境变量之中获取到方法。如果是GET方法,从环境变量中获取到query_string。如果是POST方法,需要从环境变量中获取到content_length,再从标准输入中读入body数据

                          b)从参数中解析出核心数据

                          c)根据输入参数进行计算,和具体的业务相关

                          d)根据计算结果生成动态页面

 

HTTP协议的主要特点可概括如下:

1.支持客户/服务器模式。

2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。

4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

 

 工作流程

     (1) 服务器启动,在指定端口或随机选取端口绑定 httpd 服务。

     (2)收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数。

     (3)取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ? 后面的 GET 参数。

     (4) 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页。

     (5)如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本。

    (6)读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200  状态码写到套接字。

    (7) 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程。

    (8) 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序。

    (9) 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。

(10) 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。

 关键函数的作用:

ReadLine:按行读取数据, 把回车换行等情况都统一为换行符结束 

如果当前字符是\r,读取下一个字符,如果下一个字符是\n,即\r\n的组合,就将它们变为\n

                                                           如果下一个字符不是\n,即只有一个\r,那么把它变成\n

如果当前字符是\n,退出函数

如果以上都不是,那就把该字符放入buf中

 int ReadLine(int sock,char buf[],size_t max_size){
   char c='\0';
    ssize_t i=0;
    while(i < max_size){
      ssize_t read_size = recv(sock,&c,1,0);
      if(read_size <= 0){
        return -1;
      }
  
     if(c=='\r'){
        recv(sock,&c,1,MSG_PEEK);
        if(c == '\n'){
          recv(sock,&c,1,0);
        }else{
          c = '\n';
        }
      }
     buf[i++] = c;
     if(c=='\n'){
        break;
      }
    }
   buf[i] = '\0';
   return i;
  }

 HandlerCGI:

创建一对匿名管道,

 int HandlerCGI(int new_sock,const HttpRequest* req)
 {
   int err_code = 200;
   int fd1[2],fd2[2];
   pipe(fd1);
   pipe(fd2);
   int father_read = fd1[0];
   int child_write = fd1[1];
   int father_write = fd2[1];
   int child_read = fd2[0];
 
   pid_t ret = fork();
   if(ret > 0){   /father
     close(child_read);
     close(child_write);
     HandlerCGIFather(new_sock,ret,father_read,father_write,req);
   }else if(ret ==0){   //child
     close(father_read);
     close(father_write);
     HandlerCGIChild(child_read,child_write,req);
   }else{
     perror("fork");
     err_code = 404;
     goto END;
   }
 END:
   if(err_code != 200){
     printf("Handler 404\n");
     Handler404(new_sock);
   }
 }

     HandlerCGIFather:

a)对于post方法,把body中的数据写到管道中

     从socket中读出数据,写到管道中,此处无法用sendfile,因为sendfile只能把数据写到socket之中,直接一个字节一个字节的从socket中读出来,再写到管道中去

b)父进程需要构建一个完整的HTTP协议数据。

     对于HTTP协议要求我们按照指定的格式返回数据。CGI程序返回的结果只是body部分。 header,首行等部分需要父进程自己构造

c)从管道中尝试读数据,写回到socket中。判断语句中为0表示读到了 文件结束标志EOF。子进程结束(子进程停止write),父进程就会读到文件尾

     father_read对应的是child_write,对于父进程来说,child_write已经关闭了;对于子进程来说,如果CGI程序处理完进程也就退出了。

     进程退出的同时会关闭child_write,此时就意味着管道的所有写端都关闭,再尝试读,read返回0

d)进程等待,否则会造成僵尸进程

    不能是wait,因为服务器会给每个客户端都创建一个线程,每个线程又有可能创建子进程,此时如果用wait,任何一个子进程结束都可能导致 wait返回,这样的话子进程就不是对应的线程来回收

void HandlerCGIFather(int new_sock,int child_pid,int father_read,\
                       int father_write,const HttpRequest* req)
 {  
    // a
    char c = '\0';
    if(strcasecmp(req->method,"POST")==0){
      //read data from socket,write in pipe
      //can't use sendfile,because sendfile only write data to socket
      //one byte,one byte read from socket and write to pipe
     
     ssize_t i=0;
     for(;i<req->content_length;++i){
       read(new_sock,&c,1);
       write(father_write,&c,1);
     }
   }
   
  //b
  const char* first_line = "HTTP/1.1 200 OK\n" ;
  const char* blank_line = "\n";
  send(new_sock,first_line,strlen(first_line),0);
  send(new_sock,blank_line,strlen(blank_line),0);
 
  //c
  while(read(father_read,&c,1)>0){
     write(new_sock,&c,1);
   }
     //d
     waitpid(child_pid,NULL,0);
 }

 

HandlerCGIChild:

a)exec替换后,子进程里的内容都没有了,创建环境变量。保存CGI想要传递下去的信息。 

      REQUEST_METHODD  ,   QUERY_STRING  ,   CONTENT_LENGTH

      字符串拼接结果相当于是REQUEST_METHOD = GET

b)把子进程的标准输入和标准输出重定向到管道。目的是读取父进程交给的数据,再写回到父进程的管道中

c)进程的程序替换,通过url_path拼装成一个完整的路径,把对应的文件进行替换   

     之所以采用execl,原因一:此处CGI程序不需要指定命令行参数,l/v不影响

                                  原因二:此处CGI程序对应的全路径中通过putenv的方式 进行设置 的

                                原因三:此处CGI程序环境变量在子进程中通过putenv的方式进行设置的,如果使用execle的话,就不必使用putenv了,此处的两种方式均可

d)exec执行失败,需要进行错误处理。

      如果此处不进行退出,就会出现子进程和父进程监听相同的端口号情况,而此时我们只是希望子进程去调用 CGI程序处理客户端链接这样的事情只应该由父进程来完成

      execl之后就把 动态页面生成的逻辑交给了CGI程序

void HandlerCGIChild(int child_read,int child_write,const HttpRequest* req)
 {
   //a
   char method_env[SIZE] = {0};
   sprintf(method_env,"REQUEST_METHOD=%s",req->method);
   putenv(method_env);
   if(strcasecmp(req->method,"GET") == 0){
     //set QUERY_STRING
     char query_string_env[SIZE] = {0};
     sprintf(query_string_env,"QUERY_STRING=%s",req->query_string);
     putenv(query_string_env);
   }else{
     //set CONTENT_LENGTH
     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
   char file_path[SIZE] = {0};
   HanderFilePath(req->url_path,file_path);
   execl(file_path,file_path,NULL);
   //d
   exit(0);
 } 

 

 

HandlerRequest:

1.读取并分析请求

    a)从socket中取出HTTP,读取首行

    b)分析首行,获取method和url

    c)读取并分析头部,只关注content_length

 2.通过请求判断是动态还是静态页面

   a)GET方法 且 没有 query_string--------静态页面

   b)GET方法 且 有 query_string--------动态页面

   c)POST---------动态页面

 void HandlerRequest(int new_sock)
 {
  int err_code = 200;
  HttpRequest req;
  memset(&req,0,sizeof(req));
 
 if(ReadLine(new_sock,req.first_line,sizeof(req.first_line))<0){
    printf("ReadLine first_line failed\n");
    err_code = 404;
    goto END;
  }
 printf("first_line=%s\n",req.first_line);
 
 if(ParseFirstLine(req.first_line,&req.method,&req.url)<0){
    printf("ParseFirstLine failed!first line=%s\n",req.first_line);
    err_code = 404;
    goto END;
  }
 
 if(ParseQueryString(req.url,&req.url_path,&req.query_string)<0){
    printf("ParseQueryString failed!url=%s\n",req.url);
    err_code = 404;
    goto END;
  }
 
 if(HandlerHeader(new_sock,&req.content_length)<0){
    printf("HandlerHeader failed!");
    err_code = 404;
    goto END;
  }
 
 if(strcmp(req.method,"GET")==0 && req.query_string==NULL){
     HandlerStaticFile(new_sock,&req);
  }else if(strcmp(req.method,"GET")==0 && req.query_string != NULL){
     HandlerCGI(new_sock,&req);
  }else if(strcmp(req.method,"POST")==0){
     HandlerCGI(new_sock,&req);
  }else{
     printf("method not supposed!method=POST or GET\n");
     err_code = 404;
     goto  END;
  }
 END:
   if(err_code != 200){
     printf("Handler 404\n");
     Handler404(new_sock);
   }
  
  close(new_sock);
 }

 

HttpServerStart:

1.socket

2.bind
3.listen
4. 进入循环接收客户端请求
5. 创建线程处理更多连接

 void HttpServerStart(const char* ip,short port)
 {
  int listen_socket = socket(AF_INET,SOCK_STREAM,0);
  if(listen_socket < 0){
    perror("socket");
    return;
   }
  
 int opt=1;
 setsockopt(listen_socket,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

 sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(ip);
 addr.sin_port = htons(port);
 int ret = bind(listen_socket,(sockaddr*)&addr,sizeof(addr));

 if(ret < 0){
     perror("bind");
     return;
   }
 
 ret = listen(listen_socket,5);
 
 if(ret < 0){
     perror("listen");
     return;
   }
 printf("HttpServerStart OK\n");
 
 while(1){
     sockaddr_in peer;
     socklen_t len = sizeof(peer);
     int new_sock = accept(listen_socket,(sockaddr*)&peer,&len);
     if(new_sock < 0){
       perror("accept");
       continue;
     }
    pthread_t tid;
    pthread_create(&tid,NULL,ThreadEntry,(void*)new_sock);
    pthread_detach(tid);
   }
 }

服务器 性能:

         http_load测QPS

         进程池-----FastCGI

         计算业务-------多线程进行计算

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

myhttp 的相关文章

  • Linux 安装软件 常见问题 x86 or x64

    Linux 安装软件 常见问题 x86 or x64 平民资料 x64 是指CPU是64位版本的 x86 是指CPU是32位版本的 如果你的CPU是64位的 可以安装64位的 也可以安装32位的 反过来只能安装32位的 RedHat Lin
  • React 路由基本使用

    代码示例 有Logint和Layout组件 import React from react import BrowserRouter as Router Redirect Route Switch from react router dom
  • python重命名文件excel,在excel电子表格的文件夹python上重命名多个文件

    I am pretty new at Python and I want to automate a process that takes a lot of my time but now I need to rename about 20
  • js 如何判断属性,包括多级对象的状况

    js目前没有一个明确的方法去判断对象是否存在 尤其是出现多级属性 对象 的情况 一旦一个不存在的属性跨级取 就会报错 undefined 因此考虑封装一个通用的方法去专门检测 如果存在属性返回true 反之返回falsefunction c
  • # HTB-Tier2- Oopsie

    HTB Tier2 Oopsie Tags PHP Web Custom Applications Session Handling Apache Penetration Tester Level 1 Reconaisance Web Si
  • 在 uni-app 中选中奇偶子元素

    问题描述 在 uni app 中 使用 nth child 选择器选择奇偶子元素不像预期那样生效 原代码 nth child 2n 选择偶数个子元素 nth child 2n 1 选择奇数个子元素 奇数子元素 issueData item
  • redux的理解及其工作原理?工作流程?

    理解 redux是一个用于管理JavaScript应用程序状态的可预测状态容器 它是一个独立于任何特定UI库的状态管理库 但在React应用中广泛使用 工作原理可以概括为一下几个关键概念 1 store 存储 redux应用的状态 Stat
  • 调试笔记之雨过天晴多点还原软件MBR实例

    BY SUDAMI 为了能够调试多点还原软件 雨过天晴 的启动代码 目前有2种方式 引用 1 在Bochs调试器上装Windows XP系统 然后用Bochs单步调试 不过光安装操作系统就得花20个小时以上 2 用Wnhex克隆整个磁盘 配
  • 求点集中存在的点,满足:其x、y坐标值不同时小于点集中任意一点的x、y坐标值

    问题描述 对于平面上的两个点p1 x1 y1 和p2 x2 y2 如果x1 lt x2且y1 lt y2 则p2支配p1 给定平面上的n个点 请设计算法求其中没有被任何其他点支配的点 换句话说 即 求点集中存在的点 满足 其x y坐标值不同
  • Java实现杨辉三角

    杨辉三角的模型 分析 1 最外层的数字始终是 1 2 每个数等于它上方两数之和 public class Yanghui public static void main String args int yanghui new int 10
  • springcloud配合eureka遇到的巨坑

    springcloud配合eureka遇到一个巨坑 这个问题困扰了楼主整整3天 问题描述 项目在idea能够启动 注册服务 心跳检测一切正常 但是打包后放入服务器中 发现项目启动正常 服务注册正常 但是过了30秒后 eureka开始报错 报
  • [JSP暑假实训] 五.MyEclipse+Servlet+JSP实现火车票网站注册操作及登陆验证

    本系列文章是作者暑假给学生进行实训分享的笔记 主要介绍MyEclipse环境下JSP网站开发 包括JAVA基础 网页布局 数据库基础 Servlet 前端后台数据库交互 DAO等知识 前一篇文章讲解了MyEclipse Servlet JS
  • 用户管理相关命令

    用户管理相关命令 实验目的 通过对用户管理相关命令进行练习 能够对linux中用户和组的维护和管理工作熟练处理 实验内容 1 su命令 切换另一用户 切换主用户时需要输入密码 2 用户相关命令 useradd 创建新用户 passwd us
  • android 检查otg,怎么查看手机是否支持otg

    怎么查看手机是否支持otg很多同学都遇到了这个问题 那么该如何解决呢 请看IEfans小编给大家带来的查看手机是否支持otg方法一览 希望对您有所帮助 工具 原料 手机 VIVO X6S A 系统 PD1415BA A 3 13 10And
  • LeetCode141:环形链表

    给你一个链表的头节点 head 判断链表中是否有环 如果链表中有某个节点 可以通过连续跟踪 next 指针再次到达 则链表中存在环 为了表示给定链表中的环 评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置 索引从 0 开始 注意
  • Non-resolvable parent POM for解决

    在运行maven项目是出现CIA列错误提示 INFO Scanning for projects ERROR ERROR Some problems were encountered while processing the POMs FA
  • 机器学习资源大全中文版

    中午版翻译转载自 https github com jobbole awesome machine learning cn 英文版原文转载自 https github com josephmisiti awesome machine lea
  • Jeesite 登录逻辑分析

    最近项目需求研究免登录进入jeesite系统 于是对jeesite的登录逻辑进行了研究 一 当用户从url访问jeesite系统时 首先会通过下面方法 RequestMapping value adminPath login method
  • ag-grid-vue单元格合并

    原理 调用ag的合并方法时 会先找到第一个需要合并的单元格 然后设置z index 1 继续往下找 如果第二行该列仍然需要合并 则第一行刚才找到的单元格的高度会乘以2 以此类推 如果连续找到第9个依然需要合并 则 第一个找到的单元的高度 1
  • 朴素贝叶斯分类器之天气预测算法

    朴素贝叶斯分类器之天气预测算法 1 1 题目的主要研究内容 1 根据天气情况预测要不要去打网球 1 2 题目研究的工作基础或实验条件 1 硬件环境 Intel R Core TM i7 7700HQ CPU 2 软件环境 本实验的软件环境主

随机推荐