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.pswp.cn/web/13689.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/13689.shtml
英文地址,請注明出處:http://en.pswp.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首次進入觸發器時調用…