Linux_网络项目_WEB服务器 实现HTTP类,获取HTTP报文,按行读取HTTP请求报文数据、处理HTTP基本框架,打印错误日志

1. 分析服务器获取的http报文,按行读取HTTP报文

在这里插入图片描述

在这里插入图片描述
上图是服务器获取的一个完整的http报文,这个http报文没有正文。

host:请求的目标主机IP和端口

Accept:可以接受的数据类型

重点是请求行 /不一定指的是请求服务器的根目录
在这里插入图片描述
一次完整的http请求过程:

  1. 读取请求(我们需要按照http协议规定方式(报头,有效载荷)进行读取),否则会出现粘包问题
  2. 分析请求
  3. 按照http响应格式构建响应并返回对端。

这里先完成第一步,请求协议的读取。

需要注意:
不同的平台的换行符不同,一共有三种换行的格式,为了兼容所以尽量不要使用C++自带的读取行数据的函数
①\n ②\r\n ③\r

这里设计到Util.h中专门做按行读取方法(在编码过程中需要的工具类函数实现位置)

在这里插入图片描述
recv的选项flags=MSG_PEEK时,recv会返回接受缓冲区对应大小的数据,但不会取走。(数据窥探)
通过这种方式来判断\r后面的字符是不是\n。且不会拿走\r后一个字符。

#pragma once 

//提供编码过程中所需要的工具函数 

#include<string>
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>

class Util{
  public:
    static int ReadLine(int sock,std::string&outBuff){
      char ch='D';//ch先随机赋不为'\n'、'\r'的值
      while(ch!='\n'){
        //将三种行结束符统一转化为\n换行
        ssize_t size=recv(sock,&ch,1,0);
        if(size>0){
          if(ch=='\r'){
            //特殊处理 \r->\n   \r\n->\n
            //查看\r后的字符,不取走
            recv(sock,&ch,1,MSG_PEEK);
            if(ch=='\n'){
              //是\r\n换行格式,重复recv即可
              recv(sock,&ch,1,0);//用\n将\r覆盖
            }
            else{
              //是\r的格式,修改ch
              ch='\n';
            }
          }
          outBuff.push_back(ch);//换行格式一定是\n
        }
        else if(size==0){
          return 0;//对端关闭
        }
        else{
          return -1;//读取错误
        }
      }
      return outBuff.size();//返回读取一行字符的个数
    }
};

2. 打印错误日志Log.h

错误信息打印格式如下
在这里插入图片描述
日志级别有:
INFO:正常显示
WARNING:警告
ERROR:错误
FATAL:致命,程序终止

错误时间:根据time函数获取时间戳即可

日志信息:错误,提升等信息,

错误文件与错误行数根据预定义符号来确定
在这里插入图片描述

采用宏函数形式,在编译的时候替换到调用处,可以保证文件名和行号就在对应的文件。

#pragma once 

#include<iostream>
#include<string>
#include<time.h>

#define INFO 1
#define WARNING 2
#define ERROE 3
#define FATAL 4

#define ERRORLOG(leve,mesage) ErrorLog(#leve,mesage,__FILE__,__LINE__)  //#作用为,将宏参数转化为字符串

void ErrorLog(std::string Leve,std::string msg,std::string FileName,int LineNum){
  std::cout<<"["<<Leve<<"]"<<"["<<time(NULL)<<"]"<<"["<<msg<<"]"<<"["<<FileName<<"]"<<"["<<LineNum<<"]"<<std::endl;
}
#include"Log.h"

int main(){
  ERRORLOG(INFO,"测试日志");
  return 0;
}

在这里插入图片描述
之后将需要日志的地方添加即可。

TcpSever:

#pragma once 

#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include"Log.h"

#define BACKLOG 5

class TcpSever{
  private:
    int port;
    int listen_sock;
    TcpSever(int _port):port(_port),listen_sock(-1){}//单例模式

    TcpSever(const TcpSever&)=delete;

    static TcpSever* inst;
  public:

    int GetListenSock(){return listen_sock;}

    static TcpSever*GetInstance(int _port){
      static pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER; //静态锁直接初始化,不需要释放     
      if(inst==nullptr){
        pthread_mutex_lock(&lock);
        if(inst==nullptr){
          inst=new TcpSever(_port);
          inst->InitTcpSever();
        }

        pthread_mutex_unlock(&lock);
      }
      return inst;
    }

    void InitTcpSever(){
      Socket();
      Bind();
      Listen();
      ERRORLOG(INFO,"TcpSever Init Success!!");
    }

    void Socket(){
      listen_sock=socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock<0){
        ERRORLOG(FATA,"sock error");
        exit(1);
      }
      //socket地址复用
      int opt=1;
      setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
      ERRORLOG(INFO,"creat sock...");
    }

    void Bind(){
      struct sockaddr_in local;
      memset(&local,0,sizeof(local));

      local.sin_family=AF_INET;
      local.sin_port=htons(port);
      local.sin_addr.s_addr=INADDR_ANY;
      if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){
        ERRORLOG(FATA,"bind error");
        exit(2);
      }
      ERRORLOG(INFO,"bind ...");
    }

    void Listen(){
      if(listen(listen_sock,BACKLOG)<0){
        ERRORLOG(FATA,"listen error");
        exit(3);
      }
      ERRORLOG(INFO,"listen ...");
    }

    ~TcpSever(){
      if(listen_sock>=0){
        close(listen_sock);
      }
    }
};

TcpSever*TcpSever::inst=nullptr;

在这里插入图片描述
使用浏览器请求后输出,多条的原因是因为此时还没有处理请求,浏览器没有收到响应会重复请求。
在这里插入图片描述

3. 获取HTTP报文,按行读取HTTP请求报文数据、处理HTTP基本框架

HttpSever:

#pragma once 


#include"TcpServer.h"
#include<pthread.h>
#include"Protocol.h"
#include"Log.h"

#define DEF_PORT 8081

class HttpSever{
  private:
    int port;
    bool IsStop;//表明当前服务是否运行
    TcpSever*tcp_inst;
  public:
    HttpSever(int _port=DEF_PORT):port(_port),IsStop(false),tcp_inst(nullptr){}

    void InitHttpSever(){
      tcp_inst=TcpSever::GetInstance(port);
    }

    void Start(){
      ERRORLOG(INFO,"Sever Start!");
      int listen_sock=tcp_inst->GetListenSock();
      while(!IsStop){
        sockaddr_in peer;
        socklen_t len=sizeof(peer);
        int sock=accept(listen_sock,(struct sockaddr*)&peer,&len);
        if(sock<0){
          ERRORLOG(ERROR,"accept error");
          continue;
        }
        ERRORLOG(INFO,"get a new sock");
        int* _sock=new int(sock);//目的让每个线程有自己独立套接字,如果直接给线程sock可能被修改,64位系统下会报警告
        pthread_t tid;
        pthread_create(&tid,nullptr,Entry::SolveQuest,_sock);
        pthread_detach(tid);
      }
    }

    ~HttpSever(){}
};

线程处理函数类(业务逻辑)Protocol.h:

#pragma once 


//已经存在套接字,线程通过套接字处理任务

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.h"
#include<string>
#include<vector>
#include"Log.h"

//HTTP响应报文
class HttpResponse{
  public:
    std::string StatusLine_HTTP;//状态行
    std::vector<std::string>ResponHeads;//首部字段
    std::string ResponBlank;//空行
    std::string ResponBody;//正文
};

//HTTP请求报文
class HttpRequest{
  public:
    std::string RequestLine_HTTP;//请求行
    std::vector<std::string>RequestHeads;//首部字段
    std::string RequestBlank;//空行
    std::string RequestBody;//正文
};


//读取请求,分析请求,构建响应,基本IO通信,实现基本业务逻辑
class EndPoint{
  private:
    int sock;
    HttpRequest http_request;//http请求
    HttpResponse http_response;//http响应
  private:
    void GetHttpRequestLine(){
      Util::ReadLine(sock,http_request.RequestLine_HTTP);//读取HTTP请求第一行
    }
  public:
    EndPoint(int _sock):sock(_sock){}

    void RecvQuest_HTTP(){//读取请求

    }

    void AnalyQuest_HTTP(){//解析请求
    }

    void MakeRespon_HTTP(){//构建响应

    }

    void SendRespon_HTTP(){//发送响应

    }

    ~EndPoint(){}
};

class Entry{//线程执行任务的入口
  public:
    static void*SolveQuest(void*_sock){
      ERRORLOG(INFO,"Processing Requests...");
      int sock=*(int*)_sock;
      delete(int*)_sock;
      //std::cout<<" Get a New Link: sock="<<sock<<std::endl;
      EndPoint* endpoint=new EndPoint(sock);
      endpoint->RecvQuest_HTTP();
      endpoint->AnalyQuest_HTTP();
      endpoint->MakeRespon_HTTP();
      endpoint->SendRespon_HTTP();
      delete endpoint;
      ERRORLOG(INFO,"Processing Request End!");
      return nullptr;
    }
};

Github项目代码位置
Gitee项目代码位置

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值