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.hqwc.cn/news/189713.html

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

相关文章

数据库mysql详细教学

1024 byte 构成 1 kb 1024 KB > 1MB 1024 MB > 1GB 1024 GB > 1TB 1024 TB > 1PB 内存的数据&#xff0c;断电后会丢失。外存的数据&#xff0c;断电后数据还在~ “持久化” 这样的次&#xff0c;意思就是把数据写到硬盘上。 mysql的第一组基本操作&#xff1a;数…

我为什么开始写技术博客

今天没有技术文章&#xff0c;只是想聊聊认真做CSDN和公众号以来的一些感想。 1.为什么开启技术分享 我不算是一个聪明的人&#xff0c;没有过目不忘的本事&#xff0c;所以从工作开始就养成了做笔记的习惯&#xff1b; 最开始15、16年做模型开发&#xff0c;那时候环境其实就…

MySQL优化(2):索引与优化原理(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 上一篇&#xff0c;我们…

ROS 学习应用篇(八)ROS中的坐标变换管理之tf广播与监听的编程实现

偶吼吼胜利在望&#xff0c;冲冲冲 老规矩新建功能包 工作空间目录下/src下开启终端输入 catkin_create_pkg learning_tf roscpp rospy tf turtlesim 如何实现tf广播 引入库 c python …

小米真无线耳机 Air 2s产品蓝牙配对ubuntu20.04 笔记本电脑

小米真无线耳机 Air 2s产品蓝牙配对ubuntu20.04 笔记本电脑 1.我的笔记本是 22款联想拯救者y9000k&#xff0c;安装了双系统&#xff0c;ubuntu20.04。 2.打开耳机&#xff0c;按压侧面按钮2秒&#xff0c;指示灯显示白色闪烁。 3.打开ubunru20.04 系统右上角wifi的位置&…

分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测

分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测 目录 分类预测 | Matlab实现基于PSO-SDAE粒子群优化算法优化堆叠去噪自编码器的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现基于PSO-SDAE粒子群优化算法…

大数据可视化是什么?

大数据可视化是将海量数据通过视觉方式呈现出来&#xff0c;以便于人们理解和分析数据的过程。它可以帮人们发现数据之间的关系、趋势和模式&#xff0c;并制定更明智的决策。大数据可视化通常通过图形、图表、地图和仪表盘等视觉元素来呈现数据。这些元素具有直观、易理解的特…

常见的反爬+文字加解密

一、常见的反爬介绍 基于身份识别的反爬&#xff1a;1.User-agent 2.Referer 3.Captcha 验证码 4.必备参数 基于爬虫行为的反爬&#xff1a;1.单位时间内请求数量超过一定阈值 2.相邻两次请求之间间隔小于一定阈值3.蜜罐陷阱 通过对数据加密进行反爬&#xff1a;1.对文字加密…

CF1899A Game with Integers(思维题)

题目链接 题目 题目大意 t 组测试样例 每组给一个正整数 n&#xff0c; 有两种操作&#xff1a; 1-1 A 和 B 轮流操作&#xff0c; 如果这个整数变成了一个能被3整除的数&#xff0c;A赢&#xff0c;输出First 如果在10次操作以内&#xff0c;n不能被3整数&#xff0c;B赢&…

CF1899B 250 Thousand Tons of TNT

题目链接 题目 题目大意 T T T 组测试数据 每组 n n n 个货物&#xff0c;第 i i i 个货物 的重量是 a i a_i ai​ 用k辆货车按顺序装这些货物&#xff0c;条件是每辆车上的货物个数都一样&#xff0c;也即是说 n n n 必须能被 k k k 整除&#xff0c; 求任意两辆车货物总…

一起学docker系列之五docker的常用命令--操作容器的命令

目录 前言1 启动容器2 查看容器3 退出容器4 启动已经停止的容器5 重启容器6 停止容器7 删除已经停止的容器8 启动容器说明和举例9 查看容器日志10 查看容器内运行的进程11 查看容器内部细节12 进入正在运行的容器并进行交互13 导入和导出容器结语 前言 当涉及到容器化技术&…

如何在远程协同视频会议中确保安全性?

随着远程工作的普及&#xff0c;远程协同视频会议已成为企业和团队之间进行交流和协作的重要工具。与此同时&#xff0c;会议中的安全性问题也日益凸显。本文将介绍如何在远程协同视频会议中确保安全性&#xff0c;主要包括以下方面&#xff1a; 1、内网部署 将会议服务器部署…