基于多反应堆的高并发服务器【C/C++/Reactor】(中)HttpRequest 提取请求行、解析请求行和优化 以及解析请求头并存储

news2024/11/15 18:32:42

### 知识点1:读取网络数据

  1. 客户端发送给服务器的通信数据通过封装的bufferSocketRead函数读取
  2. 读取的数据存储在struct Buffer结构体实例中,可将该实例作为参数传递给解析函数

回顾Buffer.c中的bufferSocketRead函数

// 写内存 2.接收套接字数据
int bufferSocketRead(struct Buffer* buf,int fd) {
    struct iovec vec[2]; // 根据自己的实际需求
    // 初始化数组元素
    int writeableSize = bufferWriteableSize(buf); // 得到剩余的可写的内存容量
    // 0号数组里的指针指向buf里边的数组,记得 要加writePos,防止覆盖数据
    vec[0].iov_base = buf->data + buf->writePos;
    vec[0].iov_len = writeableSize;

    char* tmpbuf = (char*)malloc(40960); // 申请40k堆内存
    vec[1].iov_base = buf->data + buf->writePos;
    vec[1].iov_len = 40960;
    // 至此,结构体vec的两个元素分别初始化完之后就可以调用接收数据的函数了
    int result = readv(fd, vec, 2);// 表示通过调用readv函数一共接收了多少个字节
    if(result == -1) {
        return -1;// 失败了
    }
    else if (result <= writeableSize) { 
        buf->writePos += result;
    }
    else {
        buf->writePos = buf->capacity; // 需要先更新buf->writePos
        bufferAppendData(buf, tmpbuf, result - writeableSize);
    }
    free(tmpbuf);
    return result;
}

### 知识点2:从Buffer中读取请求行

  1. 需要编写一个操作函数,根据换行符(\r\n)buffer中提取一行数据
  2. memem函数应返回找到的子数据块在内存中的起始位置----注意数据块的大小,并在函数中指定

一、解析请求行(通过指针方式解析非 sscanf 方式)

GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: BAIDUID=6729CB682DADC2CF738F533E35162D98:FG=1;
BIDUPSID=6729CB682DADC2CFE015A8099199557E; PSTM=1614320692; BD_UPN=13314752;
BDORZ=FFFB88E999055A3F8A630C64834BD6D0;
__yjs_duid=1_d05d52b14af4a339210722080a668ec21614320694782; BD_HOME=1;
H_PS_PSSID=33514_33257_33273_31660_33570_26350;
BA_HECTOR=8h2001alag0lag85nk1g3hcm60q
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
空行
请求数据为空
>>http get请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
 
提示: 每项信息之间都需要一个\r\n,是由http协议规定
 
************************************************
************************************************
>>http post请求报文的格式
请求行\r\n
请求头\r\n
空行(\r\n)
请求体
 
提示: 请求体就是浏览器发送给服务器的数据

>>提取请求行 

 (1)在Buffer.h新增一个函数,叫 bufferFindCRLF函数,其功能是:根据\r\n取出一行,找到其在数据块中的位置,返回该位置

// 根据\r\n取出一行,找到其在数据块中的位置,返回该位置
char* bufferFindCRLF(struct Buffer* buf);
// CRLF表示\r\n
char* bufferFindCRLF(struct Buffer* buf) {
    // strstr --> 从大字符串中去匹配子字符串(遇到\0结束)
    // memmem --> 从大数据块中去匹配子数据块(需要指定数据块大小)
    char* ptr = memmem(buf->data + buf->readPos,bufferReadableSize(buf),"\r\n",2);
    return ptr;
}

(2)HttpRequest.h  新增一个函数,叫 parseHttpRequestLine函数,用于解析请求行

// 解析请求行
bool parseHttpRequestLine(struct HttpRequest* req,struct Buffer* readBuf);
// 解析请求行
bool parseHttpRequestLine(struct HttpRequest* req,struct Buffer* readBuf) {
    // 读取请求行
    char* end = bufferFindCRLF(readBuf);
    // 保存字符串起始位置
    char* start = readBuf->data + readBuf->readPos;
    // 保存字符串结束地址
    int lineSize = end - start;
    if(lineSize>0) {
        // get /xxx/xx.txt http/1.1
        // 请求方式
        char* space = memmem(start,lineSize," ",1);
        assert(space!=NULL);
        int methodSize = space - start;
        req->method = (char*)malloc(methodSize + 1);
        strncpy(req->method,start,methodSize);
        req->method[methodSize] = '\0';

        // 请求静态资源
        start = space + 1;
        space = memmem(start,end-start," ",1);
        assert(space!=NULL);
        int urlSize = space - start;
        req->url = (char*)malloc(urlSize + 1);
        strncpy(req->url,start,urlSize);
        req->url[urlSize] = '\0';

        // http 版本
        start = space + 1;
        req->version = (char*)malloc(end-start + 1);
        strncpy(req->version,start,end-start);
        req->version[end-start] = '\0';

        // 解析请求行完毕,为解析请求头做准备
        readBuf->readPos += lineSize;
        readBuf->readPos += 2;

        // 修改状态 解析请求头
        req->curState = ParseReqHeaders;
        return true;
    }
    retrun false;
}

二、优化解析请求行代码

如果想要在一个函数里边给外部的一级指针分配一块内存,那么需要把外部的一级指针的地址传递给函数。外部的一级指针的地址也就是二级指针,把二级指针传进来之后,对它进行解引用,让其指向我们申请的一块堆内存,就可以实现外部的一级指针被初始化了,也就分配到了一块内存

  • 注意:传入指针的地址(二级指针),这个函数涉及给指针分配一块内存,指针在作为参数的时候会产生一个副本
  • 把指针的地址作为参数传入不会产生副本
char* splitRequestLine(const char* start,const char* end,const char* sub,char** ptr) {
    char* space = (char*)end;
    if(sub != NULL) {
        space = memmem(start,end-start,sub,strlen(sub));
        assert(space!=NULL);
    }
    int length = space - start;
    char* tmp = (char*)malloc(length+1);
    strncpy(tmp,start,length);
    tmp[length] = '\0';
    *ptr = tmp;// 对ptr进行解引用=>*ptr(一级指针),让其指向tmp指针指向的地址
    return space+1;
}
// 解析请求行
bool parseHttpRequestLine(struct HttpRequest* req,struct Buffer* readBuf) {
    // 读取请求行
    char* end = bufferFindCRLF(readBuf);
    // 保存字符串起始位置
    char* start = readBuf->data + readBuf->readPos;
    // 保存字符串结束地址
    int lineSize = end - start;
    if(lineSize>0) {
        start = splitRequestLine(start,end," ",&req->method);// 请求方式
        start = splitRequestLine(start,end," ",&req->url);// url资源
        splitRequestLine(start,end,NULL,&req->version);// 版本
#if 0
        // get /xxx/xx.txt http/1.1
        // 请求方式
        char* space = memmem(start,lineSize," ",1);
        assert(space!=NULL);
        int methodSize = space - start;
        req->method = (char*)malloc(methodSize + 1);
        strncpy(req->method,start,methodSize);
        req->method[methodSize] = '\0';

        // 请求静态资源
        start = space + 1;
        space = memmem(start,end-start," ",1);
        assert(space!=NULL);
        int urlSize = space - start;
        req->url = (char*)malloc(urlSize + 1);
        strncpy(req->url,start,urlSize);
        req->url[urlSize] = '\0';

        // http 版本
        start = space + 1;
        req->version = (char*)malloc(end-start + 1);
        strncpy(req->version,start,end-start);
        req->version[end-start] = '\0';
#endif
        // 为解析请求头做准备
        readBuf->readPos += lineSize;
        readBuf->readPos += 2;

        // 修改状态
        req->curState = ParseReqHeaders;
        return true;
    }
    return false;
}

三、解析请求头并存储

### 解析请求头数据

1.数据存储在对应的Buffer结构内存块中。解析时,需要将readPos更新到请求头的起始位置parseHttpRequestLine函数中已经为解析请求头做好了准备。

  • 回顾一下parseHttpRequestLine函数:
bool parseHttpRequestLine(struct HttpRequest* request, struct Buffer* readBuf) {
    ...
    if (lineSize>0)
    {
        start = splitRequestLine(start, end, " ", &request->method);
        start = splitRequestLine(start, end, " ", &request->url);
        splitRequestLine(start, end, NULL, &request->version);

        // 为解析请求头做准备
        readBuf->readPos += lineSize;
        readBuf->readPos += 2;
        // 修改状态
        request->curState = ParseReqHeaders;
        return true;
    }
    return false;
}

2.请求头的每行为一对键值对,包含一个key值和一个value

3.将数据行存储到堆内存中,并将堆内存地址传递给httpRequestAddHeader函数

  • 回顾一下httpRequestAddHeader函数:
void httpRequestAddHeader(struct HttpRequest* request, const char* key, const char* value) {
    request->reqHeaders[request->reqHeadersNum].key = (char*)key;
    request->reqHeaders[request->reqHeadersNum].value = (char*)value;
    request->reqHeadersNum++;
}

### 解析请求头数据 该函数处理请求头中的一行

  1. 标准HTTP协议中,键值和值之间使用冒号分隔,冒号后有一个空格
  2. 使用memem函数检查中间指针是否指向冒号
// 解析请求头
bool parseHttpRequestHeader(struct HttpRequest* req,struct Buffer* readBuf);
// 该函数处理请求头中的一行
bool parseHttpRequestHeader(struct HttpRequest* req,struct Buffer* readBuf) {
    char* end = bufferFindCRLF(readBuf);
    if(end!=NULL) {
        char* start = readBuf->data + readBuf->readPos;
        int lineSize = end - start;
        // 基于: 搜索字符串
        char* middle = memmem(start,lineSize,": ",2);    
        if(middle!=NULL) {
            // 拿出键值对
            char* key = malloc(middle - start + 1);
            strncpy(key,start,middle - start);
            key[middle - start] = '\0';// 获得key
            
            char* value = malloc(end - middle - 2 + 1);// end-(middle+2) + 1 = end - middle - 2 + 1
            strncpy(value,middle+2,end - middle - 2);
            value[end - middle - 2] = '\0';// 获得value

            httpRequestAddHeader(req,key,value);// 添加键值对
            // 移动读数据的位置
            readBuf->readPos += lineSize;
            readBuf->readPos += 2;
        }
        else {
            // 请求头被解析完了,跳过空行
            readBuf->readPos += 2;
            // 修改解析状态
            // 本项目忽略 post 请求,按照 get 请求处理
            req->curState = ParseReqDone;
        }
        return true;
    }
    return false;
}

 ## 注意事项

总结提取请求行 和 解析请求行和优化 这篇博客和 本文需要注意的细节如下:

  1. 在解析请求行和头部时,需要提供相应的解析函数
  2. 解析函数应返回布尔值,表示解析是否成功
  3. 在读取网络数据时,需要使用封装的bufferSocketRead函数来读取数据
  4. 读取的数据存储在struct Buffer结构体实例中,该实例作为参数传递给解析函数

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

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

相关文章

备忘录传不了图片,求备忘录里添加图片的方法是什么?

在这个信息爆炸的时代&#xff0c;我深知信息整合对于提升效率的重要性。尤其是当文字和图片能够完美结合时&#xff0c;那种查阅的便捷和直观感受&#xff0c;真的让人眼前一亮。 想象一下&#xff0c;你正在为即将到来的旅行做攻略&#xff0c;或者在准备一次重要的工作汇报…

【操作系统】重点概念汇总(手写版本)

Chapter1&#xff1a;操作系统引论 Chapter2&#xff1a;进程管理 Chapter3&#xff1a;处理机调度与死锁 Chapter4&#xff1a;存储器管理 Chapter5&#xff1a;虚拟存储器 Chapter6&#xff1a;输入输出系统 Chapter7&#xff1a;文件管理 Chapter8&#xff1a;磁盘存…

ssm基于Javaweb的网上花店系统的设计与实现论文

摘 要 进入21世纪网络和计算机得到了飞速发展&#xff0c;并和生活进行了紧密的结合。目前&#xff0c;网络的运行速度以达到了千兆&#xff0c;覆盖范围更是深入到生活中的角角落落。这就促使网上购物系统的发展。网上购物可以实现远程购物&#xff0c;远程选择喜欢的商品和随…

黑马苍穹外卖学习Day5

文章目录 Redis学习Redis简介准备工作Redis常用数据类型介绍各数据类型的特点Redis常用命令字符串操作命令哈希操作命令列表操作命令集合操作命令有序集合操作命令通用操作命令 在Java中操作Redis导入Spring Data Redis坐标配置Redis数据源编写配置类&#xff0c;创建RedisTemp…

odoo16 库存界面调整

odoo16 库存界面调整 今天布置一服装批发中心&#xff0c;嫌出入库的概述描述不清&#xff0c;如收据想改成入库&#xff0c;交货单想改成发货单 原代码如下&#xff1a; <record id"stock_picking_type_kanban" model"ir.ui.view"><field name&…

安防视频监控系统EasyCVR设备分组中在线/离线数量统计的开发与实现

安防视频监控EasyCVR系统具备较强的兼容性&#xff0c;它可以支持国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。EasyCVR平台可覆盖多类型的设备接入&am…

STM32L051使用HAL库操作实例(14)- ADC采集电压

目录 一、前言 二、ADC外设简要说明 三、STM32CubeMX配置&#xff08;本文使用的STM32CubeMX版本为6.1.2&#xff09; 1.MCU选型 2.时钟使能 3.外部时钟配置 4.串口配置 5.ADC引脚配置 6.配置STM32CubeMX生成工程文件 7.点击GENERATE CODE生成工程文件 四、工程源码 …

Linux文件系统的层次结构、每个目录的含义、文件属性以及文件中第一列的第一个字符的含义

1.Linux文件系统的层次结构 在Linux操作系统中&#xff0c;所有的文件和目录都被组织成以一个根节点“/”开始的导致的树状结构&#xff1a; 5.Linux系统的目录解析 &#xff08;1&#xff09;/bin bin是Binary的缩写&#xff0c;这个目录存放着最经常使用的命令。 &#x…

Nerf相关研究

1.Nerf相关研究 随着Luma AI的到来&#xff0c;再次将Nerf推向浪尖&#xff0c;实用性进一步得到强化。Nerf仍以极速的发展速度前行&#xff0c;越来越多的研究方向不断涌现。 4K-Nerf 4K-NeRF: High Fidelity Neural Radiance Fields at Ultra High Resolutions 论文&#…

写点东西《2024 年决心:更加以开源为中心》

写点东西《2024 年决心&#xff1a;更加以开源为中心》 简而言之1- 使用 Taipy 代替 Tableau 2- 使用 Cal.com 代替 Calendly 3- Plausible 代替 Google Analytics4- AppFlowy 代替 Notion5- Penpot 代替 Figma 6- Fonoster 代替 Twilio 7- NextCloud 替代 Dropbox 8- Jitsi 替…

想寻找Axure的替代品?我们已经试用了10+款设计工具,来看看吧!

Axure是许多产品经理和设计师进入快速原型设计的首选工具&#xff0c;但Axure的使用成本相对较高&#xff0c;学习曲线陡峭&#xff0c;许多设计师正在寻找可以取代Axure的原型设计工具&#xff0c;虽然现在有很多可选的设计工具&#xff0c;但质量不均匀&#xff0c;可以取代A…

平面光波导_三层均匀平面光波导_射线分析法

平面光波导_三层均匀平面光波导_射线分析法 三层均匀平面光波导&#xff1a; 折射率沿 x x x 方向有变化&#xff0c;沿 y y y、 z z z 方向没有变化三层&#xff1a;芯区( n 1 n_1 n1​) > > > 衬底( n 2 n_2 n2​) ≥ \geq ≥ 包层( n 3 n_3 n3​)包层通常为空…

YOLOv8-Seg改进:UNetv2多层次特征融合模块结合DualConv、GSConv

🚀🚀🚀本文改进:多层次特征融合(SDI)结合DualConv、GSConv模块等实现二次创新 🚀🚀🚀SDI 亲测在多个数据集能够实现涨点,同样适用于小目标检测 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定…

软件系统培训方案(Word)

1. 培训概述 2. 培训目的 3. 培训对象及要求 3.1. 培训对象 3.2. 培训人员基本要求 4. 培训方式 5. 培训内容 6. 培训讲师 7. 培训教材 8. 培训质量保证 8.1. 用户培训确认报告 8.2. 培训疑问解答 软件开发全文档下载&#xff1a;软件项目开发全套文档下载_软件项目文档-CSDN博…

java项目之留学生交流互动论坛(ssm)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的留学生交流互动论坛。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 管理员&#xff1a;首页、个…

电调相关英文缩写ESC、BEC、PPM、Oneshot125、Oneshot42、Multishot、DShot、ProShot

ESC ESC全称是 Electronic Speed Control 中文翻译成电子调速器&#xff0c;就是电调 BEC BEC全称是 battey elimination circuit 中文翻译成免电池电路 可以理解就是对外供电&#xff0c;BEC就是线性稳压&#xff0c;降压用的&#xff0c;给接收机飞控供电 ESC协议 ESC协议…

K8s---存储卷(动态pv和pvc)

当我要发布pvc可以生成pv&#xff0c;还可以共享服务器上直接生成挂载目录。pvc直接绑定pv。 动态pv需要两个组件 1、卷插件&#xff1a;k8s本生支持的动态pv创建不包括nfs&#xff0c;需要声明和安装一个外部插件 Provisioner: 存储分配器。动态创建pv,然后根据pvc的请求自动…

什么是金融RPA?金融RPA解决什么问题?金融RPA实施难点在哪里?

什么是金融RPA&#xff1f;金融RPA&#xff0c;即金融领域的机器人流程自动化&#xff0c;是一种利用软件机器人来代替人工完成重复性劳动任务的技术。它能够通过模仿最终用户在电脑上的手动操作方式&#xff0c;实现自动化处理大量重复、规则明确的业务流程&#xff0c;如账务…

uniapp 制作 wgt 包(用于 app 的热更新)

升级版本号 修改 manifest.json 的配置&#xff0c;应用版本名称和应用版本号 必须高于上一版的值。 制作 wgt 包 发布 wgt 包 打开 uni-admin 项目的升级中心 上传后会自动生成下载链接 app 的静默热更新 发布新版后&#xff0c;用户打开app&#xff0c;后台会自动下载 wgt…

基于JavaWeb+BS架构+SpringBoot+Vue电影订票系统系统的设计和实现

基于JavaWebBS架构SpringBootVue电影订票系统系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 1 绪 论 3 1.1研究背景和意义 3 1.2拟解决的问题及特性 3 1.3论文的结构 …