C++初级项目-webserver(1)

1.引言

Web服务器是一个基于Linux的简单的服务器程序,其主要功能是接收HTTP请求并发送HTTP响应,从而使客户端能够访问网站上的内容。本项目旨在使用C++语言,基于epoll模型实现一个简单的Web服务器。选择epoll模型是为了高效地处理大量并发连接。

2.项目概览

这个项目的目标是实现一个简单的Web服务器,可以处理基本的HTTP请求并发送相应的HTTP响应。项目结构包括服务器初始化、Epoll模型的使用、事件处理循环、HTTP请求处理、文件发送、错误处理等关键模块。技术和工具方面使用了C++语言、epoll模型以及socket编程。

根据这个服务器可以实现下面的功能,打开Linux环境下的文件。

在浏览器上面的搜索栏输入http://192.168.44.3:9999/hanzi.c

192.168.44.3是Linux环境的本机IP地址,9999是端口号,hanzi.c是打开的文件名

3.Epoll模型

1. 基本概念和优势

  • Epoll简介:Epoll(Event Poll)是Linux内核为处理大量文件描述符而设计的一种高效的I/O事件通知机制。它允许程序监视多个文件描述符上的事件状态,而无需轮询这些文件描述符。

  • 优势:

    • 高效的事件通知机制:Epoll使用基于事件的机制,只有当事件发生时才会通知应用程序,避免了轮询的开销。
    • 支持大量并发连接: 适用于处理大量并发连接的场景,能够有效管理数以千计的文件描述符。
    • 适用于非阻塞I/O: 与非阻塞模型结合使用,使得应用程序能够同时处理多个连接而不被阻塞。

2. 创建Epoll树和添加文件描述符

// 创建epoll树
int epfd = epoll_create(1024);
if (epfd < 0) {perror("epoll_create error");close(lfd);return -1;
}// 将监听文件描述符lfd添加到epoll树上
struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

  • epoll_create: 创建一个epoll实例,返回一个用于标识该实例的文件描述符。
  • epoll_ctl: 控制epoll实例的行为,可以用于注册、修改或删除文件描述符。

3. Epoll事件处理循环

int nready;
struct epoll_event events[1024];
while (1) {// 等待事件发生nready = epoll_wait(epfd, events, 1024, -1);if (nready < 0) {if (errno == EINTR) {continue;}break;}for (int i = 0; i < nready; i++) {int sockfd = events[i].data.fd;// 处理监听文件描述符lfd上的事件if (sockfd == lfd) {// 接受新的客户端连接int cfd = Accept(lfd, NULL, NULL);// 设置cfd为非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);// 将新的cfd添加到epoll树上ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);} else {// 处理客户端数据http_request(sockfd);}}
}

  • epoll_wait: 等待事件发生,返回就绪事件的数量。
  • events数组: 存储发生事件的文件描述符和事件类型。
  • EPOLLIN: 表示文件描述符上有可读数据。
  • Accept函数: 用于接受新的客户端连接。
  • fcntl函数: 用于设置文件描述符的属性,将其设置为非阻塞。

通过这样的Epoll模型,服务器能够高效地处理并发连接,只在有事件发生时才进行相应的处理,避免了不必要的轮询。

4. 事件处理循环

1. 服务器主循环

服务器的主循环是一个持续运行的事件处理循环,通过调用等待事件的发生。一旦有事件发生,主循环将负责处理这些事件。epoll_wait

  • epoll_wait: 等待事件发生,返回就绪事件的数量。
  • events数组: 存储发生事件的文件描述符和事件类型。
  • EPOLLIN: 表示文件描述符上有可读数据。
  • Accept函数: 用于接受新的客户端连接。
  • fcntl函数: 用于设置文件描述符的属性,将其设置为非阻塞。

2. 处理连接请求和客户端数据

在主循环中,通过判断就绪事件的文件描述符,可以区分是监听文件描述符lfd上的连接请求还是客户端文件描述符上的数据到达事件。

// 处理监听文件描述符lfd上的事件
if (sockfd == lfd) {// 接受新的客户端连接int cfd = Accept(lfd, NULL, NULL);// 设置cfd为非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);// 将新的cfd添加到epoll树上ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
} else {// 处理客户端数据http_request(sockfd);
}

如果是监听文件描述符lfd上的事件,表示有新的客户端连接请求,通过函数接受连接,并将新的客户端文件描述符设置为非阻塞,然后将其添加到epoll树上,监听其读事件。Accept

如果是客户端文件描述符上的事件,表示有数据到达,调用函数处理客户端的HTTP请求。http_request

通过这样的事件处理循环,服务器能够实时响应连接请求### 事件处理循环.

5.HTTP请求处理


1. 解析HTTP请求行


在处理客户端数据时,首先需要解析HTTP请求行,提取请求类型、文件名和协议版本。这是通过读取客户端发送的数据并解析其中的信息来实现的。


此代码从客户端文件描述符sockfd中读取HTTP请求行数据,然后使用函数解析出请求类型(GET、POST等)、文件名和协议版本。这样,服务器就能了解客户端请求的基本信息。sscanf

2. 区分请求类型,处理GET请求
在得到请求类型后,服务器通常需要根据不同的请求类型采取不同的处理方式。以下是处理GET请求的简化示例:

//判断文件是否存在struct stat st;if(stat(pFile, &st)<0){printf("file not exist\n");//发送头部信息send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);//发送文件内容send_file(cfd, "error.html");   }else //若文件存在{//判断文件类型//普通文件if(S_ISREG(st.st_mode)){printf("file exist\n");//发送头部信息send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);//发送文件内容send_file(cfd, pFile);}//目录文件else if(S_ISDIR(st.st_mode)){}}


在这个例子中,如果是GET请求,服务器首先检查请求的文件是否存在。如果文件存在,就发送HTTP响应头,然后发送文件内容;如果文件不存在,就发送404错误页面。对于其他类型的请求(非GET请求),服务器返回501 Not Implemented的错误响应。

6.完整代码和项目包

webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>#include "pub.h"
#include "wrap.h"int http_request(int cfd);int main()
{//改变当前进程的工作目录char path[255] = {0};sprintf(path, "%s/%s", getenv("HOME"), "webpath");chdir(path);//创建socket--设置端口复用---bindint lfd = tcp4bind(9999, NULL);//设置监听Listen(lfd, 128);//创建epoll树int epfd = epoll_create(1024);if(epfd<0){perror("epoll_create error");close(lfd);return -1;}//将监听文件描述符lfd上树struct epoll_event ev;ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);int i;int cfd;int nready;int sockfd;struct epoll_event events[1024];while(1){//等待事件发生nready = epoll_wait(epfd, events, 1024, -1);if(nready<0){if(errno==EINTR){continue;}break;}for(i=0; i<nready; i++){sockfd = events[i].data.fd;//有客户端连接请求if(sockfd==lfd){//接受新的客户端连接cfd = Accept(lfd, NULL, NULL);//设置cfd为非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);//将新的cfd上树ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);}else{//有客户端数据发来http_request(cfd);}           }       }
}int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{char buf[1024] = {0};sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);if(len>0){sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);}strcat(buf, "\r\n");Write(cfd, buf, strlen(buf));return 0;
}int send_file(int cfd, char *fileName)
{//打开文件int fd = open(fileName, O_RDONLY);if(fd<0){perror("open error");return -1;}//循环读文件, 然后发送int n;char buf[1024];while(1){memset(buf, 0x00, sizeof(buf));n = read(fd, buf, sizeof(buf));if(n<=0){break;}else{Write(cfd, buf, n);}}
}int http_request(int cfd)
{int n;char buf[1024];//读取请求行数据, 分析出要请求的资源文件名memset(buf, 0x00, sizeof(buf));Readline(cfd, buf, sizeof(buf));printf("buf==[%s]\n", buf);//GET /hanzi.c HTTP/1.1char reqType[16] = {0};char fileName[255] = {0};char protocal[16] = {0};sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);printf("[%s]\n", reqType);printf("[%s]\n", fileName);printf("[%s]\n", protocal);char *pFile = fileName+1;printf("[%s]\n", pFile);//循环读取完剩余的数据while((n=Readline(cfd, buf, sizeof(buf)))>0);//判断文件是否存在struct stat st;if(stat(pFile, &st)<0){printf("file not exist\n");//发送头部信息send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);//发送文件内容send_file(cfd, "error.html");   }else //若文件存在{//判断文件类型//普通文件if(S_ISREG(st.st_mode)){printf("file exist\n");//发送头部信息send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);//发送文件内容send_file(cfd, pFile);}//目录文件else if(S_ISDIR(st.st_mode)){}}
}

本文用到了俩个库pub.h 和wrap.h 这俩个头文件

本文在提供了完整的代码包:https://download.csdn.net/download/qq_64691289/88547649

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

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

相关文章

力扣 622.设计循环队列

目录 1.解题思路2.代码实现 1.解题思路 首先&#xff0c;该题是设计循环队列&#xff0c;因此我们有两种实现方法&#xff0c;即数组和链表&#xff0c;但具体考虑后&#xff0c;发现数组实现要更容易一些&#xff0c;因此使用数组实现&#xff0c;因此我们要给出头和尾变量&a…

Python (十二) 文件

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一份大厂面试资料《史上最全大厂面试题》&#xff0c;Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 …

【STM32】TF卡FTA32文件系统

一、SD卡介绍 1.SD简介 本质&#xff1a;NandFlash控制芯片 2.SD卡存储容量等级 3.FAT文件系统的使用 4.SD卡速度等级 5.SD卡驱动方式 1.SDIO&&SD 1&#xff09;SDIO接口通信线&#xff1a;CLK/CMD/DAT0-3&#xff08;数据传输线4根&#xff09; 2&#xff09;SPI接口…

高压开关柜无线测温系统

高压开关柜无线测温系统是一种用于监测高压开关柜内部温度的系统。依托电易云-智慧电力物联网&#xff0c;它采用无线通信技术&#xff0c;实现对开关柜内部温度的实时监测和数据传输。下面我将为您介绍高压开关柜无线测温系统的组成、原理、功能以及优势。 一、系统组成 高压开…

JAVA项目测试----用户管理系统

一)项目简介: 用户管理系统是依据于前后端分离来实现的&#xff0c;是基于Spring SpringBoot Spring MVC&#xff0c;SpringAOP&#xff0c;MyBatis等框架来实现的一个用户管理网站&#xff0c;并且已经部署到了云服务器上, 目前的用户管理系统实现了超级管理员的注册功能&…

竞赛 题目:基于深度学习的手势识别实现

文章目录 1 前言2 项目背景3 任务描述4 环境搭配5 项目实现5.1 准备数据5.2 构建网络5.3 开始训练5.4 模型评估 6 识别效果7 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的手势识别实现 该项目较为新颖&#xff0c;适合作为竞赛课题…

python中Thread实现多线程任务

目录 多线程概括&#xff1a; 使用 Thread 模块创建线程 如果不使用多线程&#xff1a; 多线程概括&#xff1a; 多线程是一种并发执行的编程方式&#xff0c;允许程序同时执行多个独立的线程&#xff0c;每个线程在程序中运行独立的任务。每个线程都是程序的基本执行单元&a…

万字长文深入理解 cache,写出高性能代码

CACHE的一致性 Cache的一致性有这么几个层面 1. 一个CPU的icache和dcache的同步问题 2. 多个CPU各自的cache同步问题 3. CPU与设备&#xff08;其实也可能是个异构处理器&#xff0c;不过在Linux运行的CPU眼里&#xff0c;都是设备&#xff0c;都是DMA&#xff09;的cache同…

用百度AI大模型给头像换风格

心血来潮想尝试尝试AI小应用&#xff0c;给图片加个风格&#xff08;例如微信头像&#xff09;&#xff0c;于是有了这篇简短的教程 目录 1. 领取免费资源2. 在应用列表创建应用3. 在线API调试4. 效果对比 1. 领取免费资源 网站&#xff1a;百度智能云 百度给提供了很多AIGC的…

OpenCV入门7——OpenCV中的滤波器(包括低通滤波与高通滤波,其中低通滤波用于降噪,而高通滤波用于边缘检测)

文章目录 图像滤波卷积相关概念锚点 实战图像卷积Blur an image with a 2d convolution matrix 方盒滤波与均值滤波高斯滤波中值滤波双边滤波高通滤波—索贝尔算子高通滤波—沙尔算子高通滤波—拉普拉斯算子边缘检测Canny 图像滤波 卷积核滤波器 卷积相关概念 锚点 锚点…

详解硬盘的接口、总线和协议

总线&#xff1a;总线是计算机系统中用于连接各个硬件组件的一种通信方式&#xff0c;它可以实现数据、地址和控制信号的传输。在服务器中&#xff0c;内部总线起着承载数据和控制信号的重要作用。总线在单位时间内能传输数据量称为带宽。分为SATA&#xff0c;SAS&#xff0c;P…

linux 系统调用流程分析

x86 1.系统调用 系统调用是用户空间程序与内核交互的主要机制。系统调用与普通函数调用不同&#xff0c;因为它调用的是内核里的代码。使用系统调用时&#xff0c;需要特殊指令以使处理器权限转换到内核态。另外&#xff0c;被调用的内核代码由系统调用号来标识&#xff0c;而…

上门维修安装派单系统小程序APP开发之会员级别设计深度解析

啄木鸟鲁班大师上门安装维修平台APP开发之VIP会员解析&#xff0c;在APP或者小程序里设置的会员叫VIP级别会员&#xff0c;系统一共分为4种会员&#xff0c;注册会员&#xff0c;正式会员&#xff0c;VIP金卡会员&#xff0c;VIP钻卡会员。注册用户是指注册了平台但是没有消费记…

预计2023年交付35万台,增速超400%!HUD硬核玩家强势崛起

随着HUD市场渗透率加速提升&#xff0c;其高速增长期已经来临。 W-HUD和AR-HUD在中国市场的萌芽导入期是在2020年前后&#xff0c;此前HUD市场不温不火&#xff0c;主要归因于以往W-HUD FOV较小&#xff0c;成像画面有限&#xff0c;显示内容简单且效果粗糙&#xff1b;而AR-H…

Linux下使用宏定义判断系统架构和系统类型

文章目录 查看编译器当前支持的宏定义查找指定的宏不同架构不同系统 附录-编译器内部常用的一些宏定义宏定义实际应用使用宏定义判断系统架构使用宏定义判断系统类型 一般情况下在linux下做C/C方面的开发不需要太关注系统架构&#xff0c;当然如果涉及到不同架构下的适配问题&a…

软考小记-软件工程

模块的控制范围包括模块本身及其所有的从属模块。模块的作用范围是指模块一个判定的作用范围&#xff0c;凡是受这个判定影响的所有模块都属于这个判定的作用范围.&#xff0c;原则上一个模块的作用范围应该在其控制范围之内&#xff0c;若没有&#xff0c;则可以将判定所在模块…

安装向量数据库milvus及其Attu

前置条件安装docker compose 在宿主机上创建文件目录 mkdir -p /home/sunyuhua/milvus/db mkdir -p /home/sunyuhua/milvus/conf mkdir -p /home/sunyuhua/milvus/etcd下载docker-compose.yml wget https://github.com/milvus-io/milvus/releases/download/v2.2.11/milvus-s…

OpenGL_Learn14(光照贴图)

1. 漫反射贴图 在光照场景中&#xff0c;它通常叫做一个漫反射贴图(Diffuse Map)&#xff08;3D艺术家通常都这么叫它&#xff09;&#xff0c;它是一个表现了物体所有的漫反射颜色的纹理图像。 我们会将纹理储存为Material结构体中的一个sampler2D 。我们将之前定义的vec3漫反…

AI技术实力认证,宏电股份荣获2023年度AI天马“领军企业”

近日&#xff0c;由中国新一代人工智能发展战略研究院指导&#xff0c;深圳市人工智能产业协会主办&#xff0c;广东未来产业研究院承办的2023年度“AI天马”认定最终结果公布&#xff0c;宏电股份荣获AI天马“领军企业”奖项。 宏电股份基于20余年的技术沉淀&#xff0c;在工业…

基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码

基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于猎食者算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于猎食者优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…