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

news2024/12/27 13:46:45

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--设置端口复用---bind
    int 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.1
    char 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.coloradmin.cn/o/1222498.html

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

相关文章

CF1899A Game with Integers(思维题)

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

Windows Server2012 R2修复SSL/TLS漏洞(CVE-2016-2183)

漏洞描述 CVE-2016-2183 是一个TLS加密套件缺陷&#xff0c;存在于OpenSSL库中。该缺陷在于使用了弱随机数生成器&#xff0c;攻击者可以利用此缺陷预测随机数的值&#xff0c;从而成功绕过SSL/TLS连接的加密措施&#xff0c;实现中间人攻击。这个漏洞影响了OpenSSL 1.0.2版本…

jenkins清理缓存命令

def jobName "yi-cloud-operation" //删除的项目名称 def maxNumber 300 // 保留的最小编号&#xff0c;意味着小于该编号的构建都将被删除 Jenkins.instance.getItemByFullName(jobName).builds.findAll { it.number < maxNumber }.each { it.delet…

python django 小程序点餐源码

开发工具&#xff1a; PyCharm mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 微信小程序 代码注释齐全&#xff0c;没有多余代码&#xff0c;适合学习(毕设)&#xff0c;二次开发&#xff0c;包含论文技术相关文档。 功能介绍&#xff1a…

MySQL优化(1):B+树与索引

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 对于60%的程序员而言&a…

ER 图是什么

文章目录 前言什么是 ER图ER 图实例简化的 ER 图总结 前言 产品经理在梳理产业业务逻辑的过程中&#xff0c;非常重要的一项工作就是梳理各个业务对象之间的关系。如果涉及对象很对的时候&#xff0c;没有工具支持的话很难处理清楚。今天我们就来介绍一个梳理业务对象关系的工…

数据结构与集合源码

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

【科技素养】蓝桥杯STEMA 科技素养组模拟练习试卷F

1、常见的加密算法可以分为对称加密算法和非对称加密算法&#xff0c;以下关于它们的描述正确的是 A、AES是一种常见的非对称加密算法 B、凯撒密码是一种非对称加密 C、非对称加密算法的解密使用的秘钥与加密不同 D、对称加密算法无法被暴力破解 答案&#xff1a;C 2、12根…

ESP32 MicroPython UART及小车类构造函数实验⑥

ESP32 MicroPython UART及小车类构造函数实验⑥ 1、实验目的2、实验内容3、参考代码4、实验结果 1、实验目的 控制小车动起来 2、实验内容 控制小车的前进、后退、左转、右转。读取小车 使用到的串口构造函数&#xff1a; uartmachine.UART(id,baudrate,rx,tx)uart:返回的构…

C++多态原理揭秘

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

二进制分析工具-radare2使用教程

二进制分析工具-radare2使用教程 按照如下执行命令 按照如下执行命令 r2 -A 二进制文件

我为什么开始写技术博客

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

*ST富吉-688272 三季报分析(20231117)

*ST富吉-688272 基本情况 公司名称&#xff1a;北京富吉瑞光电科技股份有限公司 A股简称&#xff1a;*ST富吉 成立日期&#xff1a;2011-01-20 上市日期&#xff1a;2021-10-18 所属行业&#xff1a;计算机、通信和其他电子设备制造业 周期性&#xff1a;1 主营业务&#xff1a…

23111702[含文档+PPT+源码等]计算机毕业设计javaweb高校宿舍管理系统寝室管理

文章目录 **软件开发环境及开发工具&#xff1a;****项目功能介绍&#xff1a;****论文截图&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 软件开发环境及开发工具&#xff…

Excel 文件比较工具 xlCompare 11.01 Crack

比较两个 Excel 文件之间的差异 xlCompare. xlCompare.com 是性能最佳的 Excel diff 工具&#xff0c;用于比较两个 Excel 文件或工作表并在线突出显示差异。xlCompare 包括免费的在线 Excel 和 CSV 文件比较服务以及用于比较和合并 Excel 文件的强大桌面工具。如果您想在线了…

STM32与ZigBee无线通信技术在工业自动化中的应用

工业自动化是指利用电子技术、计算机技术和通信技术等手段&#xff0c;对工厂、设备和生产过程进行自动化控制和管理的过程。在工业自动化中&#xff0c;可靠的无线通信技术对于实时数据的传输和设备的协同控制至关重要。本文将介绍STM32微控制器与ZigBee无线通信技术在工业自动…

MySQL 运算符二

逻辑运算符 逻辑运算符用来判断表达式的真假。如果表达式是真&#xff0c;结果返回 1。如果表达式是假&#xff0c;结果返回 0。 运算符号作用NOT 或 !逻辑非AND逻辑与OR逻辑或XOR逻辑异或 1、与 mysql> select 2 and 0; --------- | 2 and 0 | --------- | 0 | -…

python django 小程序商城源码

开发环境&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html vue.js bootstrap 微信小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索商品(可根据…

SQL 的 AND、OR 和 NOT 运算符:条件筛选的高级用法

AND 运算符 SQL的AND运算符用于根据多个条件筛选记录&#xff0c;确保所有条件都为TRUE才返回记录。下面是AND运算符的基本语法&#xff1a; SELECT column1, column2, ... FROM table_name WHERE condition1 AND condition2 AND condition3 ...;column1, column2,等是您要选…

“具有分布式能源资源的多个智能家庭的能源管理的联邦强化学习”文章学习四——基于联邦深度学习的多智能家居能源管理

一、用于家庭能源管理的FRL算法 在本节中&#xff0c;我们将阐述提出的FRL算法&#xff08;算法1&#xff09;&#xff0c;该算法以分布式方式调度多个智能家庭的能量消耗。在提出的FRL框架中&#xff0c;LHEMS和GS相互迭代并有效训练LHEMS的模型。我们考虑了由LHEMS控制的空调…