用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方法重载和重载方法_Java 8的方法参考进一步限制了重载

java方法重载和重载方法方法重载一直是一个充满喜忧参半的话题。 我们已经在博客上介绍了它&#xff0c;并介绍了几次警告&#xff1a; 您会后悔对Lambdas应用重载&#xff01; 保持干燥&#xff1a;方法重载 为什么每个人都讨厌操作员超载 API设计师&#xff0c;请小心 重…

在python中字符串可以使用什么来表示_Python 字符串定义

例如&#xff1a;’string’、”string”、”””string”””或者是”’string”’。在使用上&#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;力争…

spring 启动进度_在Web浏览器中显示Spring应用程序启动的进度

spring 启动进度重新启动企业应用程序时&#xff0c;客户打开Web浏览器时会看到什么&#xff1f; 他们什么也没看到&#xff0c;服务器还没有响应&#xff0c;因此Web浏览器显示ERR_CONNECTION_REFUSED 应用程序前面的Web代理&#xff08;如果有&#xff09;注意到它已关闭&a…

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;高手…

rest服务swagger_使用Swagger轻松记录您的Play Framework REST API

rest服务swagger该帖子最初在http&#xff1a;// swag ger.io&#xff08;7/30/2015&#xff09;上发布 我一直在使用Play Framework作为多个项目的基于Java的&#xff0c;闪电般的REST后端框架。 后来&#xff0c;我很高兴找到Swagger&#xff0c;并努力将其集成到几个项目中…

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

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

python使用欧氏距离knn_python运用sklearn实现KNN分类算法

KNN(K-Nearest-Neighbours Classiflication)分类算法&#xff0c;供大家参考&#xff0c;具体内容如下最简单的分类算法&#xff0c;易于理解和实现实现步骤&#xff1a;通过选取与该点距离最近的k个样本&#xff0c;在这k个样本中哪一个类别的数量多&#xff0c;就把k归为哪一…

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

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