基于reactor模式的简易web服务器

文章目录

  • 基于reactor模式的tcp服务器
    • 什么是reactor模式?
    • 实现步骤
  • 修改recv_cb逻辑变成web服务器
  • web服务器性能测试(wrk工具的使用)

基于reactor模式的tcp服务器

  • 本文基于上篇的简易tcp通信服务器基础 上进行封装,写出使用epoll的io模型和reactor模式的服务器代码。

什么是reactor模式?

  • reactor模式是非堵塞同步事件触发的处理模式,将监听客户端连接和业务处理分离,当有读写事件发生后,就执行对应事件回调函数,或者放入任务队列里,交给工作进程或者线程池去执行业务逻辑。

实现步骤

  1. 修改状态机判断,将判断IO模式改为判断事件模式,也就是在epoll_wait函数返回后的for循环判断里的状态机判断改为只判断是读事件和写事件,因为sockfd也是读事件直接归到读事件里即可,然后执行对应事件的回调函数即可。

    • 代码如下:
      //这里connlist是个全局变量,为了存储每个fd的数据的一个结构体数组,结构如下:
      //typedef int (*RCALLBACK)(int fd);
        	/*struct conn_item {
          int fd;
          char r_buffer[BUFFERSIZE];
          char w_buffer[BUFFERSIZE];
          int r_idx;
          int w_idx;
          union {//使用联合的方式区分是普通fd还是scokfd,对应不同的回调函数。
              CALL_BACK recv_callback;
              CALL_BACK accept_callback;
          } recv_t;
          CALL_BACK send_callback;
      };*/
      int ret_code = epoll_wait(epoll_fd, epoll_events, 1024, -1);
      for (int i = 0; i < ret_code; ++i) {
          int connt_fd = epoll_events[i].data.fd;
          if (epoll_events[i].events & EPOLLIN) {
              int count = connlist[connt_fd].recv_t.recv_callback(connt_fd);
              printf("recv count: %d <-- buffer: %s\n", count, connlist[connt_fd].r_buffer);
          } else if (epoll_events[i].events & EPOLLOUT){
              int count = connlist[connt_fd].send_callback(connt_fd);
              printf("send count: %d <-- buffer: %s\n", count, connlist[connt_fd].w_buffer);
          }
      }
      
  2. epoll_ctl封装抽离,每次需要更改或者添加fd状态时直接调用接口即可。

    • 代码如下:
      int sent_event(int fd, int event, int flag) {
          struct epoll_event ev;
          ev.events = event;
          ev.data.fd = fd;
          if (flag) {
              epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
          } else {
              epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
          }
      }
      
  3. 分别将io处理逻辑封装到函数里,参数只需要提供fd即可,需要写三个函数recv_cb、accept_cb、send_cb,分别对应读数据、接收新的连接、写数据。

    1. recv_cb:
      • 函数功能就是使用recv接口里的读数据逻辑封装,但是现在是将数据读到全局的conn_list里的r_buffer,然后将r_buffer的数据拷贝到w_buffer里,方便写事件拿到数据,所以在读完数据后还需要执行sentevent去注册写事件

      • 代码如下:

        //receive
            char* buffer = connlist[fd].r_buffer;
            int idx = connlist[fd].r_idx;
            int recv_len = recv(fd, buffer + idx, BUFFERSIZE - idx, 0);
            if (recv_len == -1){
                perror("recv error");
                close(fd);
            } else if (recv_len == 0) {
                //printf("disconnect\n");
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
                close(fd);
                return -1;
            }
            connlist[fd].r_idx += recv_len;
            #if 1
                memcpy(connlist[fd].w_buffer, connlist[fd].r_buffer, connlist[fd].r_idx);
                //memset(connlist[fd].r_buffer, 0, BUFFERSIZE);
                connlist[fd].w_idx = connlist[fd].r_idx;
                //connlist[fd].r_idx -= connlist[fd].r_idx;
            #else
                http_response(&connlist[fd]);
            #endif
        
            //注册写事件
            sent_event(fd, EPOLLOUT, 0);
            return recv_len;
        }
        
    2. accept_cb:
      • 就是将之前状态机里的accept新连接封装到这里,并且添加到全局list里,代码如下:
        int accept_cb(int fd) {
            struct sockaddr_in client_addr;
            int len = sizeof(struct sockaddr);
            int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);
        
            sent_event(client_fd, EPOLLIN, 1);//为新的fd注册epoll读事件
        
            // 向全局connlist添加新的连接
            connlist[client_fd].fd = client_fd;
            memset(connlist[client_fd].r_buffer, 0, sizeof(BUFFERSIZE));
            connlist[client_fd].r_idx = 0;
            memset(connlist[client_fd].w_buffer, 0, sizeof(BUFFERSIZE));
            connlist[client_fd].w_idx = 0;
        
            connlist[client_fd].recv_t.recv_callback = recv_cb;//注册回调函数
            connlist[client_fd].send_callback = send_cb;
        
            return client_fd;
        }
        
    3. send_cb:
      • send的逻辑比较简单,直接从全局conn_list里的w_buffer里拿到数据调用send函数写数据即可,注意写完数据后还需要注册读事件。

      • 代码如下:

        int send_cb(int fd) {
            //send
            char* buffer = connlist[fd].w_buffer;
            int idx = connlist[fd].w_idx;
        
            int count = send(fd, buffer, idx, 0);
            
            sent_event(fd, EPOLLIN, 0);
        
            return count;
        }
        
  4. 除此之外,还可以把服务初始化操作封装:

    • 代码如下:

      int init_server (unsigned short port) {
          int sockfd = socket(AF_INET, SOCK_STREAM, 0);
          struct sockaddr_in server_addr;
          memset(&server_addr, 0, sizeof(struct sockaddr_in));
          server_addr.sin_family = AF_INET;
          server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
          server_addr.sin_port = htons(port);
          if (-1 == bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))) {
              perror("bind error");
              return -1;
          }
      
          if (-1 == listen(sockfd, 8)) {
              perror("listen error");
              return -1;
          }
          return sockfd;
      }
      
  • 至此,基于reactor和epoll的简易tcp服务器就大功告成。

修改recv_cb逻辑变成web服务器

  • 修改recv_cb的逻辑,往w_buffer里写数据的时候,直接写一个HTTP请求格式的字符串,或者直接读取一个html文件写到w_buffer里。

  • 这里函数参数不再是fd,而是一个connection_t,其实还是上面的那个结构体,只是为了实现一连接一响应的模式。

  • 代码如下:

    //在recv_cb里调用该函数即可,之前是执行memcpy拷贝
    int http_response(connection_t* conn) {
        #if 0
        //返回字符串
        conn->w_idx = sprintf(conn->w_buffer, 
            "HTTP/1.1 200 OK\r\n"
    		"Accept-Ranges: bytes\r\n"
    		"Content-Length: 78\r\n"
    		"Content-Type: text/html\r\n"
    		"Date: Sat, 06 Aug 2023 13:16:46 GMT\r\n\r\n"
    		"<html><head><title>aha->sgt</title></head><body><h1>sgt</h1></body></html>\r\n\r\n");
        #else 
        	//返回一个html文件,这里直接用里nginx的index.html
            int fd = open("/home/sgt/project/learn/daily_learn/learn/index.html", O_RDONLY);
            struct stat fstat_buf; 
            fstat(fd, &fstat_buf);//获取文件属性
            //todo使用sendfile();
            conn->w_idx = sprintf(conn->w_buffer, 
    		"HTTP/1.1 200 OK\r\n"
    		"Accept-Ranges: bytes\r\n"
    		"Content-Length: %ld\r\n"
    		"Content-Type: text/html\r\n"
    		"Date: Sat, 06 Aug 2023 13:16:46 GMT\r\n\r\n", fstat_buf.st_size);
            conn->w_idx += read(fd, conn->w_buffer + conn->w_idx, BUFFERSIZE - conn->w_idx);
              
        #endif
        return conn->w_idx;
    }
    
  • 效果如下:
    在这里插入图片描述

web服务器性能测试(wrk工具的使用)

这里测试服务器的性能使用开源的服务器性能测试工具wrk,github地址: https://github.com/wg/wrk.git

  • clone下来后,进入wrk目录,需要有zip压缩工具,没有下载一个: sudo apt install zip,然后执行make等待编译完成。
    在这里插入图片描述
  • 测试方法:
    • 执行命令:./wrk -c 100 -d10s -t 50 +ip:port//参数可以根据自己需求去修改

      -c, --connections Connections to keep open 连接数
      -d, --duration Duration of test 间隔时间
      -t, --threads Number of threads to use 线程数

    • 这里可以优化一些,就是把代码里的输出注释掉,性能会更好。
    • 执行结果:
      • 优化前: 在这里插入图片描述
      • 优化后: 在这里插入图片描述- 这- 这里对参数做下解释:
Running 10s test @ http://www.baidu.com
#这里使用50个线程,100个连接
  50 threads and 100 connections
 各项分别为平均值,标准差,最大值以及标准差占比,一般我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大。
  Thread Stats   Avg      Stdev     Max   +/- Stdev
     # 延迟: 
    Latency   243.13ms   74.40ms 999.31ms   72.63%
    # 每秒请求数
    Req/Sec     8.70      3.72    20.00     76.50%
  4096 requests in 10.08s, 41.03MB read #10s请求4096次,产生流量41.03M
Requests/sec:    406.28	#qps:每秒的连接数
Transfer/sec:      4.07MB #每秒的流量

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

递归方法的理解

递归方法调用 &#xff1a;方法自己调用自己的现象就称为递归。 递归的分类 : 直接递归、间接递归。 直接递归&#xff1a;方法自身调用自己 public void methodA (){ methodA (); } 间接递归&#xff1a;可以理解为A()方法调用B()方法&#xff0c;B()方法调用C()方法&am…

fastllm在CPU上推理ChatGLM3-6b,即使使用CPU依然推理速度很快,就来看这篇文章

介绍: GitHub - ztxz16/fastllm: 纯c++的全平台llm加速库,支持python调用,chatglm-6B级模型单卡可达10000+token / s,支持glm, llama, moss基座,手机端流畅运行纯c++的全平台llm加速库,支持python调用,chatglm-6B级模型单卡可达10000+token / s,支持glm, llama, moss基…

【实验报告】--基础VLAN

【VLAN实验报告】 一、项目背景 &#xff08;为 Jan16 公司创建部门 VLAN&#xff09; Jan16 公司现有财务部、技术部和业务部&#xff0c;出于数据安全的考虑&#xff0c;各部门的计算机需进 行隔离&#xff0c;仅允许部门内部相互通信。公司拓扑如图 1 所示&#xff0c; …

安装uim-ui插件不成功,成功解决

安装&#xff1a;这种安装&#xff0c;umi4 不支持&#xff0c;只有umi3才支持。而我发现官网现在默认使用的umi4。 yarn add umijs/preset-ui -D 解决&#xff1a;更改umi版本重新安装umi3 npm i ant-design/pro-cli3.1.0 -g #使用umi3 (指定umi3版本) pro create user-ce…

什么是防火墙,部署防火墙有什么好处?

与我们的房屋没有围墙或界限墙一样&#xff0c;没有防护措施的计算机和网络将容易受到黑客的入侵&#xff0c;这将使我们的网络处于巨大的风险之中。因此&#xff0c;就像围墙保护我们的房屋一样&#xff0c;虚拟墙也可以保护和安全我们的设备&#xff0c;使入侵者无法轻易进入…

WEB APIS知识点案例总结

随机点名案例 业务分析: 点击开始按钮随机抽取数组中的一个数据,放到页面中点击结束按钮删除数组当前抽取的一个数据当抽取到最后一个数据的时候,两个按钮同时禁用(只剩最后一个数据不用抽了) 核心:利用定时器快速展示,停止定时器结束展示 <!DOCTYPE html> <html…

【送书福利第六期】:《AI绘画教程:Midjourney使用方法与技巧从入门到精通》

文章目录 一、《AI绘画教程&#xff1a;Midjourney使用方法与技巧从入门到精通》二、内容介绍三、作者介绍&#x1f324;️粉丝福利 一、《AI绘画教程&#xff1a;Midjourney使用方法与技巧从入门到精通》 一本书读懂Midjourney绘画&#xff0c;让创意更简单&#xff0c;让设计…

小米SU7 我劝你再等等

文 | AUTO芯球 作者 | 李逵 我必须承认我一时没忍住 犯错了 我不会被我老婆打吧 感觉有点慌呀 这不前两天 我刚提了台问界M9嘛 但是昨晚看小米汽车发布会 是真的被雷总感染到了 真的没忍住 我又冲了台小米SU7 Pro版 本来我是准备抢创始版的 结果1秒钟时间 点进去就…

Java基础语法(六)| 类和对象

前言 Hello&#xff0c;大家好&#xff01;很开心与你们在这里相遇&#xff0c;我是一个喜欢文字、喜欢有趣的灵魂、喜欢探索一切有趣事物的女孩&#xff0c;想与你们共同学习、探索关于IT的相关知识&#xff0c;希望我们可以一路陪伴~ 1. 面向对象概述 1.1 什么是面向对象 Ja…

【毕业论文】| 基于Unity3D引擎的冒险游戏的设计与实现

&#x1f4e2;博客主页&#xff1a;肩匣与橘 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由肩匣与橘编写&#xff0c;首发于CSDN&#x1f649; &#x1f4e2;生活依旧是美好而又温柔的&#xff0c;你也…

字符串(KMP)

P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; #define ll long long const int N1e6100; int n0,m; char s1[N]; char s2[N];…

【MATLAB源码-第22期】基于matlab的手动实现的(未调用内置函数)CRC循环码编码译码仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 循环码是线性分组码的一种&#xff0c;所以它具有线性分组码的一般特性&#xff0c;此外还具有循环性。循环码的编码和解码设备都不太复杂&#xff0c;且检(纠)错能力强。它不但可以检测随机的错误&#xff0c;还可以检错突发…

2024年大广赛联通沃派命题解析:赛题内容一览

2024大广赛又又又又又出新命题了&#xff0c;它就是助力青少年积极向上&#xff0c;乐观自信&#xff0c;探享多彩人生的5G时代潮牌——联通沃派&#xff0c;让我们来看看命题详情吧&#xff01; 联联通沃派是中国联通面向青少年群体推出的客户品牌&#xff0c;契合目标群体特…

数据结构 - 图

参考链接&#xff1a;数据结构&#xff1a;图(Graph)【详解】_图数据结构-CSDN博客 图的定义 图(Graph)是由顶点的有穷非空集合 V ( G ) 和顶点之间边的集合 E ( G ) 组成&#xff0c;通常表示为: G ( V , E ) &#xff0c;其中&#xff0c; G 表示个图&#xff0c; V 是图 G…

某东推荐的十大3C热榜第一名!2024随身wifi靠谱品牌推荐!2024随身wifi怎么选?

一、鼠标金榜&#xff1a;戴尔 商务办公有线鼠标 售价:19.9&#xffe5; 50万人好评 二、平板电脑金榜&#xff1a;Apple iPod 10.2英寸 售价:2939&#xffe5; 200万人好评 三、随身WiFi金榜&#xff1a;格行随身WiFi 售价:69&#xffe5; 15万人好评 四、游戏本金榜&#xff…

Codeforces Round 934 (Div. 2) D. Non-Palindromic Substring

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…

第一次给开源项目做贡献,我给 Hutool 改了行注释

大家好&#xff0c;我是松柏。 前两天在修 bug 的时候&#xff0c;写了个indexOf的方法。 这个方法是用来获取一段文本中某个字符串第 n 次出现的索引&#xff0c; 由于第一次写这个方法的时候少考虑了一种边界条件&#xff0c;导致最后查出的数据有时候会不符合预期。 我处…

【论文精读】CAM:基于上下文增强和特征细化网络的微小目标检测

文章目录 &#x1f680;&#x1f680;&#x1f680;摘要一、1️⃣ Introduction---介绍二、2️⃣Related Work---相关工作2.1 &#x1f393; 基于深度学习的对象检测器2.2 ✨多尺度特征融合2.3 ⭐️数据增强 三、3️⃣提议的方法3.1 &#x1f393; 具有上下文增强和特征细化的特…

代码随想录——删除有序数组中的重复项(Leetcode26)

题目链接 双指针思想&#xff0c;和上一篇Leetcode27类似 class Solution {public int removeDuplicates(int[] nums) {int slow 0;for(int fast 1; fast < nums.length; fast){if(nums[fast] ! nums[slow]){nums[slow] nums[fast];}}return slow 1;} }

【文末 附 gpt4.0升级秘笈】超越Sora极限,120秒超长AI视频模型诞生

120秒超长AI视频模型发布&#xff1a;开启视频生成新纪元 随着人工智能技术的迅猛发展&#xff0c;AI视频生成领域也取得了令人瞩目的突破。近日&#xff0c;一项名为“StreamingT2V”的120秒超长AI视频模型正式发布&#xff0c;标志着文生视频技术正式进入长视频时代。这一技…