当前位置: 首页 > 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…...

2026年建议收藏|顶尖配置的降AIGC网站 —— 千笔·降AIGC助手

在AI技术迅速渗透学术写作领域的今天&#xff0c;越来越多的学生、研究人员和职场人士开始依赖AI工具来提升写作效率。然而&#xff0c;随着各大查重系统对AI生成内容的识别能力不断提升&#xff0c;论文中的“AI率超标”问题逐渐成为影响学术成果的关键障碍。无论是知网、维普…...

ANIMATEDIFF PRO优化升级:MP4输出、LUT调色,让动画更专业

ANIMATEDIFF PRO优化升级&#xff1a;MP4输出、LUT调色&#xff0c;让动画更专业 1. 从“能动的GIF”到“可交付的成片”&#xff0c;你只差这一步 如果你已经用ANIMATEDIFF PRO生成过几段动画&#xff0c;可能会发现一个现实问题&#xff1a;生成的GIF文件&#xff0c;在手机…...

科研绘图还在啃软件?Paperxie AI:一句话生成学术图表,流程图 / CAD 图全搞定

paperxie科研绘图https://www.paperxie.cn/drawinghttps://www.paperxie.cn/drawing 在学术圈流传着这样一句话&#xff1a;「论文写得好&#xff0c;不如图画得巧」。一张清晰规范的图表&#xff0c;不仅能让审稿人眼前一亮&#xff0c;更是科研成果可视化的核心载体。但现实是…...

【简记】vbox虚拟机放开nat域名解析支持宿主机专用网络域名解析

以cmd进入vbox目录&#xff0c;执行VBoxManage命令进行操作 D:\MyTools\VirtualBox>.\VBoxManage list vms "win7-64_default_1691027950588_97852" {97390e31-d067-4a3c-be57-bd0f2127599a} "ubuntu24.04.2" {ca20ffcd-db4d-4ca8-b81d-2d6f1db887d7} &…...

Shopee买家账号注册与养号实战经验:跨境电商账号体系搭建思路

在跨境电商不断发展的今天&#xff0c;Shopee 已成为东南亚及多个新兴市场的重要电商平台。对于从事跨境业务的人来说&#xff0c;Shopee买家账号的注册与养护同样十分关键。一个稳定、安全的买家账号&#xff0c;不仅能保障日常使用的顺畅&#xff0c;也有助于长期的账号管理与…...

终极指南:如何测试AST Explorer解析器准确性的7个核心策略

终极指南&#xff1a;如何测试AST Explorer解析器准确性的7个核心策略 【免费下载链接】astexplorer A web tool to explore the ASTs generated by various parsers. 项目地址: https://gitcode.com/gh_mirrors/as/astexplorer AST Explorer是一款强大的Web工具&#x…...

De Boor算法实战:从理论到B样条曲线点计算的完整实现

1. 从“搭积木”到“画曲线”&#xff1a;为什么你需要De Boor算法&#xff1f; 如果你玩过3D建模、做过动画路径设计&#xff0c;或者搞过机器人轨迹规划&#xff0c;那你肯定遇到过“画一条光滑曲线”这个看似简单、实则让人头疼的问题。直接用直线段连接控制点&#xff1f;太…...

华为也出“龙虾”了,热闹了。。。

2026 年 3 月 11 日&#xff0c;华为基于鸿蒙系统推出了“小艺 Claw”Beta 版。华为终端 BG 首席执行官何刚 当天也公开展示了相关界面&#xff0c;并评价称“很惊喜&#xff0c;挺好用的&#xff0c;能帮我干不少事儿”。从目前公开流出的介绍来看&#xff0c;这款产品已经不再…...

3步解决Windows运行库缺失难题,让系统兼容性提升90%

3步解决Windows运行库缺失难题&#xff0c;让系统兼容性提升90% 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否也曾遇到过"找不到MSVCR120.dll"…...

告别“直觉编程”:OpenSpec 规范驱动开发 (SDD) 工作流完全指南

在 AI 辅助编程时代&#xff0c;我们常常面临一个痛点&#xff1a;当你让 AI 开发一个复杂功能时&#xff0c;它一开始表现得很聪明&#xff0c;但随着对话轮数的增加&#xff0c;它开始遗忘上下文、偏离需求&#xff0c;最终陷入胡乱猜测的**“直觉编程” (Vibe Coding)** 陷阱…...