当前位置: 首页 > news >正文

从零实现Web服务器(三):日志优化,压力测试,实战接收HTTP请求,实战响应HTTP请求

文章目录

  • 一、日志系统的运行流程
    • 1.1 异步日志和同步日志的不同点
    • 1.2 缓冲区的实现
  • 二、基于Webbench的压力测试
  • 三、HTTP请求报文解析
    • http报文处理流程
    • epoll相关代码
    • 服务器接收http请求
  • 四、HTTP请求报文响应


一、日志系统的运行流程

步骤:

  1. 单例模式(局部静态变量懒汉方法)获取实例。
  2. 主程序一开始Log::get_instance()->init()初始化实例。
    初始化后:服务器启动按当前时刻创建日志(前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count)。如果是异步(通过是否设置队列大小判断是否异步,0为同步), 工作线程将要写的内容放进阻塞队列,还创建了写线程用于在阻塞队列里取出一个内容(指针),写入日志。
  3. 其他功能模块调用write_log()函数写日志。(write_log:实现日志分级、分文件、按天分类,超行分类的格式化输出内容。)

1.1 异步日志和同步日志的不同点

因为同步日志的,日志写入函数与工作线程串行执行,由于涉及到I/O操作,在单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。

而异步日志采用生产者-消费者模型,工作线程将所写的日志内容先存入缓冲区,写线程从缓冲区中取出内容,写入日志。并发能力比较高。

1.2 缓冲区的实现

单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。
在这里插入图片描述

在实际项目中,使用循环数组实现队列,作为两者共享的缓冲区。

二、基于Webbench的压力测试

父进程fork若干个子进程,每个子进程在用户要求时间或默认的时间内对目标web循环发出实际访问请求,父子进程通过管道进行通信,子进程通过管道写端向父进程传递在若干次请求访问完毕后记录到的总信息,父进程通过管道读端读取子进程发来的相关信息,子进程在时间到后结束,父进程在所有子进程退出后统计并给用户显示最后的测试结果,然后退出。

三、HTTP请求报文解析

http报文处理流程

  1. 浏览器端发出http连接请求,主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,工作线程从任务队列中取出一个任务进行处理。

  2. 工作线程取出任务后,调用process_read函数,通过主、从状态机对请求报文进行解析。(中篇讲)

  3. 解析完之后,跳转do_request函数生成响应报文,通过process_write写入buffer,返回给浏览器端。

这一部分代码在TinyWebServer/http/http_conn.h中,主要是http类的定义。

class http_conn{
public:static const int FILENAME_LEN = 200;static const int READ_BUFFER_SIZE = 2048;static const int WRITE_BUFFER_SIZE = 1024;//报文的请求方法,本项目只用到GET和POSTenum METHOD{GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATH};enum CHECK_STATE{CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};enum LINE_STATUS{LINE_OK=0,LINE_BAD,LINE_OPEN};public: http_conn(){}~http_conn(){}//初始化套接字地址,函数内部会调用私有方法initvoid init(int sockfd,const sockaddr_in &addr);void close_conn(bool real_close=true);void process();//读取浏览器发来的所有数据void read_once();bool write();sockaddr_in *get_address(){return &m_address;}void initmysql_result();//CGI使用线程池初始化数据库表void initresultFile(connection_pool *connPool);private:void init();HTTP_CODE process_read();bool process_write(HTTP_CODE ret);HTTP_CODE parse_request_line(char *text);//主状态机解析报文中的请求头数据HTTP_CODE parse_headers(char *text);//主状态机解析报文中的请求内容HTTP_CODE parse_content(char *text);//生成响应报文HTTP_CODE do_request();//m_start_line是已经解析的字符//get_line用于将指针向后偏移,指向未处理的字符char* get_line(){return m_read_buf+m_start_line;};LINE_STATUS parse_line();void unmap();//根据响应报文格式,生成对应8个部分,以下函数均由do_request调用bool add_response(const char* format);bool add_content(const char* content);bool add_status_line(int status, const char* title);bool add_headers(int content_length);bool add_content_type();bool add_content_length(int content_length);bool add_linger();bool add_blank_line();public:static int m_epollfd;static int m_user_count;MYSQL *mysql;private:int m_sockfd;sockaddr_in m_address;//存储读取的请求报文数据char m_read_buffer[read_buffer_size];//缓冲区中m_read_buffer中的长度int m_read_idx;//m_read_buf读取的位置m_checked_idxint m_checked_idx;//m_read_buf中已经解析的字符个数int m_start_line;  //存储发出的响应报文数据char m_write_buf[write_buffer_size];//指示buffer中的长度int m_write_idx;//主状态机的状态CHECK_STATE m_check_state;//请求方法METHOD m_method;//以下为解析请求报文中对应的6个变量//存储读取文件的名称char m_real_file[FILENAME_LEN];char *m_url;char *m_version;char *m_host;int m_content_length;bool m_linger;char *m_file_address;        //读取服务器上的文件地址struct stat m_file_stat;struct iovec m_iv[2];        //io向量机制iovecint m_iv_count;int cgi;                    //是否启用的POSTchar *m_string;                //存储请求头数据int bytes_to_send;          //剩余发送字节数int bytes_have_send;        //已发送字节数};

这里,对read_once进行介绍。read_once读取浏览器端发送来的请求报文,直到无数据可读或对方关闭连接,读取到m_read_buffer中,并更新m_read_idx。

 1//循环读取客户数据,直到无数据可读或对方关闭连接2bool http_conn::read_once()3{4    if(m_read_idx>=READ_BUFFER_SIZE)5    {6        return false;7    }8    int bytes_read=0;9    while(true)
10    {
11        //从套接字接收数据,存储在m_read_buf缓冲区
12        bytes_read=recv(m_sockfd,m_read_buf+m_read_idx,READ_BUFFER_SIZE-m_read_idx,0);
13        if(bytes_read==-1)
14        {    
15            //非阻塞ET模式下,需要一次性将数据读完
16            if(errno==EAGAIN||errno==EWOULDBLOCK)
17                break;
18            return false;
19        }
20        else if(bytes_read==0)
21        {
22            return false;
23        }
24        //修改m_read_idx的读取字节数
25        m_read_idx+=bytes_read;
26    }
27    return true;
28}

epoll相关代码

项目中epoll相关代码部分包括了非阻塞模式,内核事件表注册事件,删除事件,重置EPOLLONESHOT事件四种。

  1. 非阻塞模式
int setnonblocking(int fd)
{int old_option = fcntl(fd,F_GETFL);int new_option = old_option | O_NONBLOCK;fcntl(fd,F_SETFL,new_option);return old_option;
} 
  1. 内核事件表注册新事件,开启EPOLL ONESHOT,针对客户端连接的描述符,listenfd不用开启
void addfd(int epollfd,int fd, bool one_shot)
{epoll_event event;event.data.fd = fd;#ifdef ETevent.events = EPOLLIN | EPOLLET | EPOLLRDHUP;#endif #ifdef LTevent.events = EPOLLIN | EPOLLRDHUP;#endif if(ont_shot) event.events |= EPOLLONESHOT;epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);setnonblocking(fd);
}

3.内核事件表删除事件

void removefd(int epollfd,int fd)
{epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);close(fd);}
  1. 重置EPOLLONESHOT事件
void modfd(int epollfd,int fd,int ev)
{epoll_event event;event.data.fd = fd;#ifdef ETevent.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;#endif #ifdef LTevent.events = ev | EPOLLONESHOT | EPOLLRDHUP;#endif epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}

服务器接收http请求

http_conn* users = new http_conn[max_fd];//创建内核事件表
epoll_event events[max_event_number];
epollfd = epoll_create(5);
assert(epoll_fd != -1);//添加listenfd
addfd(epollfd,listenfd,false);//将上述epollfd赋值给http类对象的m_epollfd属性
http_conn::m_epollfd = epollfd;while(!stop_server)
{int number = epoll_wait(epollfd,events,max_event_number, -1);if(number < 0 && errno != EINTR)break;for(int i=0;i<number;i++){int sockfd = events[i].data.fd;if(sockfd == listenfd){struct sockaddr_in client_address;socklen_t client_addrlength = sizeof(client_address);#ifdef LT //水平触发int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);if(connfd < 0)continue;if(http_conn::m_user_count >= max_fd){show_error(connfd,"Internal Server Busy");continue;}users[connfd].init(connfd,client_address);#endif#ifdef ETwhile(1){//需要不断接收数据int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);if(connfd < 0)break;if(http_conn::m_user_count >= max_fd){show_error(connfd,"Internal Server Busy");break;}users[connfd].init(connfd,client_address);}continue;#endif}else if(events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){//强制关闭连接}//pipefd[0]即读端文件描述符,pipefd[1]即写端文件描述符else if( (sockfd==pipefd[0]) && (events[i].events & EPOLLIN) ){}//处理客户连接上接收到的数据else if (events[i].events & EPOLLIN){//读入对应缓冲区if (users[sockfd].read_once()){//若监测到读事件,将该事件放入请求队列pool->append(users + sockfd);}else{//服务器关闭连接}}}
}

四、HTTP请求报文响应


相关文章:

从零实现Web服务器(三):日志优化,压力测试,实战接收HTTP请求,实战响应HTTP请求

文章目录一、日志系统的运行流程1.1 异步日志和同步日志的不同点1.2 缓冲区的实现二、基于Webbench的压力测试三、HTTP请求报文解析http报文处理流程epoll相关代码服务器接收http请求四、HTTP请求报文响应一、日志系统的运行流程 步骤: 单例模式&#xff08;局部静态变量懒汉…...

MFC入门

1.什么是MFC?全称是Microsoft Foundation Class Library&#xff0c;我们称微软基础类库。它封装了windows应用程序的各种API以及相关机制的C类库MFC是一个大的类库MFC是一个应用程序框架MFC类库常用的头文件afx.h-----将各种MFC头文件包含在内afxwin.h-------包含了各种MFC窗…...

1、H5+CSS面试题

1, HTML5中新增了哪些内容&#xff1f;广义上的html5指的是最新一代前端开发技术的总称&#xff0c;包括html5&#xff0c;CSS3&#xff0c;新增的webAPI。Html中新增了header,footer,main,nav等语义化标签&#xff0c;新增了video,audio媒体标签&#xff0c;新增了canvas画布。…...

亚马逊云科技重磅发布《亚马逊云科技汽车行业解决方案》

当今&#xff0c;随着万物智联、云计算等领域的高速发展&#xff0c;创新智能网联汽车和车路协同技术正在成为车企加速发展的关键途径&#xff0c;推动着汽车产品从出行代步工具向着“超级智能移动终端”快速转变。挑战无处不在&#xff0c;如何抢先预判&#xff1f;随着近年来…...

Springboot扩展点之FactoryBean

前言FactoryBean是一个有意思&#xff0c;且非常重要的扩展点&#xff0c;之所以说是有意思&#xff0c;是因为它老是被拿来与另一个名字比较类似的BeanFactory来比较&#xff0c;特别是在面试当中&#xff0c;动不动就问你&#xff1a;你了解Beanfactory和FactoryBean的区别吗…...

新库上线 | CnOpenDataA股上市公司交易所监管措施数据

A股上市公司交易所监管措施数据 一、数据简介 证券市场监管是指证券管理机关运用法律的、经济的以及必要的行政手段&#xff0c;对证券的募集、发行、交易等行为以及证券投资中介机构的行为进行监督与管理。 我国《证券交易所管理办法》第十二条规定&#xff0c;证券交易所应当…...

同步辐射XAFS表征方法的应用场景分析

X射线吸收精细结构XAFS表征方法是一种用于研究物质结构和化学环境的分析技术。XAFS 使用 X 射线照射到物质表面&#xff0c;并观察由此产生的 X 光吸收谱。 ​XAFS 技术通常应用于研究高分子物质、生物分子、纳米结构和其他类型的物质。例如&#xff0c;XAFS 可以用来研究高分子…...

06 antdesign react Anchor 不同页面之间实现锚点

react Anchor 不同页面之间实现锚点一、定义二、使用步骤三、开发流程(一)、组件(二)、页面布局(三)、点击事件(四)、总结说明一、react单页面应用&#xff0c;当前页面的锚点二、react单页面应用&#xff0c;不同页面的锚点思路&#xff1a;锚点只能在当前页面使用&#xff0c…...

mysql调优-内存缓冲池

因本地查询和服务器查询相比服务器慢了很多&#xff0c;同样的数据&#xff0c;同样的sql查询&#xff0c;考虑了是不是链接太多了&#xff0c;自行查询了下&#xff0c;我使用的c3p0的链接池&#xff0c;配置一个小时超时&#xff0c;正常情况下是20多个链接&#xff0c;而mys…...

【LeetCode】每日一题(5)

目录 题目&#xff1a;2341. 数组能形成多少数对 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;2341. 数组能形成多少数对 -…...

输入任意多个整数, 把这些数据保存到文件data.txt中.(按ctrl + z)

#pragma once #include <iostream> #include <fstream> using namespace std; /* 输入任意多个整数, 把这些数据保存到文件data.txt中. 如果在输入的过程中, 输入错误, 则提示用户重新输入. 指导用户输入结束(按ctrl z) [每行最多保存10个整数] */ int main() { …...

Mysql数据库的时间(3)一如何用函数插入时间

暂时用下面四个日期函数插入时间 如:insert into Stu(time) values (now()); Mysql的时间函数描述对应的Mysql的时间类型now()/sysdate()NOW()函数以YYYY-MM-DD HH:MM:SS返回当前的日期时间date/time/dateTime/timeStamp/yearcurDate()/current_date()返回当前的日期YYYY-M…...

关于eval函数(将JSON格式的字符串转换成JSON格式对象)

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>关于eval函数</title> </head> <body> <!--JSON是一种行业内的数据交换格式标准。在JS当中以对象的形式存在…...

2023最强软件测试面试题,精选100 道,内附答案版,冲刺金3银4

精挑细选&#xff0c;整理了100道软件测试面试题&#xff0c;都是非常常见的面试题&#xff0c;篇幅较长&#xff0c;所以只放出了题目&#xff0c;答案在评论区&#xff01; 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 2、我现在有…...

一文搞懂Docker容器里进程的 pid 是如何申请出来的?

如果大家有过在容器中执行 ps 命令的经验&#xff0c;都会知道在容器中的进程的 pid 一般是比较小的。例如下面我的这个例子。 # ps -ef PID USER TIME COMMAND1 root 0:00 ./demo-ie13 root 0:00 /bin/bash21 root 0:00 ps -ef 不知道大家是否和我一样…...

若依框架如何新增自定义主题风格

若依框架新增主题风格1.实现结果2.实现步骤2.1Settings目录下2.2 variables.scss2.3 sidebar.scss2.4 Logo.vue2.5 Siderbar目录下的index.vue1.实现结果 2.实现步骤 需要改动的文件目录&#xff1a; 2.1Settings目录下 <div class"setting-drawer-block-checbox-it…...

C语言格式化输入和输出; Format格式化

Format格式化 %1s或者%2s,%3s:取字符串的前1,2或者3位。%*c:屏蔽一个字符。%[A-Z]:取一个A到Z的值。 %[^a-z]:不取a到z的值。 %[^\n]&#xff1a;取非换行之前的值。printf("%5d", a):左边补 格式化&#xff1a;有正则在其中。 int main() {printf("%5d\n&quo…...

Revit教程:怎么关掉工具栏的实时提示?

一、Revit中如何关闭工具栏的实时帮助提示 如图1所示&#xff0c;Revit会对每一个命令有一个简单的图文说明&#xff0c;方便不熟悉软件的用户使用。对于已经熟悉软件的用户&#xff0c;会觉得鼠标在菜单上悬停时弹出的实时帮助页面很干扰使用&#xff0c;而且很占内存资源&…...

javascript 简介

JavaScript 是互联网上最流行的脚本语言&#xff0c;这门语言可用于 HTML 和 web&#xff0c;更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。JavaScript 是脚本语言JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代码。JavaScript…...

医学图象分割常用损失函数(附Pytorch和Keras代码)

对损失函数没有太大的了解&#xff0c;就是知道它很重要&#xff0c;搜集了一些常用的医学图象分割损失函数&#xff0c;学习一下&#xff01; 医学图象分割常见损失函数前言1 Dice Loss2 BCE-Dice Loss3 Jaccard/Intersection over Union (IoU) Loss4 Focal Loss5 Tvesky Loss…...

【新2023】华为OD机试 - 病菌感染(Python)

华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 病菌感染 题目 在一个地图中(地图有N*N个区域组成) 有部分区域被感染病菌 感染区域每天都会把周围上下左右的四个区域感染 请根据给定的地图计算多少天以后全部区域都会被感染 如果初始地图上所有区域都…...

QGIS中进行批量坡向计算

QGIS中进行坡向计算1. 坡向计算中的Z因子&#xff08;垂直单位与水平单位的比值&#xff09;2. 坡向计算步骤坡度计算的姊妹篇–坡向计算来了 1. 坡向计算中的Z因子&#xff08;垂直单位与水平单位的比值&#xff09; z 因子是一个转换因子&#xff0c;当输入表面的垂直坐标&…...

Redis持久化机制

一、RDB RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。 RDB是Redis默认的持久化机制 - RDB持久化文件&#xff0c;速度比较快&#xff0c;而且存储的是一个二进制的文件&#xff0c;传输起来很方便。 - RD…...

2、VUE面试题

1, 如何让CSS只在当前组件中起作用&#xff1f;在组件中的style前面加上scoped2、<keep-alive></keep-alive>的作用是什么?keep-alive 是 Vue 内置的一个组件&#xff0c;可以使被包含的组件保留状态&#xff0c;或避免重新渲染。3, vue组件中如何获取dom元素?使…...

DeepSort:论文翻译

文章目录摘要1、简介2、利用深度关联度量进行排序2.1、轨迹处理和状态估计2.3、匹配的级联2.4、深度外观描述符3、实验4、结论论文链接&#xff1a;https://arxiv.org/pdf/1703.07402.pdf摘要 简单在线实时跟踪(SORT)是一种实用的多目标跟踪方法&#xff0c;专注于简单、有效的…...

Debezium系列之:重置Sqlserver数据库的LSN拉取历史数据

Debezium系列之:重置Sqlserver数据库的LSN拉取历史数据 一、需求背景二、理解LSN三、sqlserver offset数据样式四、写入历史LSN五、观察历史数据六、拉取最新数据一、需求背景 需要重新拉取sqlserver数据库采集表的历史数据或者connector故障,从指定LSN处拉取历史数据二、理解…...

一起Talk Android吧(第四百九十四回:在Android中使用MQTT通信四)

文章目录 问题概述解决办法经验总结各位看官们大家好,这一回中咱们说的例子是" 在Android中使用MQTT通信四",本章回内容与前后章节内容无关联。闲话休提,言归正转,让我们一起Talk Android吧! 问题概述 我们在很早之前介绍过MQTT的用法,本章回是在原来的基础上…...

【vcpkg】cpprestsdk之64位编译链接及踩坑

▒ 目录 ▒&#x1f6eb; 问题描述1️⃣ 多版本vs报错指定VS路径2️⃣ error LNK2001: 问题排查通过IDA打开lib文件&#xff0c;确认导出内容查看源码增加参数--editable&#xff0c;重新编译3️⃣ error LNK2001: 外部符号__imp_?close_...去除__imp_&#x1f6ec; 结论vcpkg…...

初始QML

Qt Quick的介绍 &#xff1a; Qt Quick是QML的标准类型和功能库。它包括视觉类型&#xff0c;交互类型&#xff0c;动画&#xff0c;模型和视图&#xff0c;粒子效果和着色器效果。QML 应用程序开发人员可以通过单个导入语句访问所有这些功能&#xff0c;简单来说Qt Quick是一…...

SpringAOP切面实例实现对数据过滤返回,SpringAOP切面实现对用户权限控制,通过@Around注解过滤修改方法返回值

文章目录需求内容:实现&#xff1a;步骤一&#xff1a;导入SpringAOP相关依赖pom.xml步骤二&#xff1a;自定义两个注解步骤三&#xff1a;需要用到的实体类**步骤四&#xff1a;切面具体实现**用法1.需要过滤返回值的方法添加注解FilterByUser2.数据Dto在需要过滤的字段添加Fi…...

顶级复刻手表网站/推蛙网络

转载于:https://www.cnblogs.com/viplued/p/7765149.html...

手机营销型网站建设/手游代理加盟哪个平台最强大

我正在为数据库实现一个漂亮的基本搜索引擎&#xff0c;用户可能会在其中包含不同类型的信息。搜索本身由几个联合组成&#xff0c;选择结果总是合并到3列中。然而&#xff0c;返回的数据正在从不同的表中获取。每个查询使用$ term进行匹配&#xff0c;我将其绑定到“&#xff…...

做网站 人工智能/网络培训机构

一、准备工作 &#xff08;只做一次准备工作&#xff0c;以后都会很方便&#xff09; 1. 安装pip &#xff08;1&#xff09;下载pip到D:\download pip下载地址&#xff1a;https://pypi.python.org/pypi/pip#downloads &#xff08;2&#xff09;下载后解压到当前目录&#xf…...

什么是推广员/公众号排名优化软件

Jmeter界面模式&#xff0c;在运行的时候会消耗较多资源&#xff0c;所以为了节约资源可以采用命令行模式运行Jmeter测试脚本。 一、操作步骤&#xff1a; 现在Jmeter上做好脚本&#xff1b;脚本做好后&#xff0c;将Jmeter关掉&#xff0c;windows下打开dos窗口&#xff1b;在…...

wordpress获取tags/百度推广客户端app下载

在很多的嵌入式的软件中&#xff0c;我们拿到代码&#xff0c;如果有文档先看看文档&#xff0c;没有文档也只能从Makefile的角度去看看软件的基本架构了。有一些大型的嵌入式软件项目的Makefile写的很复杂&#xff0c;这样在看学习Makefile的时候&#xff0c;需要一些技巧去分…...

沧州市做网站价格/2023年的新闻十条

声明&#xff1a;本文引用吴恩达教授的DeepLearning课程内容。 滑动窗口法的卷积实现虽然效率更高&#xff0c;但仍然存在问题&#xff0c;不能输出最精准的边界框。因为滑动窗口法中&#xff0c;取这些离散的位置集合并不能完全匹配目标大小、位置。 其中一个能得到更精准边界…...