用C++写一个http服务器/web服务器

点击蓝字

083e225fd0393145ac757658599f02f7.png

关注我们

来源于网络,侵删

本篇文章不会涉及到很多复杂的概念,也没有写很难读懂的模板函数,代码简单可读,本篇文章送给每一个想自己用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 <string>
#include <unordered_map>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>class TcpClient;class Http{// 文件的根目录const std::string path_ = "./www/dxgzg_src";std::string filePath_;// 具体文件的绝对路径std::string fileType_;// 请求文件的类型std::string header_;  // http头responseint 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 <regex>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <unordered_map>
// 用于test
#include <iostream>using namespace std;
void Http::addHeader(const string& head)
{if (!head.empty()){header_ += head;header_ += "\r\n";// Console.WriteLine("我在这里 head!= null" + header_);}// 自动加个结尾else{header_ += "\r\n";// Console.WriteLine("我在这里 head == null" + header_);}
}void Http::Header(bool flag)
{// 判断要发送的头部 true 表示200 false 表示404if(flag == true){ header_ = "HTTP/1.1 200 OK\r\n";}else{header_ = "HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n";}
}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("不是正常请求");// 啥都不是直接返回falsereturn 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;// 连续好几次没变的话就加一个numwhile (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] = '\0';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协议是\r\n结尾。最后还有一个\r\n,就比如404,HTTP/1.1 404 NOTFOUND\r\nContent-Length:0\r\n\r\n,最后面再跟一个\r\n,结束一段跟一个\r\n

5e0ca237e66911e5ed80b3ec975dbd1f.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

fb9b358b10885de008bcedf453bb944f.gif

戳“阅读原文”我们一起进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337274.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

repl java9_Java 9抢先体验:与JShell进行动手实践– Java REPL

repl java9从今天开始&#xff0c;如何开始使用Java 9的最酷功能之一&#xff1f; 上周末&#xff0c;我终于开始尝试使用Java 9的早期访问版本。第一站是JShell&#xff0c;它也被称为Project Kulla。 首先让我鼓起勇气尝试早期访问Java版本的原因。 那就对了。 Java 9的正式…

java iterator获取索引_2020年Java面试题最新整理(1625)

16.Java集合框架是什么&#xff1f;说出一些集合框架的优点&#xff1f;每种编程语言中都有集合&#xff0c;最初的Java版本包含几种集合类&#xff1a;Vector、Stack、HashTable和Array。随着集合的广泛使用&#xff0c;Java1.2提出了囊括所有集合接口、实现和算法的集合框架。…

搞定红黑树(C++实现)

点击蓝字关注我们来源于网络&#xff0c;侵删红黑树的概念红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是红色或黑色。通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条…

java launcher_JAR清单类路径不仅适用于Java Application Launcher

java launcher自从我开始学习Java以来​​&#xff0c;我几乎已经知道&#xff0c; 清单文件中的Class-Path标头字段为可执行JAR &#xff08;具有由另一个称为Main-Class清单指定应用程序起点的 JAR&#xff09;指定了相对运行时类路径。 一个同事最近碰到一个让我感到惊讶&am…

C语言实现银行ATM存取款系统 | 附源码

点击蓝字关注我们来源于网络&#xff0c;侵删银行ATM存取款系统银行ATM存取款系统业务描述如下&#xff1a;银行ATM存取款系统能为用户提供存款、取款、查询、转账和修改密码的功能。为了模拟真实的ATM业务环境&#xff0c;本系统必须实现存款、取款、查询、转账、修改密码以及…

php 链接文件名_7、php-fpm进程管理

1、进程管理php-fpm采用的是master-worker的进程方式。其中&#xff0c;master负责fork worker进程;其次&#xff0c;注册信号&#xff0c;通过信号进行管理worker负责监听端口&#xff0c;等待链接&#xff0c;处理具体的逻辑如下图所示2、信号管理master进程可以理解如下信号…

C语言代码实现平衡二叉树|图解+详细代码

点击蓝字关注我们来源于网络&#xff0c;侵删1. 什么是平衡二叉树平衡二叉树&#xff0c;我们也称【二叉平衡搜索树/AVL】,树中任何节点的两个子树的高度最大差别为1&#xff0c;巴拉巴拉。。。(https://baike.baidu.com/item/AVL树/10986648?fraladdin)但是有个注意的点: 平衡…

cba比赛比分预测_【CBA直播】深圳vs广东前瞻:深圳战广东再掀反攻?

北京时间4月13日晚19点35分&#xff0c;CBA季后赛半决赛第三回合&#xff0c;深圳队主场迎战广东队。尽管目前双方总比分深圳以0-2落后对手&#xff0c;但他们在第二战的顽强表现给人留下了深刻印象。回归主场作战的他们&#xff0c;将在沈梓捷和贺希宁的带领下&#xff0c;力争…

C语言内存泄漏问题及其检视方法

点击蓝字关注我们来源于网络&#xff0c;侵删通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝内存泄漏导致的网上问题发生。本文通过介绍内存泄漏问题原理及检视方法&#xff0c;希望后续能够从编码检视环节就杜绝此类问题发生。预防内存泄漏…

未定义与 struct 类型的输入参数相对应的函数 fetch_引入鲁棒性作为连续参数,这种新损失函数实现了自适应、随时变换...

编辑&#xff1a;陈萍损失函数是机器学习里最基础也是最为关键的一个要素&#xff0c;其用来评价模型的预测值和真实值不一样的程度。最为常见的损失函数包括平方损失、指数损失、log 对数损失等损失函数。这里回顾了一种新的损失函数&#xff0c;通过引入鲁棒性作为连续参数&a…

清理jdk注册表_JDK 9早期版本安装后的Windows注册表清理

清理jdk注册表在我的上一篇博文中 &#xff0c;我演示了在安装早期版本的JDK 9&#xff08;内部版本68&#xff09;之后围绕Oracle Java符号链接 &#xff08;基于Windows的计算机上的C:\ProgramData\Oracle\Java\javapath\目录&#xff09;的问题的解决方案。这似乎阻止了早期…

汇编语言调用C语言/C++实例:乘法表

点击蓝字关注我们来源于网络&#xff0c;侵删现在编写一个简单的应用程序&#xff0c;提示用户输入整数&#xff0c;通过移位的方式将其与 2 的幕 (2〜2ⁿ) 相乘&#xff0c;并用填充前导空格的形式再次显示每个乘积。输入-输出使用 C。汇编模块将调用 3 个 C 编写的函数。程序…

rect函数_R函数不会写,quot;抄quot;总会吧!

前面我们简单的介绍了R函数。有些人可能会说&#xff0c;我现在的R水平有限&#xff0c;还不足以写出很高级的函数&#xff0c;该怎么办&#xff1f;俗话说前人栽树后人乘凉&#xff0c;他山之石可以攻玉&#xff0c;鲁迅同志也提出过“拿来”主义。已经有前人&#xff0c;高手…

10个超赞的C语言开源项目,强烈推荐!

点击蓝字关注我们来源于网络&#xff0c;侵删今天分享10个超赞的C语言开源项目&#xff0c;希望这些内容能对大家有所帮助&#xff01;目录&#xff1a;1. Webbench2. Tinyhttpd3. cJSON4. CMockery5. Libev6. Memcached7. Lua8. SQLite9. UNIX v610. NETBSD1. WebbenchWebbenc…

jboss性能指标_JBoss BRMS复杂事件处理(CEP)性能基准

jboss性能指标技术来了又去&#xff0c;但是一件事保持不变。 在设计企业解决方案时&#xff0c;我们喜欢使我们的生活更轻松的复杂组件&#xff0c;作为建筑师和开发人员&#xff0c;我们一直在寻找使我们的生活更轻松的方法。 一种方法是跟上与感兴趣的技术有关的流行新站点…

C语言经验分享:二维指针与二维数组的两种错误用法

点击蓝字关注我们来源于网络&#xff0c;侵删引子首先看一段代码:void test(int *p) {}int main() {int arr[] {30, 450,14,5};test(arr);return 0; }毫无疑问&#xff0c;上面这段代码是运行OK的。因为C语言标准中有以下规则:在函数参数的声明中&#xff0c;数组名被编译器当作…

camel 使用_使用Camel从WildFly 8向WebLogic 12发送JMS消息

camel 使用系统集成是一个很好的挑战。 特别是当您在寻找通信标准和可靠的解决方案时。 在当今的微服务世界中&#xff0c;每个人都在谈论REST服务和基于http的协议。 实际上&#xff0c;对于大多数通常具有更复杂的需求集的大多数企业项目来说&#xff0c;这是远远不够的。 合…

C++异常处理控制流下的OLLVM混淆

点击蓝字关注我们来源于网络&#xff0c;侵删Inflated!!!C异常化处理OLLVM-控制流平坦化Two PuzzlesException一般碰到C异常逆向&#xff0c;确定了异常分发、处理部分&#xff0c;直接把call throw改为jmp catch块&#xff0c;再F5即可。PS: 多个catch块根据rdx来当为异常处理…

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

C语言知识总结一:C语言的基本知识汇总

点击蓝字关注我们来源于网络&#xff0c;侵删C语言是一种计算机程序设计语言。它既有高级语言的特点&#xff0c;又具有汇编语言的特点。它可以作 为系统设计语言&#xff0c;编写工作系统应用程序&#xff0c;也可以作为应用程序设计语言&#xff0c;编写不依赖计算机 硬件的应…