1. 分析服务器获取的http报文,按行读取HTTP报文
上图是服务器获取的一个完整的http报文,这个http报文没有正文。
host:请求的目标主机IP和端口
Accept:可以接受的数据类型
重点是请求行 /不一定指的是请求服务器的根目录
一次完整的http请求过程:
- 读取请求(我们需要按照http协议规定方式(报头,有效载荷)进行读取),否则会出现粘包问题
- 分析请求
- 按照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;
}
};