网络学习-利用reactor实现http请求(六)

news2025/5/24 4:57:22

一、实现HTTP请求

1、印象里面,总有人说C/C++语言不能实现HTTP请求,其实不然。C/C++语言完全可以实现HTTP请求。通过对select,poll,epoll等IO多路复用技术的学习以及reactor模式的学习,完全能够实现HTTP请求。

2、webserver

主要解决两个问题

1、请求数据

2、响应,回发数据

3、简单小测试

/**
 *向服务器发送HTTP请求
 */
int Http_Request(Conne *c)
{
    cout<<"Http_Request:"<<c->rbuffer<<endl;
    return 0;
}

/**
 * 处理HTTP响应
 */
int Http_Response(Conne *c)
{
    cout<<"Http_Response:"<<c->wbuffer<<endl;
    return 0;
}

/*在reactor的那一套回调函数的基础上,添加接收到请求数据后,调用Http_Request,在回发数据之前,调用下Http_Response*/
int Recv_cb(int fd)
{
    int count = recv(fd, conn_poll[fd].rbuffer, BUFFER_SIZE, 0);
    if (count == 0)
    {
        cout << "client close" << endl;
        close(conn_poll[fd].fd);                                // 关闭客户端的连接描述
        epoll_ctl(fd, EPOLL_CTL_DEL, conn_poll[fd].fd, NULL); // 将客户端的连接描述符从epoll实例中删除
        return 0;
    }
    cout << "recv_buffer:" << conn_poll[fd].rbuffer << endl;

    Http_Request(&conn_poll[fd]);           //接收到数据后,进行解析请求数据

    conn_poll[fd].wlen = count;
    memcpy(conn_poll[fd].wbuffer, conn_poll[fd].rbuffer, count);

    SetEvent(fd, EPOLLOUT,0); //监听可写事件

    return count;
}

int Send_cb(int fd)
{
    Http_Response(&conn_poll[fd]);          // 在回发数据之间,响应数据,解析响应数据
    // 返回信息
    int count = send(fd, conn_poll[fd].wbuffer, conn_poll[fd].wlen, 0);

    SetEvent(fd, EPOLLIN,0); //监听可读事件

    return count;
}

客户端连接:
在这里插入图片描述

浏览器连接:
在这里插入图片描述

在这里插入图片描述

4、可以看到连接成功,但浏览器这边空空如也,添加点东西


int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=UTF-8\r\n"
        "Accept-Ranges: bytes\r\n"
        "Content-Length: 82\r\n"
        "Date: %s\r\n"
        "<html><head><title>Hello</title></head><body><h1>LengYa</h1></body></html>\r\n", ctime(&t));
    return 0;
}

在这里插入图片描述

在这里插入图片描述

5、C/C++里面写标签,太麻烦了,换成html文件,直接读取文件内容。


int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    int filefd = open("index.html", O_RDONLY);

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=UTF-8\r\n"
        "Accept-Ranges: bytes\r\n"
        "Content-Length: %ld\r\n"
        "Date: %s\r\n", stat_buf.st_size,ctime(&t));
    int count = read(filefd, c->wbuffer + c->wlen, BUFFER_SIZE-c->wlen);
    c->wlen += count;
    close(filefd);
    return 0;
}

在这里插入图片描述

6、压力测试

工具准备:wrk

#c:连接
#t:线程
#d:持续时间
./wrk -c 10 -t 2 -d 30s http://192.168.127.132:2000/

在这里插入图片描述

在这里插入图片描述

结果:
在30.07s内,总共发送了168276个请求,总共读取91.47MB数据;平均每秒发送5595.89个请求,平均每秒读取3.04MB数据。

7、小结

通过上面的测试,可以发现,C/C++语言完全可以实现HTTP请求。只不过相对于专门处理web的java,c#,php等语言,在处理HTTP请求上,显得笨拙了些。
毕竟C/C++在处理业务逻辑上,不是强项,在处理底层,性能调优上才是强项。

二、拓展

1、请求图片数据

之前请求的html文本数据,而且数据量不大,这次换下个数据量大的,比如图片。

int filefd = open("test.jpg", O_RDONLY);                        // 打开文件


struct stat stat_buf;
fstat(filefd, &stat_buf);

c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
            "Content-Type: image/jpeg; charset=UTF-8\r\n"       //请求类型
            "Accept-Ranges: bytes\r\n"
            "Content-Length: %ld\r\n"
            "Date: %s\r\n", stat_buf.st_size,ctime(&t));

在这里插入图片描述

可以发现,图片数据量很大,基本没加载出来,毕竟代码中写的缓冲区大小就只有1024字节,远远不够。
如果要加载完图片,有两种思路:

1、增大缓冲区大小,让其足够大。

但多少才算是足够大呢,每次发现不够,需要重新修改代码,内测倒是可以,上线的话就麻烦了。
所以这个方法,不推荐。

2、分段发送,每次只发一小部分。

每次只发送一小部分,直到全部发送完毕。

/*
原来设置1024的缓冲区大小,如果数据量为10*1024字节,可以设置缓冲区大小为10*1024
也可以不必变更原来的大小,循环10次,每次发送1024字节,也能达到同样的效果
*/
/*
accept_cb----->Recv_cb----->Send_cb----->recv_cb----->Send_cb---->...
IO连接成功----->接收部分数据----->回发部分数据---->接收部分数据----->回发部分数据---->...---->数据全部接收完毕--->全部数据发送完毕
如何让其自动循环接收,发送数据,可以使用循环,通过计算文件大小,除以缓冲区大小,计算出需要循环的次数。
也可以设置状态,让其自动循环接收,发送数据。
*/
int status;       //0--发送头,1--发送body,2--关闭连接

//Http请求中初始化状态
int Http_Request(Conne *c)
{
    cout<<"Http_Request:"<<c->rbuffer<<endl;

    memset(c->rbuffer, 0, BUFFER_SIZE);
    c->wlen = 0;
    c->status = 0;

    return 0;
}

//Http响应中,根据状态机,分段发送数据
int Http_Response(Conne *c)
{
    time_t t = time(NULL);
    struct tm *local_time = localtime(&t);

    int filefd = open("test.jpg", O_RDONLY);

    struct stat stat_buf;
    fstat(filefd, &stat_buf);

    if(c->status == 0){
        c->wlen = sprintf(c->wbuffer, "HTTP/1.1 200 OK\r\n"
            "Content-Type: image/jpeg; charset=UTF-8\r\n"
            "Accept-Ranges: bytes\r\n"
            "Content-Length: %ld\r\n"
            "Date: %s\r\n", stat_buf.st_size,ctime(&t));
        c->status = 1;
    }else if(c->status == 1){
        int ret = sendfile(c->fd, filefd, NULL, stat_buf.st_size);  //数据拷贝
        if(ret < 0){                //出错处理
            cout << "sendfile error:" << strerror(errno) << endl;
            return -1;
        }
        c->status = 2; //发送完成,不再继续发送文件内容(防止重复发送
    }else if(c->status == 2){
        c->wlen = 0;
        memset(c->wbuffer, 0, BUFFER_SIZE); //清空缓冲区,防止重复发送
        c->status = 0; //发送完成,重置状态机
    }

    close(filefd);
    return 0;
}
int Send_cb(int fd)
{
    Http_Response(&conn_poll[fd]);
    // 返回信息
    int count = 0;
    if (conn_poll[fd].status == 1)
    {
        count = send(fd, conn_poll[fd].wbuffer, conn_poll[fd].wlen, 0);

        SetEvent(fd, EPOLLOUT, 0); // 监听可写事件
    }
    else if (conn_poll[fd].status == 2)
    {
        SetEvent(fd, EPOLLOUT, 0); // 监听可写事件
    }
    else if (conn_poll[fd].status == 0)
    {
        SetEvent(fd, EPOLLIN, 0); // 监听可读事件
    }

    return count;
}

在这里插入图片描述

2、视频流

在这里插入图片描述

可惜视频流失败,大体思路也是分段,但不可和文本、图片的资源一样看待,后续有时间再研究。

三、总结

1、C/C++可以实现HTTP请求,但相对于专门处理web的java,c#,php等语言,显得笨拙。

2、如果要实现高性能的服务器,C/C++是首选。

3、对于频繁接收部分数据,发送部分数据的场景,分段处理是个不错的选择;状态机应该优先考虑。

4、状态机使得代码逻辑更加清晰,便于扩展,更容易处理错误。

5、循环不易于错误处理,且代码会变得更加复杂和难以理解。

Code:
代码链接

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

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

相关文章

【IC_Design】跨时钟域的寄存器更新后锁存

目录 设计逻辑框图场景概述总结电路使用注意事项***波形图代码 设计逻辑框图 场景概述 最典型的应用场景就是——在一个时钟域&#xff08;比如 CPU/总线域&#xff09;更新了一个多位配置字&#xff0c;需要把它安全地送到另一个时钟域&#xff08;比如时钟发生器、串口、视频…

Java微服务架构:Spring Cloud全栈指南,附最新Demo源码,可独立运行!

在日常java开发中你是不是经常遇到这种问题&#xff1a;开发中不知道要引入什么版本&#xff0c;创建新项目时直接从老工程拷贝引入了一堆杂乱的包&#xff0c;随便升级下其中一个包就导致整个微服务跑不起来&#xff01; 如果你也遇到这种问题&#xff0c;可以认证看下本篇文…

使用LLaMA-Factory微调ollama中的大模型(一)------家用电脑安装LLaMA-Factory工具

前提&#xff1a;本机已安装python&#xff0c;且版本大于3.9&#xff0c;推荐3.10 官方规定如下 我已安装 1.安装torch 查看自己电脑显卡信息 说明我没有装CUDA 使用 nvidia-smi 命令查看驱动信息 说明我NVIDIA 显卡已安装驱动&#xff0c;支持的 CUDA Runtime 版本为 12.6…

支持向量机(SVM):分类与回归的数学之美

在机器学习的世界里&#xff0c;支持向量机&#xff08;Support Vector Machine&#xff0c;简称 SVM&#xff09;是一种极具魅力且应用广泛的算法。它不仅能有效解决分类问题&#xff0c;在回归任务中也有着出色的表现。下面&#xff0c;就让我们深入探索 SVM 如何在分类和回归…

人工智能+:职业价值的重构与技能升级

当“人工智能”成为产业升级的标配时&#xff0c;一个令人振奋的就业图景正在展开——不是简单的岗位替代&#xff0c;而是职业价值的重新定义。这场变革的核心在于&#xff0c;AI并非抢走工作机会&#xff0c;而是创造了人类与技术协作的全新工作范式。理解这一范式转换的逻辑…

JVM部分内容

1.JVM内存区域划分 为什么要划分内存区域&#xff0c;JAVA虚拟机是仿照真实的操作系统进行设计的&#xff0c;JVM也就仿照了它的情况&#xff0c;进行了区域划分的设计。 JAVA进程也就是JAVA虚拟机会从操作系统申请内存空间给进程使用&#xff0c;JVM内存空间划分&#xff0c…

python-leetcode 68.有效的括号

题目&#xff1a; 给定一个只包括“&#xff08;”)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a;左括号必须用相同类型的右括号闭合&#xff1b;左括号必须以正确的顺序闭合&#xff0c…

NLP学习路线图(四):Python编程语言

引言 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能领域最引人注目的分支之一。从智能客服到机器翻译&#xff0c;从舆情分析到聊天机器人&#xff0c;NLP技术正在重塑人机交互的边界。本文将结合Python编程语言&#xff0c;带您走进NLP的…

Serverless爬虫架构揭秘:动态IP、冷启动与成本优化

一、问题背景&#xff1a;旧技术的瓶颈 在传统爬虫架构中&#xff0c;我们通常部署任务在本地机器或虚拟机中&#xff0c;搭配定时器调度任务。虽然这种方式简单&#xff0c;但存在以下明显缺陷&#xff1a; 固定IP易被封禁&#xff1a;目标网站如拼多多会通过IP频率监控限制…

从单体到分布式:深入解析Data Mesh架构及其应用场景与价值

Data Mesh&#xff08;数据网格&#xff09;是一种新兴的数据架构范式&#xff0c;旨在解决传统集中式数据平台的可扩展性、敏捷性和治理问题。它强调领域驱动的分布式数据所有权、自助数据平台以及跨组织的协作&#xff0c;使数据成为产品&#xff0c;并通过去中心化的方式提高…

AI大模型ms-swift框架实战指南(十三):Agent智能体能力构建指南

系列篇章&#x1f4a5; No.文章1AI大模型ms-swift框架实战指南&#xff08;一&#xff09;&#xff1a;框架基础篇之全景概览2AI大模型ms-swift框架实战指南&#xff08;二&#xff09;&#xff1a;开发入门之环境准备3AI大模型ms-swift框架实战指南&#xff08;三&#xff09…

LLM最后怎么输出值 解码语言模型:从权重到概率的奥秘

LM Head Weights&#xff08;语言模型头部权重&#xff09;&#xff1a;左侧的“LM Head Weights”表示语言模型头部的权重矩阵&#xff0c;它是模型参数的一部分。权重矩阵与输入数据进行运算。Logits&#xff08;未归一化对数概率&#xff09;&#xff1a;经过与LM Head Weig…

Leetcode百题斩-回溯

回溯是一个特别经典的问题&#xff0c;也被排在了百题斩的第一部分&#xff0c;那么我们接下来来过一下这个系列。 这个系列一共八道题&#xff0c;偶然间发现我两年前还刷到这个系列的题&#xff0c;回忆起来当时刚经历淘系大变动与jf出走海外事件&#xff0c;大量同事离职闹…

超小多模态视觉语言模型MiniMind-V 训练

简述 MiniMind-V 是一个超适合初学者的项目&#xff0c;让你用普通电脑就能训一个能看图说话的 AI。训练过程就像教小孩&#xff1a;先准备好图文材料&#xff08;数据集&#xff09;&#xff0c;教它基础知识&#xff08;预训练&#xff09;&#xff0c;再教具体技能&#xf…

边缘云的定义、实现与典型应用场景!与传统云计算的区别!

一、什么是边缘云&#xff1f;‌ 边缘云是一种‌分布式云计算架构‌&#xff0c;将计算、存储和网络资源部署在‌靠近数据源或终端用户的网络边缘侧‌&#xff08;如基站、本地数据中心或终端设备附近&#xff09;&#xff0c;而非传统的集中式云端数据中心。 ‌核心特征‌&…

Scrapy爬取heima论坛所有页面内容并保存到MySQL数据库中

前期准备&#xff1a; Scrapy入门_win10安装scrapy-CSDN博客 新建 Scrapy项目 scrapy startproject mySpider # 项目名为mySpider 进入到spiders目录 cd mySpider/mySpider/spiders 创建爬虫 scrapy genspider heima bbs.itheima.com # 爬虫名为heima &#xff0c;爬…

com.alibaba.fastjson2 和com.alibaba.fastjson 区别

1&#xff0c;背景 最近发生了一件很奇怪的事&#xff1a;我们的服务向第三方发送请求参数时&#xff0c;第三方接收到的字段是首字母大写的 AppDtoList&#xff0c;但我们需要的是小写的 appDtoList。这套代码是从其他项目A原封不动复制过来的&#xff0c;我们仔细核对了项目…

了解Android studio 初学者零基础推荐(2)

在kotlin中编写条件语句 if条件语句 fun main() {val trafficLight "gray"if (trafficLight "red") {println("Stop!")} else if (trafficLight "green") {println("go!")} else if (trafficLight "yellow")…

C# 初学者的 3 种重构模式

(Martin Fowlers Example) 1. 积极使用 Guard Clause&#xff08;保护语句&#xff09; "如果条件不满足&#xff0c;立即返回。将核心逻辑放在最少缩进的地方。" 概念定义 Guard Clause&#xff08;保护语句&#xff09; 是一种在函数开头检查特定条件是否满足&a…

MySQL 数据类型深度全栈实战,天花板玩法层出不穷!

在 MySQL 数据库的世界里&#xff0c;数据类型是构建高效、可靠数据库的基石。选择合适的数据类型&#xff0c;不仅能节省存储空间&#xff0c;还能提升数据查询和处理的性能 目录 ​编辑 一、MySQL 数据类型总览 二、数值类型 三、字符串类型 四、日期时间类型 五、其他…