用C++写一个http服务器

嵌入式技术

1368人已加入

描述

 

本篇文章不会涉及到很多复杂的概念,也没有写很难读懂的模板函数,代码简单可读,本篇文章送给每一个想自己用C++写一个http服务器的小伙伴!高手们、大佬们当然可以不用看的啦!

正文

怎么写一个简单的http服务器阿?很简单,只需要返回最基本的3个东西即可。

  • 状态码

  • 发送文件的长度

  • 发送文件的类型

 

状态码如200(找到请求文件)、404(未找到请求文件),我实现的也比较简单,就实现了这两个状态码。

文件长度比如客户请求的是index.html页面,浏览器如何知道收到的这个文件什么时候结束呢?就靠是文件的长度

文件类型 html的类型是html格式,css的是css格式,图片有图片的格式,zip有zip格式文件格式对应的文件类型表(https://tool.oschina.net/commons)

返回给客户端三个这种东西加上请求的文件即可(存在请求文件的情况下)可以了

Http工作流程

在这个部分大概介绍一下大概的http的工作流程。客户端通过网址访问到你的网站(一定要记住客户端是主动请求连接的,服务端是被动连接的),实则就是通过ip+port访问的,只不过http的默认端口号是80。比如你还没有域名、云服务器这些东西,那如何在本地测试呢?就是在浏览框输入ip:port,例如127.0.0.1:9996,按下回车就可以在本地访问自己的http服务器了。当然了http要给客户(请求者)一个首页,当客户没有指定网页,单纯的打出域名或者127.0.0.1:9996,就给他一个默认的首页,这也是我们要实现的事情。客户写了请求文件,我们来判断是否存在,存在就返回状态码200和请求文件的内容。不存在就直接返回404。那我们如何判断啊,拿最简单的GET为例子吧,其他也大同小异,有兴趣的同学可以自行研究其他的。我们利用正则表达式来解析客户发来的请求是GET还是POST还是其他请求,在解析出来要请求文件。再利用状态机思想来文件是否存在,若存在在判断文件的类型。大概的流程就是这些。

Http.h

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#pragma once#include #include #include #include #include 

class TcpClient;
class Http{        // 文件的根目录        const std::string path_ = "./www/dxgzg_src";        std::string filePath_;// 具体文件的绝对路径        std::string fileType_;// 请求文件的类型        std::string header_;  // http头response        int fileSize_;        int fileFd_;
        struct stat fileStat_;
        // POST请求的话将留言保存到本地文件中        bool isPostMode_ = false;public:        Http() = default;        void addHeader(const std::string& head);        void Header(bool flag);        // 把一些头文件的信息都加进来,只有成功的时候调用这个函数,        // 并返回文件中的数据        void processHead();
        // 把请求文件的路径加上        void addFilePath(const std::string& requestFile);
        // 获取文件的类型        void analyseFileType(const std::string& requestFile);

        bool analyseFile(const std::string& request);
        void SendFile(int clientFd,bool isRequestOk);
        bool fileIsExist();
        // 用户自定义的回调函数要正确的处理异常和自己负责关闭套接字        void ReadCallback(TcpClient* t);};

 

Http.cpp

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include "Http.h"#include "TcpClient.h"#include "Logger.h"

#include #include #include #include #include // 用于test#include 
using namespace std;void Http::addHeader(const string& head){    if (!head.empty())    {        header_ += head;        header_ += "
";    // Console.WriteLine("我在这里 head!= null" + header_);    }    // 自动加个结尾    else    {        header_ += "
";    // Console.WriteLine("我在这里 head == null" + header_);    }}
void Http::Header(bool flag){    // 判断要发送的头部 true 表示200 false 表示404    if(flag == true)    {         header_ = "HTTP/1.1 200 OK
";    }    else    {        header_ = "HTTP/1.1 404 NOTFOUND
Content-Length:0

";    }}
void Http::processHead(){    string ContentType = "Content-Type:";    if (fileType_ == "html")    {        ContentType += "text/html";    }    else if(fileType_ == "js")    {        ContentType += "application/x-javascript";    }    else if(fileType_ == "css")    {        ContentType += "text/css";    }    else if(fileType_=="jpg" || fileType_== "png")    {        ContentType += "image/" + fileType_;    }    else if (fileType_== "zip" || fileType_ == "tar")    {        ContentType += "application/" + fileType_;    }    addHeader(ContentType);
    // 代完善,要打开文件 filePath_是请求文件的路径    fileSize_= fileStat_.st_size;    string ContentLength = "Content-Length:" + to_string(fileSize_);    addHeader(ContentLength);    // 最后加了一个结尾    addHeader("");    // Console.WriteLine("process fileContent_:" + );}
void Http::addFilePath(const string& requestFile){    filePath_ += requestFile;}
void Http::analyseFileType(const string& requestFile){    for (int i = 0; i < requestFile.size(); ++i)    {        if (requestFile[i] == '.')        {            // 获取请求文件以什么结尾的            fileType_ = requestFile.substr(i + 1);        }    }}
bool Http::fileIsExist(){    fileFd_ = ::open(filePath_.c_str(),O_CLOEXEC | O_RDWR);    if (fileFd_ < 0)    {   // 说明为找到请求的文件        return false;    }    return true;}
bool Http::analyseFile(const string& request){    // 调用header的    // 在[]的^是以什么什么开头,放在[]里面的是非的意思    string pattern = "^([A-Z]+) ([A-Za-z./1-9-]*)";    regex reg(pattern);    smatch mas;    regex_search(request,mas,reg);    // 因为下标0是代表匹配的整体    if(mas.size() < 3){        LOG_INFO("不是正常请求");        // 啥都不是直接返回false        return false;    }    string requestMode = mas[1];    if(requestMode == "POST"){        isPostMode_ = true;        cout << "POST请求!!!!!" << endl;    }    // 请求的具体文件    string requestFile = mas[2];    // 先获取请求的文件
    bool flag;    if (requestFile == "/")    { // 如果是/的话就给默认值        filePath_.clear(); // 先清个零        filePath_ = path_;        filePath_ += "/run.html";        // 文件的类型也要给人家加上        fileType_ = "html";     }    else    {        filePath_.clear(); // 先清个零        filePath_ = path_;        addFilePath(requestFile);        // 利用open函数            }    flag = fileIsExist();    // 未找到文件的话    if(!flag){        LOG_INFO("未找到客户要的文件");        cout << filePath_ << endl;        return false;    }    ::fstat(fileFd_,&fileStat_);    // 如果文件不存在的话也就不需要解析类型    analyseFileType(requestFile);    return true;}

void Http::SendFile(int clientFd,bool isRequestOk){    long len = 0;    // 头部一定是有的。    while(len < header_.size()){        len += ::send(clientFd,header_.c_str(),header_.size(),0);        cout << "len header" << header_ <<endl;    }    // 发完了头,在发请求文件的信息。如果是404这里是没有的    if (isRequestOk == true)    {        len = 0;  int num = 0;         int tmpLen = 0;// 连续好几次没变的话就加一个num   while (len < fileSize_)        {      // 发送的文件个数已经写入在len当中了             ::sendfile(clientFd,fileFd_,(off_t*)&len,fileStat_.st_size- len);            cout << "len sendfile" <<"len:" << len << "fileSize" << fileSize_ <<endl;            if(len <= 0 ){    break;      }      if(tmpLen == len){    ++num;    if(num > 10){      break;    }      }      tmpLen = len;  }
    }
}
void Http::ReadCallback(TcpClient* t){    cout << "ReadCallback" << endl;    int  sockFd = t->getFd();    char buff[1024];    int r = ::recv(sockFd,buff,sizeof(buff),0);    if (r == 0)    {        t->CloseCallback();        return;    }    buff[r] = '';    string str = buff;    cout << str << endl;    // 未找到文件直接回应404.    bool flag = analyseFile(str);    Header(flag);    if(!flag){        SendFile(sockFd,false);       // t->CloseCallback();        return ;    }    // 这个修改头文件的,先调用这个    processHead();    //这是文件找到了发送的    SendFile(sockFd,true);
    if(isPostMode_){        int fd = ::open("./postLog/message.txt",O_RDWR);        if(fd < 0){            LOG_ERROR("未找到文件");                    }        else{            // 文件偏移到末尾            ::lseek(fd,0,SEEK_END);            ::write(fd,str.c_str(),str.size());            close(fd);        }        isPostMode_ = true;    }
    // 关闭文件套接字    close(fileFd_);    // 发完就关闭连接,主要是为了多去几个线程还能跑的快一些    //t->CloseCallback();}

不考虑高并发的情况,设计一个同步阻塞的epoll即可,看完http必备的三要素已经能够写出一个服务器了,我的底层socket采用的是自己封装的网络库,Reactor模型,one loop per thread的代码文件比较多,所以就没有放上来,但只要把状态码、文件类型(那一大段if)、文件的长度这三个实现了就可以搭建一个简易的http服务器了。可以利用sendfile零拷贝来发送文件

在补充一点就是,http协议是 结尾。最后还有一个 ,就比如404,HTTP/1.1 404 NOTFOUND Content-Length:0 ,最后面再跟一个 ,结束一段跟一个。


	

	
  审核编辑:汤梓红
 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分