web服务器

小型http服务器main.cc

gitee代码

#include "HttpServer.hpp"
#include "Log.hpp"

static void Usage(std::string _porc)
{
    std::cout << "Usage: \n\t";
    std::cout << "method one: "<<_porc << "port\n\t";
    std::cout << "method two: "<<_porc << std::endl;
}

int main(int argc, char *argv[])
{
    if(argc != 1 && argc != 2){
        Usage(argv[0]);
        return 1;
    }
    HttpServer *svr = nullptr;
    if(argc == 1){
        svr = HttpServer::GetInstance(8081);
    }
    else{
        svr = HttpServer::GetInstance(atoi(argv[1]));
    }
    daemon(1, 1);
    svr->InitServer();
    svr->Start();
    return 0;
}

HttpServer.hpp

#pragma once

#include <pthread.h>
#include "Sock.hpp"
#include "Protocol.hpp"
#include "ThreadPool.hpp"

#define PORT 8081

class HttpServer{
    private:
        int port;
        int lsock;
        ThreadPool *tp;
        static HttpServer *http_svr;
        static pthread_mutex_t lock;

        //HttpServer()
        //{}
    public:
        HttpServer(int _p = PORT)
            :port(_p),lsock(-1),tp(nullptr)
        {}

        static HttpServer *GetInstance(int sk)
        {
            if(nullptr == http_svr){
                pthread_mutex_lock(&lock);
                if(nullptr == http_svr){
                    http_svr = new HttpServer(sk);
                }
                pthread_mutex_unlock(&lock);
            }
            return http_svr;
        }

        void InitServer()
        {
            signal(SIGPIPE, SIG_IGN);
            lsock = Sock::Socket();创建
            Sock::SetSockOpt(lsock);可复用
            Sock::Bind(lsock, port);
            Sock::Listen(lsock);

            tp = new ThreadPool();
            tp->InitThreadPool();
        }

        void Start()
        {
            for(;;){
                int sock = Sock::Accept(lsock);
                if(sock < 0){
                    continue;
                }
                LOG(Notice, "get a new link ...");
                Task *tk = new Task(sock);
                tp->PushTask(tk);
                //demo 线程
                //pthread_t tid;
                //int *sockp = new int(sock);把整形初始化为sock
                //pthread_create(&tid, nullptr, Entry::HandlerHttp, sockp);
                //pthread_detach(tid);
            }
        }
pthread_create创建的线程如果永远不退出,会阻塞
HandlerHttp:获取请求、分析、响应
        ~HttpServer()
        {
            if(lsock >= 0){
                close(lsock);
            }
        }
};

HttpServer *HttpServer::http_svr = nullptr;
pthread_mutex_t HttpServer::lock = PTHREAD_MUTEX_INITIALIZER;

创建线程 处理请求HandlerRequest(获取请求 分析请求) 分离线程 制作响应 发送响应

Sock.hpp

#pragma once
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/sendfile.h>
#include<unistd.h>
#include<stdlib.h>
#include<cstring>
#include<vector>
#include<unordered_map>
#include<sstream>
#include"Log.hpp"
#define BACKLOG 5

class Sock 
{
  public:
    static int Socket()
    {
      int sock = socket(AF_INET,SOCK_STREAM,0);
      //原先我们都是直接把错误打印出来,但现在改为日志的形式
      if(sock < 0){
        LOG(Fatal,"socket create error");
        exit(SocketErr);
      }
      return sock;
    }

    static void Bind(int sock,int port)
    {
      struct sockaddr_in local;
      bzero(&local,sizeof(local));

      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = htonl(INADDR_ANY);
      if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
        LOG(Fatal,"socket bind error");
        exit(BindErr);
      }
    }
    static void Listen(int sock)
    {
      if(listen(sock,BACKLOG) < 0){
        LOG(Fatal,"socket listen error");
        exit(ListenErr);
      }
    }

    static void SetSockOpt(int sock)
    {
      int opt = 1;
      setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    }

    static int Accept(int sock)
    {获取新连接
      struct sockaddr_in peer;
      socklen_t len = sizeof(peer);
      int s = accept(sock,(struct sockaddr*)&peer,&len);
      //peer连接我
      if(s < 0){
        LOG(Warning,"accept error");
        //再次申明一次,这里就相当于你去引流,如果没有引到,你会接着去引,所以说即使错误了也不要紧,可不敢直接exit
      }
      return s;
    }

    //从sock读取一行内容
    static void GetLine(int sock,std::string& line)
    {引用
      char c = 'X';//按字符读取,初始化
      while(c != '\n'){
        ssize_t s = recv(sock,&c,1,0);从sock读,放到c
        if(s > 0){
                    if(c == '\r'){
            ssize_t ss = recv(sock,&c,1,MSG_PEEK);
            if(ss > 0 && c == '\n'){
              //换行符是\r\n
              recv(sock,&c,1,0);
            } 
            else{
              // 把\r改为\n
              c = '\n';
            }
          }
//走到这里时:情况1.读取到的内容是常规字符;况2.所有换行符改为\n
          if(c != '\n'){
             line.push_back(c);
          }
        }
      }
    }

};

recv函数的扩展MSG_PEEK(数据的窥探功能):换行符是没有硬性规定的,有些平台下有可能是\r \n \r\n 这3种都有可能是换行符标志,这里以\n为换行符;知道是\r时,判断一下是否是\n,如果是正常字符,非\n,正常字符放回内核缓冲区是很难做到的,用MSG_PEEK检测下一个字符是否是正常字符 ,但是并不从内核缓冲区拷贝到用户缓冲区

Log.hpp

[日志级别][message][时间戳][filename文件名]line无符号整形、行号]

#pragma once 
#include<iostream>
#include<string>
#include<sys/time.h>

#define Notice  1
#define Warning 2
#define Error   3
#define Fatal   4 

//尽可能不要在项目中出现数字
enum ERR 
{
  SocketErr = 1,
  BindErr,
  ListenErr,
  ArgErr//命令行参数错误
};

#define LOG(level,message) \
  Log(#level,message,__FILE__,__LINE__)

void Log(std::string level,std::string message,std::string filename, size_t line)
{
  struct timeval curr;
  gettimeofday(&curr,nullptr);
  std::cout << "[" << level << "]"<< "[" << message << "]" << "[" << curr.tv_sec << "]" << "["<< filename <<"]"<<"["<< line <<"]" <<std::endl;
}

Util.hpp

工具类代码

#pragma once
#include"Sock.hpp"
#include"Log.hpp"
//打过来的请求是一行一行,so也要按行:读取
class Util
{
  public:
    //字符串拆分,也就是1变多
    static void StringParse(std::string& request_line,std::string& method,std::string& uri,std::string& version)
    {
      //这是最简单的方法来进行字符串解析
      std::stringstream ss(request_line);
      ss >> method >> uri >> version;
    }
    
    //容器内的东西是坚决不能修改的,所以最好传参的时候不要传引用
    static void MakeStringToKV(std::string line,std::string& k,std::string& v)
    {
      std::size_t pos = line.find(": ");//pos指向冒号的位置
      if(pos != std::string::npos){
        //找到了
        k = line.substr(0,pos);0下标开始到pos
        v = line.substr(pos+2);从pos+2到结尾
      }
    }

    static ssize_t StringToInt(const std::string& v)
    {
      //方法1: return stoi(v);
      //stringstream 也可以转换成整数
      方法2:
      std::stringstream ss(v);
      ssize_t i = 0;
      ss >> i;流追加到i,自动识别类型
      return i;
    }
};

ThreadPool.hpp

生产者消费者模型,对外提供:任务队列、push pop接口,有事就处理,没事就等待,

#pragma once 
#include<iostream>
#include<pthread.h>
#include<queue>
#include"Protocol.hpp"
typedef void (*handler_t)(int);

class Task 
{
  private:
    int sock;
    handler_t handler;

  public:
    Task(int sk):sock(sk),handler(Entry::HandlerHttp)
    {}

    void Run()
    {
      handler(sock);
    }
    ~Task()
    {}
};

class ThreadPool
{
  private:
    std::queue<Task*> q; 
    int num;
    pthread_mutex_t lock;
    pthread_cond_t cond;条件变量
  public:
    void LockQueue()
    {
      pthread_mutex_lock(&lock); 
    }

    void UnlockQueue()
    {
      pthread_mutex_unlock(&lock);
    }

    bool IsEmpty()
    {
      return q.size() == 0;
    }

    void ThreadWait()
    {
      pthread_cond_wait(&cond,&lock);在cond条件下等,释放曾经拥有的锁
    }

    void ThreadWakeup()
    {唤醒特定条件变量下的信号
      pthread_cond_signal(&cond);
    }

  public:
    ThreadPool(int n = 8):num(n)默认线程数=8
    {}

    static void *Routine(void *args)
    {
      //static是静态成员函数,不能够去访问非静态的成员函数,但是我现在要使用你ThreadPool里面queue,对弈可以通过对象的方式来
      ThreadPool *this_p = (ThreadPool*)args;
      this_p->LockQueue();锁住队列,任务队列:临界资源,任务队列里都是指针
      while(this_p->IsEmpty()){
        this_p->ThreadWait();线程休眠
      }
      Task *tk = this_p->PopTask();取任务
      this_p->UnlockQueue();

      tk->Run();处理任务
      delete tk;
    }

    void InitThreadPool()
    {
      pthread_mutex_init(&lock,nullptr);
      pthread_cond_init(&cond,nullptr);
      pthread_t tid[num];
      for(int i = 0; i < num;i++){1次创建1批线程
        pthread_create(tid+i,nullptr,Routine,this);
        pthread_detach(tid[i]);
      }
    }

    void PushTask(Task *tk)
    {往线程池里push任务
      LockQueue();
      q.push(tk);//队列里插入任务
      UnlockQueue();
      ThreadWakeup();唤醒线程,让处理任务
    }

    Task* PopTask()把任务拿出来
    {
        Task *tk = q.front();
        q.pop();
        return tk;
    }
    ~ThreadPool()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&cond);
    }
};

Makefile

BIN=http_server
SRC=main.cc
CC=g++
FLAGS=-std=c++11 -lpthread #-DEBUG
CURR_PATH=$(shell pwd)

.PHONY:all
all:CGI $(BIN) 


.PHONY:CGI 
CGI:
		cd $(CURR_PATH)/cgi;\
			make;\
			cd $(CURR_PATH)


$(BIN):$(SRC)
	$(CC) -o $@ $^ $(FLAGS)

.PHONY:clean
clean:
	rm -f $(BIN);\
	cd $(CURR_PATH)/cgi;\
	make clean;\
	cd $(CURR_PATH);\
	rm -rf output 

.PHONY:output 
output:
	mkdir -p output/wwwroot/cgi;cp $(BIN) output;cp -rf wwwroot/* output/wwwroot;cp cgi/test_cgi output/wwwroot/cgi

#-DEBUG:-D命令行式的宏定义

Protocol.hpp 协议

#pragma once
#include"Sock.hpp"
#include"Log.hpp"
#include"Util.hpp"
//服务器从webroot(web根目录)中拿资源
#define WEBROOT "webroot"
#define HOMEPAGE "index.html"//首页
#define VERSION "HTTP/1.0"
//不想要被外面人访问到,函数只和本文件强相关
static std::string CodeToDesc(int code)
{
  std::string desc;
   switch(code){
    case 200:
      desc = "OK";
      break;
    case 404:
      desc = "Not Found";
      break;
    default:
      break;
  }
  return desc;
}

static std::string SuffixToDesc(const std::string& suffix)
{
  if(suffix == ".html" || suffix == ".htm"){
    return "text/html";
  }
  else if(suffix == ".js"){
    return "application/x-javascript";
  }
  else if(suffix == ".css"){
    return "text/css";
  }
  else if(suffix ==".jpg"){
    return "image/jpeg";
  }
  else{
    return "text/html";
  }
}
//服务器处理请求Request(接收)
//请求行、请求报头、空行、正文
class HttpRequest
{
  private:
    std::string request_line;
    std::vector<std::string> request_header;
    std::string blank;
    std::string request_body;
  private:
    std::string method;方法
    std::string uri;
    std::string version;
    std::string path;url的路径
    std::string query_string;请求参数

    std::unordered_map<std::string,std::string> header_kv; 

    ssize_t content_length; //为了区分开给三个默认为-1表示不存在 0表示没有  >0表示有正文

    bool cgi;

    ssize_t file_size; //供后续使用的,不然会显得代码很冗余再调用一次stat

    std::string suffix;//提取文件后缀
  public:
    类对象有默认构造函数
    HttpRequest():blank("\n"),content_length(-1),path(WEBROOT),cgi(false),suffix("text/html")
    {}

    void SetRequest(std::string& line)
    {获取的11行内容放入请求
      request_line = line;
    }

    void RequestLineParse()
    {//解析请求行:1个request_line分为3个字符串:method uri version
      Util::StringParse(request_line,method,uri,version);
      //LOG(Notice,request_line);
      //LOG(Notice,method);
      //LOG(Notice,uri);
      //LOG(Notice,version);
    }

    void InsertHeaderLine(const std::string& line)
    {报头行插入vector(request_header)
      request_header.push_back(line);
      LOG(Notice,"line");
    }
//字符串在vector,遍历vector,每个字符串切分为KV,插入map,拆分1行报头
    void RequestHeaderParse()
    {      for(auto& e: request_header){
       std::string k,v;
        Util::MakeStringToKV(e,k,v);
        LOG(Notice,k);
        LOG(Notice,v);
        if(k == "Content-Length"){
          content_length = Util::StringToInt(v);
        }
        header_kv.insert({k,v});
      }
    }

    //1.请求方法必须得是post
    //2.Content-length必须不是0
     bool IsNeedRecvBody()是否需要读正文
    {
      //Post PoSt  POST 统一大小写
      if(strcasecmp(method.c_str(),"POST") == 0 && content_length > 0){
        cgi = true;
        return true;
        }
      return false;
    }

    ssize_t GetContentLength()
    {
      return content_length;
    }

    void SetRequestBody(const std::string& body)
    {
      request_body = body;
    }

    bool IsMethodLegal()
    {判断是否合法
      if(strcasecmp(method.c_str(),"POST") == 0 || strcasecmp(method.c_str(),"GET") == 0){
        return true;
      }
      return false;
    }

    bool IsGet()
    {
      return strcasecmp(method.c_str(),"GET") == 0? true : false;
    }

    bool IsPost()
    {
      return strcasecmp(method.c_str(),"POST") == 0? true : false;
    }

    void UriParse()
    {
      std::size_t pos = uri.find('?');找字符?
      if(pos == std::string::npos){
               path += uri;
      }
      else{//找到了'?',有参数query_string,Pos位置指向'?'
        path += uri.substr(0,pos);前闭后开区间
        query_string = uri.substr(pos+1);
        cgi = true;
      }
    }

    void SetUriEqPath()
    {
      path += uri;
    }

    std::string GetPath()
    {
      return path;
    }

    void IsAddHomePage()
    {字符串已/结尾时:添加首页HomePage,字符串支持[]操作
      if(path[path.size()-1] == '/'){
        path += HOMEPAGE;
      }
    }

    std::string SetPath(std::string _path)
    {
      return path = _path;
    }

    void SetCgi()
    {
      cgi = true;
    }

    bool IsCgi()
    {
      return cgi;
    }

    void SetFileSize(ssize_t s)
    {
      file_size = s;
    }

    ssize_t GetFileSize()
    {
      return file_size;
    }

    std::string GetQueryString()
    {
      return query_string;
    }

    std::string GetBody()
    {
      return request_body;
    }

    std::string GetMethod()
    {
      return method;
    }

    std::string MakeSuffix()制作后缀,后缀在path里
    {
      std::string suffix;
      size_t pos = path.rfind(".");
      if(std::string::npos != pos){
          suffix = path.substr(pos);
      }
      return suffix;
    }
    ~HttpRequest()
    {}
};

//状态行、响应报头、空行、响应正文(发出去)
//定义一套标准
class HttpResponse
{
  private:
    std::string status_line;
    std::vector<std::string> response_header;
    std::string blank;
    std::string response_body;

  public:
    HttpResponse():blank("\r\n")
    {}

    //版本号 状态码 状态码描述
    void SetStatusLine(const std::string& line)
    {
      status_line =line; 
    }

    std::string GetStatusLine()
    {
      return status_line;
    }

    
    const std::vector<std::string>& GetRspHeader()
    {
      response_header.push_back("\n");
      return response_header;
    }

    void AddHeader(const std::string& ct)
    {
      response_header.push_back(ct);
    }

    ~HttpResponse()
    {}
};

class EndPoint
{
  private:
    int sock;
    HttpRequest  req;
    HttpResponse rsp;
  private:
    void GetRequestLine()//获取请求行
    {
      std::string line;
      Sock::GetLine(sock,line);
      //读上来的这一行我们要拿去给HttpRequest进行分析
      req.SetRequest(line);
      //设置进去了之后,最主要的是能够分析出来他所使用请求方法、URL、版本号
      req.RequestLineParse();
    }

    void SetResponseStatusLine(int code)
    {
      std::string status_line;
      status_line = VERSION;
      status_line += " ";
      status_line += std::to_string(code);转为字符串
      status_line += " ";
      status_line += CodeToDesc(code);//状态码转为所对应的描述
      status_line += "\r\n";//对于我们自己写的我们就设置为以\r\n为结束

      rsp.SetStatusLine(status_line);
    }
    void GetRequestHeader()
    {//不断的循环读,一直读取到"\n"停止
      std::string line;
      do{
          line = "";读之前,清空
          Sock::GetLine(sock,line);
          req.InsertHeaderLine(line);
      }while(!line.empty());
      req.RequestHeaderParse(); //解析报头
    }

    void SetResponseHeaderLine()
    {根据后缀制作Content-type
      //图片、网页类型不同表示Content-type不同
      std::string suffix = req.MakeSuffix();
      std::string content_type = "Content-Type: ";
      content_type += SuffixToDesc(suffix);
      content_type += "\r\n";
      rsp.AddHeader(content_type);
    }

    void GetRequestBody()
    {
      int len = req.GetContentLength();
      char c;
      std::string body;临时变量
      while(len != 0){
        ssize_t s = recv(sock,&c,1,0);
        body.push_back(c);把c放入body
        len--;
      }
      req.SetRequestBody(body);
    }
  public:
    EndPoint(int _sock):sock(_sock)
    {}

    void RecvRequest()
    {     //获取完整http请求
     //分析http请求
     
      //读取并分析完了
      GetRequestLine();
      //读取报头
      GetRequestHeader();
      if(req.IsNeedRecvBody())
      {  
      GetRequestBody();//读正文+设置进协议里
      }
      //已经读完了所有的请求
    }

    void MakeResponse()
    {
      int code = 200;
      std::string path; 
      if(!req.IsMethodLegal()){
        //不是POST或者GET方法
        LOG(Warning,"method is not legal");
        code = 404;
        goto end;
      }
      if(req.IsGet()){//get方法
        req.UriParse();
      }
      else{
        //POST方法
        req.SetUriEqPath();
      }
      req.IsAddHomePage();
        path = req.GetPath();
      LOG(Notice,path);
      struct stat st;
      if(stat(path.c_str(),&st) < 0){
        LOG(Warning,"html is not exist! 404");文件不存在
        code = 404;
        goto end;
      }
      else{        //文件路径合法
                if(S_ISDIR(st.st_mode)){文件是目录
          //拼接:/index.html
            path += "/";
            req.SetPath(path);
            req.IsAddHomePage();
        }
        else{ //可执行程序有可执行权限属性
          if((st.st_mode & S_IXUSR) ||\
             (st.st_mode & S_IXGRP) ||\
             (st.st_mode & S_IXOTH)){
          req.SetCgi();
          }
          else{
            //网页请求      
          }
        }
        if(!req.IsCgi()){
          req.SetFileSize(st.st_size);//设置文件大小
        }
      }

end:
       //制作response
       //设置状态行
       SetResponseStatusLine(code);
       SetResponseHeaderLine();
       //设置响应报头
       //SetResponseHeader();
    }

//打开文件(文件在path里),文件里面的内容通过sock发回去
    void ExecNonCgi(const std::string& path)
    {
      ssize_t s = req.GetFileSize();把fd传给sock
      int fd = open(path.c_str(),O_RDONLY);
      if(fd < 0){
        LOG(Error,"path is not exists bug!!!");
        return;
      }
      sendfile(sock,fd,nullptr,s);
      close(fd);
    }

    void ExecCgi()
    {
           //std::string arg;
      //if(req.IsGet()){get方法,参数在GetQueryString
               //arg = req.GetQueryString();
      //}
      //else{post方法,参数在body
        //arg = req.GetBody();
      //}
      std::string  content_length_env;
      std::string path = req.GetPath();
      std::string method = req.GetMethod();
      std::string method_env = "METHOD=";
      method_env += method;
          std::string query_string;
      std::string query_string_env;
      std::string body;
       int pipe_in[2] ={0};
      int pipe_out[2] = {0};
      pipe(pipe_in);
      pipe(pipe_out);
      putenv((char*)method_env.c_str());导入环境变量

      pid_t id = fork();
      if(id == 0){        //子进程
        //但是此时数据是父进程拿着呢,但是要交给子进程去处理,所以这里还涉及到一个进程间通信的问题
        close(pipe_in[1]); //父进程来写数据进管道,子进程来读
        close(pipe_out[0]);//父进程来读结果,子进程来写
        dup2(pipe_in[0],0);子进程用pipe_in[0]读重定向到0
        dup2(pipe_out[1],1);显示到标准输出里的重定向到管道
        //第1种
        if(req.IsGet()){
          query_string = req.GetQueryString();
          query_string_env = "QUERY_STRING=";
          query_string_env += query_string;
          putenv((char*)query_string_env.c_str());
        }
        else if(req.IsPost()){
          content_length_env = "CONTENT-LENGTH=";
          content_length_env += std::to_string(req.GetContentLength());
          putenv((char*)content_length_env.c_str());
        }
        已经知道方法、参数,开始替换(执行程序)
        execl(path.c_str(),path.c_str(),nullptr); 
        exit(0);如果替换失败,直接返回
      }
      //father
      close(pipe_in[0]);
      close(pipe_out[1]);
      char c = 'X';
      //第2种
      if(req.IsPost()){
        body = req.GetBody();
        int size = body.size();
        int i = 0;
        for(;i< size;i++){
          write(pipe_in[1],&body[i],1);
        }
      }

      //替换程序结束后,父进程从管道里读返回结果
      ssize_t s = 0;
      do{
        s = read(pipe_out[0],&c,1);
        if(s > 0){
          send(sock,&c,1,0);
        }
      }while(s > 0);子进程处理完数据,退出,管道关闭,s=0. do while循环结束

      waitpid(id,nullptr,0);
    }

    void SendResponse()
    {      //发送状态行,发送的本质:拷贝到发送缓冲区
      std::string line = rsp.GetStatusLine()获得状态行
      send(sock,line.c_str(),line.size(),0);发送状态行
      //发送响应报头
      for(auto& e : rsp.GetRspHeader()){
        send(sock,e.c_str(),e.size(),0);
      }
      //发送空行,
      //判断执行模式,有可能你只是想要一个普通的html,还有可能是给我传过参数
     
      //1. GET方法uri里面有参数
      //2.POST方法 有正文
      //3 分析出来的路径最终访问的是一个可执行程序都要使用cgi技术
      if(req.IsCgi()){
       LOG(Notice,"use cgi model!");
        ExecCgi();
      }
      else{
        LOG(Notice,"use no cgi model!");
        //普通html
        std::string path = req.GetPath();
        ExecNonCgi(path);
      }
    }
sock:正文
    //短链接,完成一次请求和响应后,链接关闭:
    ~EndPoint()
    {
      if(sock >= 0){
        close(sock);
      }
    }
};

class Entry
{
  public:
    static void HandlerHttp(int sock)处理http协议
    {
      //int sock = *(int*)arg;//堆空间上的arg拷贝到私有栈
      //delete (int*)arg;//释放堆空间保存的sock
#ifdef DEBUG//条件编译,方便调试时看到底哪里出错了
      char request[10240];
      recv(sock,request,sizeof(request),0);从sock读,放到request
      close(sock);
#else 
      EndPoint *ep = new EndPoint(sock);
      ep->RecvRequest(); 
      ep->MakeResponse(); //2种方法:是GET和POST 对于其他的方法如果来了,直接构建response响应返回回去
      ep->SendResponse();

      delete ep;
#endif

    }
};

c++面向对象:方法由对象提供。strcasecmp():忽略字母大小写问题。shift+#:函数跳转。上网目标:1.把网页资源从服务器上拿下来2.把自己的数据提交给服务器。守护进程:不会随着终端的关闭而释放,特殊的孤儿进程,
get方法时:分2种:1无参数,如:https: / / www.baidu.com/index.html\访问首页,静态文件,把网页资源从服务器上拿下来,访问的资源在path,
2有参数,如:https://www. baidu.com/s? ie=utf8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=nba&fenlei=256,类比url=Path ?参数,把自己的数据提交给服务器, path + query_string
网址里的某些信息自动拼接到域名后面
post方法 时:路径在uri中,参数在body中,访问的资源在path,把自己的数据提交给服务器
goto 和 end之间最好不要定义变量,因为跳转的时候有可能会导致一些变量的定义丢失
访问:1目录,文件路径合法:结尾没有带’/'但是最后一个是一个目录,如./ wwwroot/a/b/dir,每个目录下面必须有index.html,是文本(静态资源)则直接返回;2普通的html;3可执行程序(二进制)(动态资源),CGI:服务器把程序跑完,返回运行结果
stat(),成功返回0,失败返回-1,stat=检测是否存path文件=看文件属性,系统是c语言写的,stat系统调用时,path.c_str()转为c语言,c++永远摆脱不了c语言,
通过sock来获取过来的Request和Response,然后在交给对应的两个类里面的具体方法进行拆解分析解析。 EndPoint类:只负责读取协议
switch(code):代码的维护性高,如果来了新的错误,直接加一个case即可
ssize_t sendfile(int out_fd,int in_fd, off_t *offset,size_t count);in_fd :为了读; out_fd :为了写; offset 偏移量 ,系统调用接口
在命令行输入错字母,但删除不了,则ctrl+删除键
创建两个管道1个是父进程把参数给子进程(打开写端),即子进程用pipe_in读取, 一个是子进程给父进程返回结果, 站在被调用进程的角度说In(读)、out,pipe[0]是读端 pipe[1]是写端,给客户端响应正文:cgi模式:服务器创建一个子进程,子进程去处理参数,父进程读取结果pipe_out[0],父进程在返回, 对于url有参数的就是要环境变量传参; 环境变量是全局变量,子进程可以获得到的;第1种是通过环境变量传参给子进程 : GET方法的参数在query_string 、第2种 : POST方法在 body ,可能body十分的长,管道来传参数;
execl(执行谁(路径),以什么方式执行,nullptr)程序替换只替换数据、代码,不换文件描述符,
waitpid():只有1个线程在等,其他线程在干其他事情,等子进程运行完,把结果给父进程,回收已退出的子进程资源
dup2:写入管道的内容重定向到cgi 的 0 读取和1写入,

wwwroot

所有资源保存到web根目录

index.html

w3school:学习写网页,静态网页:不需要交互;

<from action="cgi/test_cgi">
    First date:<>
    <input type="text" name="a" value="0">
    <br>
    Last date:<br>
    <input type="text" name="b" value="0">
    <br><br>
    <input type="submit" value="Submit">
</form>

cgi

服务器调用cgi
访问可执行程序A,A得到传过来的参数,运行A,结果给服务器,web服务器再把结果给浏览器

#include<iostream>
#include<stdlib.h>
#include<cstring>
#include<string>
#include<unistd.h>
void CalDate(std::string& qs)
{
  //a=100&b=200
  std::string part1;
  std::string part2;
  int x = 0;
  int y = 0;
  std::size_t pos = qs.find("&");
  if(pos != std::string::npos){
    part1 = qs.substr(0,pos);
    part2 = qs.substr(pos+1);
  }

  pos = part1.find("=");
  if(pos != std::string::npos){
    x = std::atoi(part1.substr(pos+1).c_str());
  }

  pos = part2.find("=");
  if(pos != std::string::npos){
    y = std::atoi(part2.substr(pos+1).c_str());
  }

  std::cout <<"<html>" << std::endl;
  std::cout << "<h1>"<<x << " + " << y <<" = " << x + y <<"</h1>"<< std::endl;
  std::cout << x << " - " << y <<" = " << x - y << std::endl;
  std::cout << x << " * " << y <<" = " << x * y << std::endl;
  std::cout << x << " / " << y <<" = " << x / y << std::endl;
  std::cout << "</html>" << std::endl;
}
//119.29.193.229:8081/cgi/test_cgi/?a=100&b=200
int main()
{
  //对于getenv这个成功就返回地址,失败就返回nullptr
  std::string method;
  std::string query_string;
  if(getenv("METHOD")){
    method = getenv("METHOD");
  }
  else{
    return 1;
  }

  if(strcasecmp(method.c_str(),"GET") == 0)
  {
    //可以通过环境变量直接的获取参数
    query_string = getenv("QUERY_STRING");
  }
  else if(strcasecmp(method.c_str(),"POST") == 0){
    //cgi -> 0 读取
    //cgi -> 1 写入
    int cl = std::atoi(getenv("CONTENT-LENGTH"));
    char c = 0;
    while(cl){
      read(0,&c,1);
      query_string.push_back(c);
      cl--;
    }
  }

  //处理数据
  CalDate(query_string);
  return 0;
}
test_cgi:test_cgi.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean 
clean:
	rm -f test_cgi 

在这里插入图片描述
路径+s+……:把某些东西交给s程序,S程序做搜索,

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值