C++实现基于http协议的epoll非阻塞模型的web服务器框架(支持访问服务器目录下文件的解析)

使用方法:

编译

例子:./httpserver 9999 ../ htmltest/

可执行文件  +端口 +要访问的目录下的

例子:http://192.168.88.130:9999/luffy.html

前提概要

http协议 :应用层协议,用于网络通信,封装要传输的数据,通过http协议组织的数据最终会是一个数据块多行数据,换行需要 \r\n

通信流程:

客户端:通过使用http传输数据发送给服务器通过http协议组织数据→得到一个字符串→发送给服务器接受数据→根据http协议解析→得到原始数据→处理服务器端:接受数据→通过http协议解析→得到原始数据→处理回复数据→通过http协议组织数据→得到一个字符串→发送给客户端

http协议分成两部分:

http请求:

客户端发送给服务器的一种数据格式

http响应:

服务器端回复客户端的一种格式

http请求

客户端给服务器发送的一种数据格式,可以分为四部分

1.请求行 指定提交数据的方式有两种提交的方式:**get**:简单 **post**:复杂2. 请求头 多个键值对客户端给服务器发送的身份的描述符3. 空行 4.请求的数据向服务器提交的数据
这是网页用GET 发过来的请求

第一行:请求行用的GET

第一部分:GET :提交的数据的方式

第二部分:中间的橙色的字符

/ :访问的服务器资源目录,/ →代表资源根目录? :后面的内容:客户端向服务器端提交的数据key=value

第三部分 :HTTP/1.1 →http协议的版本

第二-第八行: 请求头

若干个键值对,每一个键值对占一行,使用\r\n换行

第九行是:空行

用post请求

第一行:请求行

post:提交数据的方式

/:作为客户端访问了服务器的什么目录,资源的根目录

http 、1.1http协议的版本

第二行-12请求头

第13行:空行

第14 行:客户端向服务器提交的数据

GET与POST的区别

功能上:

get

作为客户端向服务器申请访问静态资源(网页,图片,文件)

post :

向服务器提交动态数据用户登录信息上传下载文件

从操作的数据量来说:

get:

比较少,使用get向服务器提交的数据在请求行的第二部分在请求第二部分的时候需要显示到浏览器的地址栏中浏览器的地址栏的缓存很小,谷歌默认7k左右,数据量小

post :

可以操作大数据文件上传(大文件)post 提交数据放到了请求协议的第四部分

安全性:

get :

提交的数据会显示到浏览器的地址栏中,容易泄露

post :

不会泄露,提交数据不再浏览器的地址栏中

http响应

服务器给客户端回复数据

http响应的组成部分→4个部分

状态行

响应头(包头)

n个键值对里面的信息是服务器发送给客户端

空行

响应的数据,根据客户端请求给客户端回复的数据

第一行 :状态行

HTTP 、1.1 http协议版本

200:状态码

ok :对应状态码的描述

第二-九行 :响应头

content-type :服务器给客户端的数据快的格式==http协议的第四块的数据格式

text、plain→纯文本charset =iso-8859-1→数据的字符编码iso-8859-1→不支持中文utf 支持中文

content-length :服务器给客户端的数据快的长度==http协议的第四块的数据块的长度,总字节数;不知道写-1;

http状态码:

3.web服务器实现

 客户端:浏览器

通过浏览器访问服务器: -访问方式:

服务器的IP地址:端口 应用层协议使用:http,数据需要在浏览器端使用该协议进行包装响应消息的处理也是浏览器完成的 => 程序猿不需要管-客户端通过ur1访问服务器资源

-客户端访问的路径:http://192.168.1.100:8989/或者http://192.168.1.100:8989

**[访问服务器提供的资源目录的根目录](http://192.168.1.100:8989/或者http://192.168.1.100:8989访问服务器提供的资源目录的根目录)**并不是服务器的 / 目录

#### 服务器端:

提供服务器,让客户端访问

支持多客户端访问

-使用I0多路转接=>epo11

客户端发送给的请求消息是基于http的 -需要能够解析http请求 服务器回复客户端数据,使用http协议封装回复的数据=>http响应

服务器端需要提供一个资源目录,目录中的文件可以供客户端访问

客户端访问的文件没有在资源目录中,就不能访问了

假设服务器端提供的目录:/home/robin/luffy

 代码展示

main()函数

/*************************************************************************> File Name: main.cpp> Author:Wux1aoyu>  > Created Time: Fri 17 May 2024 05:02:16 AM PDT************************************************************************/#include"sever.h"
using namespace std;
// 原则上 main 函数只是逻辑函数调用,具体的内容不会写在这里面
//代码量少
int main(int argc,char *argv[]){//启动服务器->epollif(argc<3){cout<<"./a.out port path\n"<<endl;exit(0);}//argv[2]是path的路径 //将进程进入到当前的目录相当于cdchdir(argv[2]);//启动服务器 -》基于epoll ET 非阻塞unsigned short port=atoi(argv[1]);// ./后面的参数epollrun(port);
}

头文件


#ifndef SERVER_H
#define SERVER_H#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <strings.h>
#include<dirent.h>using namespace std;#ifdef __cplusplus
extern "C" {
#endif// 初始化监听的文件描述符
int initlistenFd(unsigned short port);// 启动 epoll 模型
int epollrun(unsigned short port);// 建立新连接
int acceptConn(int lfd, int epfd);// 接收 HTTP 请求
int recvHttprequest(int cfd, int epfd);// 解析请求行
int parserequestline(const char *requline, int cfd);// 发送头信息
int sendHeadmsg(int cfd, int status, const char *descr, const char *type, int length);//发送目录
int senddir(int cfd,char*dirname);// 发送文件
int sendFile(int cfd, const char *file);// 断开连接
int disconnect(int cfd, int epfd);#ifdef __cplusplus
}
#endif#endif // SERVER_H

 服务器端: sever.cpp

#include"sever.h"
//初始化监听套接字
int initlistenFd(unsigned short port)
{//1.创建监听的套接字int lfd=socket(AF_INET,SOCK_STREAM,0);if(lfd==-1){perror("socket");return -1;}//2. 端口复用//如果服务器主动断开链接,那么将会进入TIME_WAIT 状态,等待2msl,这个时间太长了,所以就设置端口复用,继续使用端口复用,使客户端用这个端口链接,但是上一个仍处于TIME_WAIT int opt=1;int ret = setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));if(ret==-1){perror("ret");}//3.绑定//设置文件描述符的地址ip端口struct sockaddr_in addr;addr.sin_family=AF_INET;//IPV4addr.sin_port=htons(port);addr.sin_addr.s_addr=INADDR_ANY; //0地址ret=bind(lfd,(sockaddr*)&addr,sizeof(addr));if(ret==-1){perror("bind");return -1;}//4.设置监听ret=listen(lfd,128);if(ret==-1){perror("listen");return -1;}//5.返回可用的监听的套接字return lfd;}//启动epoll模型
int epollrun(unsigned short port){//初始化epoll模型int epfd=epoll_create(1000);//创建epoll树if(epfd==-1){perror("create");return -1;}//初始化epoll树,将监听lfd添加上树int lfd=initlistenFd(port);struct epoll_event ev;//事件结构体ev.events=EPOLLIN;//检查读事件ev.data.fd=lfd;//将lfd添加属性中//添加上树int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);if(ret==-1){perror("epoll_ctl-add");return -1;}//检测,循环检测,边沿ET模式,epoll非阻塞struct epoll_event evs[1024];int size=sizeof(evs)/sizeof(int);int flag =0;while (1){if(flag==1){break;}int num=epoll_wait(epfd,evs,size,0);//非阻塞进行//遍历发生可读事件的变化的数组for (int  i = 0; i < num; i++){int curfd=evs[i].data.fd;//临时变量找到变化的文件描述符if(curfd==lfd)//如果使监听套接字发生变化,一定是客户端请求链接{//建立链接int ret= acceptConn(curfd,epfd);if(ret==-1){//建立链接失败直接终止程序flag=1;break;}}else{//通信//接受http请求recvHttprequest(curfd,epfd);}}}return 0;
}//和客户端建立新连接,并且将通信文件描述符设置成非阻塞属性
int acceptConn(int lfd,int epfd){//建立链接int cfd=accept(lfd,NULL,NULL);if(cfd==-1){perror("accept");return -1;}//设置通信文案描述属性为非阻塞int flag=fcntl(cfd,F_GETFL);flag|=O_NONBLOCK;fcntl(cfd,F_SETFL,flag);//通信套接字添加到epoll模型上struct epoll_event ev;ev.data.fd=cfd;ev.events=EPOLLIN | EPOLLET;//事件为边沿属性,检查读缓冲区;int ret =epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);if(ret==-1){perror("epoll_ctl");return -1;}}//和客户端断开新链接
int disconnect(int cfd,int epfd){//将节点从epoll模型删除int ret =epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);//删除操作最后一个制空if(ret==-1){perror("epoll_ctl_del");return -1;}//关闭通信套接字close(cfd);return 0;
}
//接受客户端http的请求消息
int recvHttprequest(int cfd,int epfd){//因为是边沿非阻塞模型,所以要一次性循环读char tmp[1024];//每次读1k数据char buf[4096];//每次把读的数据存到这个缓冲区里面//循环读数据int len,total=0;//total 是当前的buf的数据//客户端申请的都是静态资源,请求的资源内容,在请求行的第二部分//只需将请求完整的保存下来就可以//不需要解析请求头的数据,因此接受到之后不储存也是没问题的while((len=recv(cfd,tmp,sizeof(tmp),0))>0){if(len+total<sizeof(buf))//说明接受的和当前的还没超过缓冲区的大小{//有空间储存数据memcpy(buf+total,tmp,len);//从当前的数据往后加}total+=len;//当前的缓冲区的容量;buf[total] = '\0';}//循环结束了,说明读完了//非阻塞,缓存没有数据,返回-1,返回错误号if(len==-1&&errno==EAGAIN){//将请求行从接收的数据中拿出来 (http协议中他分了很多行,我们要拿第一行)//找到 \r\n就可以找到第一行char*pt= strstr(buf,"\r\n");//找到了\r\n之前的请求行int reqlen=pt-buf;//\r\n   的位置-首地址的位置//保留请求行buf[reqlen]='\0';//截断了//此时buf里面存在的是http的请求行的内容//解析请求行parserequestline(buf,cfd);}else if(len==0){cout<<"客户端断开连接了....."<<endl;//服务器和客户端也断开,cfd,从epoll删除文件描述符disconnect(cfd,epfd);}else{perror("recv");return -1;}return 0;}//解析请求行
int parserequestline(const char *requline,int cfd){//请求行分为三部分//GET /HELLO/WORLD/HTTP/1.1//1.拆分请求行,有用的是前两部分//提交数据的方式//客户端向服务器请求的文件名//拆分用正则表达式 sscanfchar method[5]; //POST GET char path[1024]; //存储的是目录文件地址sscanf(requline,"%[^ ] %[^ ]",method,path);//2. 判断请求的方式是不是get' ,不是get 直接忽略if(strcasecmp(method,"get")!=0){cout<<"用户提交不是get请求"<<endl;return -1;}//3. 判断用户访问的是文件还是目录// /HELLO/WORLD/ ,判断是不是  用statchar *file=NULL;if(strcmp(path,"/")==0){ //就是比较是不是/file="./";}else{file=path+1; //"./" +1 就是从h开始的//   hello/a.txt == ./hello/a.txt 这个目录等价   加.比较麻烦,如果什么都不加,就是从根目录找了}//属性判断 是不是文件或者目录struct stat st;//传出参数int ret=stat(file,&st);if(ret==-1){//判断失败//无文件发送404给客户端sendHeadmsg(cfd,404,"not found","text/html",-1);sendFile(cfd,"404.html");}if(S_ISDIR(st.st_mode)){//如果是目录的话将目录内容发送给客户端}else{//如果是普通文件,发送文件,把头信息发出去sendHeadmsg(cfd,200,"ok","text/html",st.st_size); //这里我们默认传输html文件sendFile(cfd,file);}return 0;
}//发送头信息
int sendHeadmsg(int cfd,int status,const char *descr,const char*type,int length){//状态行 +消息包头 +空行char buf[4096];//http/1.1 200 oksprintf(buf,"http/1.1 %d %s\r\n",status,descr);//消息包头 ->这里只需两个键值对//content-type /content-length   https://tool.oschina.net/commons去这里查sprintf(buf + strlen(buf), "Content-Type: %s\r\n", type);sprintf(buf + strlen(buf), "Content-Length: %d\r\n\r\n", length);// 空行//拼接完成之后发送send(cfd,buf,strlen(buf),0);//非阻塞return 0;}int sendFile(int cfd,const char *file){//读文件,发送给客户端//在发送内容之前应该有状态+消息包头,+空行+文件内容//这四部分数据组织好之后再发送数据吗?//不是 为什么,因为传输层默认人是tcp的//面向连接的流式传输协议-》只有最后全部发送完就可以int fd=open(file,O_RDONLY);//只读while (1){char buf[1024];int len=read(fd,buf,sizeof(buf));if(len>0){//发送读出的数据send(cfd,buf,len,0);}else if(len==0){//文件读完了break;}else{perror("read");return -1;}}return 0;
}

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

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

相关文章

npm install [Error]

npm install 依赖的时候报错 依赖版本问题的冲突&#xff0c;忽视即可 使用 npm install --legacy-peer-deps

剪画小程序:3个分离人声提取小技巧,赶紧收藏起来吧!

Hello&#xff01;大家好呀&#xff01;这里是社会主义搬砖人小画&#xff01; 人声分离&#xff0c;是指将混合在一起的人声和其他声音&#xff08;如背景音乐、环境噪音等&#xff09;分离开来&#xff0c;提取出单独的人声部分的过程。 在实际应用中&#xff0c;人声分离技…

leetcode654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索

654.最大二叉树 构造树一般采用的是前序遍历&#xff0c;因为先构造中间节点&#xff0c;然后递归构造左子树和右子树 确定递归函数的参数和返回值 参数传入的是存放元素的数组&#xff0c;返回该数组构造的二叉树的头结点&#xff0c;返回类型是指向节点的指针。 TreeNode…

Unity 开发Hololens,制作面板跟随眼镜一起移动,(面板跟踪)

Hololens滑动框以及面板跟踪 创建空物体&#xff0c;并添加组件 SolverHandler、RedialView、FollowMeToggle 创建按钮&#xff0c;控制停止/开始跟踪 创建一个Hololens自带的按钮放到右上角&#xff0c;并添加事件 创建蓝色背景板 创建空物体Backplate&#xff0c;下面再…

个体因果效应估计|EDVAE:用于个体治疗效果估计的反事实推理中的解开潜在因素模型

【摘要】根据观察数据估计个体治疗效果&#xff08;ITE&#xff09;是一项至关重要但具有挑战性的任务。解缠结表示已用于将代理变量分为混杂变量、工具变量和调整变量。然而&#xff0c;根据观测数据准确地进行反事实推理来识别 ITE 仍然是一个悬而未决的问题。在本文中&#…

AppInventor2要在界面上做一个电量图标,有什么好的思路吗?

问&#xff1a;要在界面上做一个电量图标&#xff0c;有什么好的思路吗&#xff1f; 答&#xff1a;首先&#xff0c;很容易想到使用进度条相关的组件&#xff0c;原生”滑动条“组件可以吗&#xff1f; 答案显而易见&#xff0c;首先它的样式自定义不够&#xff0c;UI不外乎上…

STM32_ADC

1、ADC简介 ADC&#xff0c;即Analog-Digital Converter&#xff0c;模拟-数字转换器。 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁。 12位逐次逼近型ADC&#xff0c;1us转换时间。 输入电压范围&#xff1a;0~3.3…

P6【力扣144,94,145】【数据结构】【二叉树遍历】C++版

【144】二叉树的前序遍历 1、递归法&#xff1a; class Solution { public:void preorder(TreeNode* root, vector<int> &res){if(root nullptr){return;}res.push_back(root->val);preorder(root->left, res);preorder(root->right, res);}vector<in…

没有密码如何卸载卡巴斯基?

如果忘记卡巴斯基6.0的保护密码&#xff0c; &#xff08;1&#xff09;进入安全模式下 &#xff08;2&#xff09;打开6.0的安装目录 Kaspersky Anti-Virus 6.0: C://Program Files//Kaspersky Lab//Kaspersky Anti-Virus 6.0 &#xff08;3&#xff09;将目录中的avp.exe改…

CVE-2020-7982 OpenWrt 远程命令执行漏洞学习(更新中)

OpenWrt是一款应用于嵌入式设备如路由器等的Linux操作系统。类似于kali等linux系统中的apt-get等&#xff0c;该系统中下载应用使用的是opgk工具&#xff0c;其通过非加密的HTTP连接来下载应用。但是其下载的应用使用了SHA256sum哈希值来进行检验&#xff0c;所以将下载到的数据…

开发过程中使用MySQL和Oracle的差异

前言 小型项目中使用MySQL的占比还是相对较高的&#xff0c;但是也不排除随着项目的扩大&#xff0c;产品的丰富&#xff0c;或者甲方的财大气粗&#xff0c;有可能会有MySQL换成Oracle。那么这两者对于开发者而言&#xff0c;有什么差异化的地方呢。 官方文档 MySQL5.7 htt…

weblogic简介

WebLogic是美国Oracle公司出品的一个Application Server&#xff0c;它是一个基于JAVA EE架构的中间件。WebLogic主要用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。它将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的…

什么是安全左移如何实现安全左移

文章目录 一、传统软件开发面临的安全挑战二、什么是安全左移四、安全左移与安全开发生命周期&#xff08;SDL&#xff09;三、安全左移对开发的挑战五、从DevOps到DevSecOps六、SDL与DevSecOps 一、传统软件开发面临的安全挑战 传统软件开发面临的安全挑战主要包括以下几个方…

yarn常用命令

Yarn 是一个快速、可靠且安全的依赖管理工具&#xff0c;用于替代 npm。以下是一些常用的 Yarn 命令&#xff0c;用于不同的包管理和项目依赖安装场景&#xff1a; 初始化一个新的项目 yarn init这个命令会引导你创建一个 package.json 文件。 安装依赖 yarn add [package]…

抄表:现代生活中的数据采集关键

1.界定与发源 抄表&#xff0c;简单的说&#xff0c;指从各种各样计量机器设备(如智能水表、电度表、天然气表等)载入做好记录使用量的全过程。这一概念自工业化时代至今就出现了&#xff0c;最初由人工进行&#xff0c;伴随着科技创新&#xff0c;如今已经演化出自动化和远程…

Java中的时间戳【详解】

一.何为Java时间戳 在Java中&#xff0c;时间戳通常指的是自1970年1月1日午夜&#xff08;UTC&#xff09;以来的毫秒数。 这个概念在Java中主要通过java.util.Date类和java.sql.Timestamp类来表示 而在Java 8及以后的版本中&#xff0c;引入了新的日期时间API&#xff0c;即…

给大家分享一套非常棒的python机器学习课程

给大家分享一套非常棒的python机器学习课程——《AI小天才&#xff1a;让小学生轻松掌握机器学习》&#xff0c;2024年5月完结新课&#xff0c;提供配套的代码笔记软件包下载&#xff01;学完本课程&#xff0c;可以轻松掌握机器学习的全面应用&#xff0c;复杂特征工程&#x…

【C++刷题】优选算法——递归第三辑

floodfill篇 图像渲染 unordered_multimap<int, int> direction {{0, 1},{0, -1},{1, 0},{-1, 0} }; void dfs(vector<vector<int>>& image, int sr, int sc, int color, int val) {image[sr][sc] color;for(auto& e : direction){int x sr e.…

关于微服务的一点感悟和过往经验的思考

一、为什么有微服务 解决单体应用的局限性 随着业务发展&#xff0c;业务逻辑复杂、关联方多&#xff0c;导致业务系统的代码臃肿、难于做迭代或者维护&#xff0c;导致很多的问题&#xff0c;如&#xff1a;bug多、难于维护修复、每次需要评估改动服务接口影响的范围&#xf…

碰撞器触发事件(OnTriggerEnter/OnTriggerStay/OnTriggerExit)

碰撞器触发事件&#xff08;OnTriggerEnter/OnTriggerStay/OnTriggerExit&#xff09;简介 在Unity中&#xff0c;触发器事件是当一个游戏对象进入、停留或离开另一个游戏对象的触发器碰撞器时发生的事件。这些事件分别是: OnTriggerEnter: 当其他Collider首次进入触发器时调用…