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

news2024/11/17 23:49:39

文章目录

  • 一、日志系统的运行流程
    • 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和POST
   enum 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(){}
   //初始化套接字地址,函数内部会调用私有方法init
   void 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_idx
   int 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向量机制iovec
    int m_iv_count;
    int cgi;                    //是否启用的POST
    char *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 ET
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    #endif 
	
	#ifdef LT
	event.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 ET
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    #endif 
    #ifdef LT
    event.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 ET
			while(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请求报文响应


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

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

相关文章

手把手带你读java源码之JAVA-stream数据结构和初始化源码详解(万字长文详解)

手把手带你读java源码之JAVA-stream数据结构和初始化源码详解(万字长文详解) stream stream是java8新增的非常重要的一个特性。并且非常的常用。它实现了函数式编程。具体函数式编程的概念已经很久了&#xff0c;比如js中的箭头函数。java中也通过stream做出了支持。想深入理…

云原生安全检测器 Narrows(CNSI)的部署和使用

近日&#xff0c; 云原生安全检测器 Narrows&#xff08;Cloud Native Security Inspector&#xff0c;简称CNSI&#xff09;发布了0.2.0版本。 &#xff08;https://github.com/vmware-tanzu/cloud-native-security-inspector&#xff09; 此项目旨在对K8s集群中的工作负载进…

分布式文件管理系统(MinIO)

1.去中心化&#xff0c;每个点是对等的关系&#xff0c;通过Ngix对负载做均衡工作。 好处&#xff1a; 能够避免单点故障&#xff0c;将多块硬盘组成一个对象存储服务。 2. 使用纠删编码技术来保护数据&#xff0c;是一种回复丢失和损坏的数据的数学算法&#xff0c;他将数据分…

小红书用户画像 | 小红书数据平台

小红书的用户画像是小红书品牌营销的必备技能&#xff0c;也是小红书推广种草的一个重要前提。通过对小红书用户画像进行分析&#xff0c;对品牌进行精准营销&#xff0c;实现更高的流量转化。 2022小红书粉丝人群画像 千瓜数据在2022年发布的千瓜活跃用户画像趋势报告中分析了…

Hive---安装教程

Hive安装教程 Hive属于Hadoop生态圈&#xff0c;所以Hive必须运行在Hadoop上 文章目录Hive安装教程上传安装包解压并且更名修改 /etc/profile创建hive-site.xml将mysql的jar包放入Hive库中开启刷新配置文件hadoop开启mysql初始化启动hive上传安装包 将安装包上传到/opt/insta…

一文搞懂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 不知道大家是否和我一样…

始于日志,不止于日志,Elastic Stack全面介绍

1、Elastic Stack是什么&#xff1f; 说Elastic Stack之前&#xff0c;先说一下ELK Stack。这个词相信很多人都是耳熟能详的&#xff0c;作为一个著名的日志系统解决方案&#xff0c;应用非常广泛。 “ELK”是三个开源项目的首字母缩写词&#xff1a;Elasticsearch、Logstash…

第五章.与学习相关技巧—Batch Normalization

第五章.与学习相关技巧 5.3 Batch Normalization Batch Norm以进行学习时的mini_batch为单位&#xff0c;按mini_batch进行正则化&#xff0c;具体而言&#xff0c;就是进行使数据分布的均值为0&#xff0c;方差为1的正则化。Batch Norm是调整各层激活值的分布使其拥有适当的广…

进程组和用处

进程组&#xff1a;一个或多个进程的集合&#xff0c;进程组id是一个正整数。组长进程&#xff1a;进程组id 进程id组长进程可以创建一个进程组&#xff0c;创建该进程组的进程&#xff0c;终止了&#xff0c;只要进程组有一个进程存在&#xff0c;进程组就存在&#xff0c;与…

卷积神经网络(CNN)

目录The Basic Usage of CNNPadding&#xff08;填充&#xff09;Weights&#xff08;权重&#xff09;PoolingThe Basic Usage of CNN What are Convolutional Neural Networks? They’re basically just neural networks that use Convolutional layers&#xff08;卷积层…

家政服务小程序实战教程13-接入客服

小程序在微信里使用&#xff0c;以其无需安装随用随走为特点。但是有个问题是&#xff0c;如果提供商品或者服务的&#xff0c;用户如果有问题往往希望平台的运营方给出专业的解答。为了满足这类需求&#xff0c;就需要我们提供客服接入的功能&#xff0c;用户可以点击客服图标…

Linux使用定时任务监控java进程并拉起

需求描述&#xff1a; 设计一个脚本&#xff0c;通过Linux定时任务&#xff0c;每分钟执行一次&#xff0c;监控jar包进程是否存在&#xff0c;存在则不做动作&#xff0c;不存在则重新拉起jar包程序。 定时任务配置&#xff1a; */1 * * * * bash -x /root/myfile/jars/che…

stk 根据六根数文件生成卫星轨迹(一)

先简单介绍下上面的参数。 Propagator预报轨道模型。 TwoBody为二体&#xff08;开普勒运动模型&#xff09;。HPOP为高精度轨道模型。目前只用到这两个。 下图为六根数参数 Orbit Epoch&#xff1a;为根数时间&#xff08;UTC&#xff09; Semimajor Axis&#xff1a;长半…

软考高项——第五章进度管理

范围管理进度管理总线索规划进度管理定义活动活动排序估算活动资源估算活动时间制定进度管理计划控制进度进度管理总线索 进度管理的总线索包括&#xff1a; 1&#xff09;规划进度管理 2&#xff09;定义活动 3&#xff09;活动排序 4&#xff09;估算活动资源 5&#xff09;…

pandas基本操作

df.head()/tail() 查看头/尾5条数据&#xff1b;df.info 查看表格简明概要&#xff1b;df.dtypes 查看字段数据类型&#xff1b;df.index 查看表格索引&#xff1b;df.columns 查看表格列名&#xff1b;df.values 以array形式返回指定数据的取值&#xff1b;list(dt.groupby(&q…

vue2 使用 cesium 篇

vue2 使用 cesium 篇 今天好好写一篇哈&#xff0c;之前写的半死不活的。首先说明&#xff1a;这篇博文是我边做边写的&#xff0c;小白也是&#xff0c;实现效果会同时发布截图&#xff0c;如果没有实现也会说明&#xff0c;仅仅作为技术积累&#xff0c;选择性分享&#xff0…

远程管理时代,还得是智能化PDU才靠得住!

在如今这个信息技术高速发展的时代&#xff0c;数据中心IDC机房服务器数量与日俱增&#xff0c;提供DNS域名服务、主机托管服务、虚拟主机服务等服务的服务器是IDC最基本的功能之一。服务器需要7*24小时不间断持续工作&#xff0c;但当服务器数量很大&#xff0c;服务器工作、重…

.net6API使用AutoMapper和DTO

AutoMapper&#xff0c;是一个转换工具&#xff0c;说到AutoMapper时&#xff0c;就不得不先说DTO&#xff0c;它叫做数据传输对象(Data Transfer Object)。 通俗的来说&#xff0c;DTO就是前端界面需要用的数据结构和类型&#xff0c;而我们经常使用的数据实体&#xff0c;是数…

华为ensp模拟校园网/企业网实例(同城灾备及异地备份中心保证网络安全)

文章简介&#xff1a;本文用华为ensp对企业网络进行了规划和模拟&#xff0c;也同样适用于校园、医院等场景。如有需要可联系作者&#xff0c;可以根据定制化需求做修改。作者简介&#xff1a;网络工程师&#xff0c;希望能认识更多的小伙伴一起交流&#xff0c;私信必回。一、…

多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序)

多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序) 目录 多元回归分析 | CNN-LSTM卷积长短期记忆神经网络多输入单输出预测(Matlab完整程序)预测结果评价指标基本介绍程序设计参考资料预测结果 评价指标 训练集平均绝对误差MAE:0.69559 训练…