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;人声分离技…

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…

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

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

weblogic简介

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

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

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

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

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

服务端Web资源缓存

1.前言 虽然客户端缓存效果很好&#xff0c;但它有一个核心问题&#xff1a;要在本地提供资源&#xff0c;必须先将其存储在缓存中。因此&#xff0c;每个客户端都需要其缓存的资源。如果请求的资源需要大量计算&#xff0c;则无法扩展。服务器端缓存背后的理念是计算一次资源…

第10章 软件架构的演化和维护

软件架构周期&#xff1a;初始设计、实际使用、修改完善(这就是演化)、退化弃用。 演化和维护的目的&#xff1a;为了使软件能够适应环境的变化而进行的纠错性修改和完善性修改等&#xff0c;而且这个过程是一个不断迭代的过程。 架构演化的重要性、演化过程、演化分类、演化…

Java——通过方法交换实参值

想写一个方法来交换main函数中的两个变量值&#xff0c;代码如下&#xff1a; public class Test {public static void swap(int x,int y) {int tmp x;x y;y tmp;}public static void main(String[] args) {int a 10;int b 20;System.out.println("交换前&#xff1…

Autodesk Maya 2025软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; Autodesk Maya 2025是一款领先的三维动画设计软件&#xff0c;界面直观且功能丰富。它集成了全球领先的3D设计技术&#xff0c;提供了多种创意功能&a…

深度学习 --- stanford cs231 编程作业(如何在chrome中安装colab)

stanford cs231 编程作业(如何开始你的colab编程&#xff09; 斯坦福231n的所有作业都要求在colab里面做&#xff0c;colab可以为你提供免费的云计算。实际上在他的官网中也有关于如何安装colab的详细说明视频。 https://youtu.be/DsGd2e9JNH4https://youtu.be/DsGd2e9JNH4 我…

电路笔记 :元器件焊接相关 酒精灯松香浴加热取芯片

记录一下只使用松香和小火源加热&#xff08;如酒精灯、小蜡烛&#xff09;从电路板中取芯片。 过程 多放松香 让松香淹没芯片尽量均匀加热&#xff0c;等芯片旁边的松香开始从芯片里冒细小的“泡泡”&#xff0c;就差不多了 注&#xff1a;这种方法也可以用于焊接&#xff0…

Qt QString详细用法

一.基础用法 1.创建QString对象 QString str1 "Hello, World!"; QString str2("This is a QString object."); //一个是等号的重载&#xff0c;一个是拷贝构造&#xff0c;本质上是等价的 2.获取字符串长度 int length str1.length(); // 返回字符串…

大模型落地竞逐,云计算大厂“百舸争流”

作者 | 辰纹 来源 | 洞见新研社 从ChatGPT到Sora&#xff0c;从图文到视频&#xff0c;从通用大模型到垂直大模型……经过了1年多时间的探索&#xff0c;大模型进入到以落地为先的第二阶段。 行业的躁动与资本的狂热相交汇&#xff0c;既造就了信仰派的脚踏实地&#xff0c;也…

7.从0做一个vue键盘组件

文章目录 1. 从0做一个键盘组件1.1. 最终效果1.2. 分析1.3. 实现1.4. 如何引用 1. 从0做一个键盘组件 首先是why的问题&#xff1a;为什么需要做键盘组件&#xff1f; 我们目前可知的场景&#xff1a; 在新增账单的时候&#xff0c;需要用到键盘在比如从账单列表页&#xff…