从零实现Web服务器(三):日志优化,压力测试,实战接收HTTP请求,实战响应HTTP请求
文章目录
- 一、日志系统的运行流程
- 1.1 异步日志和同步日志的不同点
- 1.2 缓冲区的实现
- 二、基于Webbench的压力测试
- 三、HTTP请求报文解析
- http报文处理流程
- epoll相关代码
- 服务器接收http请求
- 四、HTTP请求报文响应
一、日志系统的运行流程
步骤:
- 单例模式(局部静态变量懒汉方法)获取实例。
- 主程序一开始Log::get_instance()->init()初始化实例。
初始化后:服务器启动按当前时刻创建日志(前缀为时间,后缀为自定义log文件名,并记录创建日志的时间day和行数count)。如果是异步(通过是否设置队列大小判断是否异步,0为同步), 工作线程将要写的内容放进阻塞队列,还创建了写线程用于在阻塞队列里取出一个内容(指针),写入日志。 - 其他功能模块调用write_log()函数写日志。(write_log:实现日志分级、分文件、按天分类,超行分类的格式化输出内容。)
1.1 异步日志和同步日志的不同点
因为同步日志的,日志写入函数与工作线程串行执行,由于涉及到I/O操作,在单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。
而异步日志采用生产者-消费者模型,工作线程将所写的日志内容先存入缓冲区,写线程从缓冲区中取出内容,写入日志。并发能力比较高。
1.2 缓冲区的实现
单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。大概的结构如下图。

在实际项目中,使用循环数组实现队列,作为两者共享的缓冲区。
二、基于Webbench的压力测试
父进程fork若干个子进程,每个子进程在用户要求时间或默认的时间内对目标web循环发出实际访问请求,父子进程通过管道进行通信,子进程通过管道写端向父进程传递在若干次请求访问完毕后记录到的总信息,父进程通过管道读端读取子进程发来的相关信息,子进程在时间到后结束,父进程在所有子进程退出后统计并给用户显示最后的测试结果,然后退出。
三、HTTP请求报文解析
http报文处理流程
-
浏览器端发出http连接请求,主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列,工作线程从任务队列中取出一个任务进行处理。
-
工作线程取出任务后,调用process_read函数,通过主、从状态机对请求报文进行解析。(中篇讲)
-
解析完之后,跳转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事件四种。
- 非阻塞模式
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;
}
- 内核事件表注册新事件,开启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);}
- 重置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请求报文响应一、日志系统的运行流程 步骤: 单例模式(局部静态变量懒汉…...
MFC入门
1.什么是MFC?全称是Microsoft Foundation Class Library,我们称微软基础类库。它封装了windows应用程序的各种API以及相关机制的C类库MFC是一个大的类库MFC是一个应用程序框架MFC类库常用的头文件afx.h-----将各种MFC头文件包含在内afxwin.h-------包含了各种MFC窗…...
1、H5+CSS面试题
1, HTML5中新增了哪些内容?广义上的html5指的是最新一代前端开发技术的总称,包括html5,CSS3,新增的webAPI。Html中新增了header,footer,main,nav等语义化标签,新增了video,audio媒体标签,新增了canvas画布。…...
亚马逊云科技重磅发布《亚马逊云科技汽车行业解决方案》
当今,随着万物智联、云计算等领域的高速发展,创新智能网联汽车和车路协同技术正在成为车企加速发展的关键途径,推动着汽车产品从出行代步工具向着“超级智能移动终端”快速转变。挑战无处不在,如何抢先预判?随着近年来…...
Springboot扩展点之FactoryBean
前言FactoryBean是一个有意思,且非常重要的扩展点,之所以说是有意思,是因为它老是被拿来与另一个名字比较类似的BeanFactory来比较,特别是在面试当中,动不动就问你:你了解Beanfactory和FactoryBean的区别吗…...
新库上线 | CnOpenDataA股上市公司交易所监管措施数据
A股上市公司交易所监管措施数据 一、数据简介 证券市场监管是指证券管理机关运用法律的、经济的以及必要的行政手段,对证券的募集、发行、交易等行为以及证券投资中介机构的行为进行监督与管理。 我国《证券交易所管理办法》第十二条规定,证券交易所应当…...
同步辐射XAFS表征方法的应用场景分析
X射线吸收精细结构XAFS表征方法是一种用于研究物质结构和化学环境的分析技术。XAFS 使用 X 射线照射到物质表面,并观察由此产生的 X 光吸收谱。 XAFS 技术通常应用于研究高分子物质、生物分子、纳米结构和其他类型的物质。例如,XAFS 可以用来研究高分子…...
06 antdesign react Anchor 不同页面之间实现锚点
react Anchor 不同页面之间实现锚点一、定义二、使用步骤三、开发流程(一)、组件(二)、页面布局(三)、点击事件(四)、总结说明一、react单页面应用,当前页面的锚点二、react单页面应用,不同页面的锚点思路:锚点只能在当前页面使用,…...
mysql调优-内存缓冲池
因本地查询和服务器查询相比服务器慢了很多,同样的数据,同样的sql查询,考虑了是不是链接太多了,自行查询了下,我使用的c3p0的链接池,配置一个小时超时,正常情况下是20多个链接,而mys…...
【LeetCode】每日一题(5)
目录 题目:2341. 数组能形成多少数对 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 写在最后: 题目: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
精挑细选,整理了100道软件测试面试题,都是非常常见的面试题,篇幅较长,所以只放出了题目,答案在评论区! 测试技术面试题 1、什么是兼容性测试?兼容性测试侧重哪些方面? 2、我现在有…...
一文搞懂Docker容器里进程的 pid 是如何申请出来的?
如果大家有过在容器中执行 ps 命令的经验,都会知道在容器中的进程的 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.实现步骤 需要改动的文件目录: 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]:取非换行之前的值。printf("%5d", a):左边补 格式化:有正则在其中。 int main() {printf("%5d\n&quo…...
Revit教程:怎么关掉工具栏的实时提示?
一、Revit中如何关闭工具栏的实时帮助提示 如图1所示,Revit会对每一个命令有一个简单的图文说明,方便不熟悉软件的用户使用。对于已经熟悉软件的用户,会觉得鼠标在菜单上悬停时弹出的实时帮助页面很干扰使用,而且很占内存资源&…...
javascript 简介
JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web,更可广泛用于服务器、PC、笔记本电脑、平板电脑和智能手机等设备。JavaScript 是脚本语言JavaScript 是一种轻量级的编程语言。JavaScript 是可插入 HTML 页面的编程代码。JavaScript…...
医学图象分割常用损失函数(附Pytorch和Keras代码)
对损失函数没有太大的了解,就是知道它很重要,搜集了一些常用的医学图象分割损失函数,学习一下! 医学图象分割常见损失函数前言1 Dice Loss2 BCE-Dice Loss3 Jaccard/Intersection over Union (IoU) Loss4 Focal Loss5 Tvesky Loss…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
