muduo项目排查错误+测试

发现无法返回hello

tcp建立成功,说明连通性没问题,发送get方法却没有得到响应

说明问题出现在服务器,现在一步步排查服务器

先看服务器的初始化是否有问题

打印线程,看一下是否是出现了两个线程,一个主线程acceptor,一个从线程管理连接

博主在调试过程中,发现有时候不会打印日志???httpserver所有的初始化日志都全无

调试:每个类打印初始化和完成初始化

既然这句话打印了说明初始化完成,说明tcpserver中的base线程初始化没问题

按照顺序应该查一下acceptor

并没有打印这两句,说明acceptor的初始化有问题

单看,说明所有类的初始化都是没问题的,但是到acceptor的channel就有问题

channel传参,传入一个loop,loop是之前的baseloop没问题。要主线程去listen,问题就出在第二个参数,acceptTcp,这个函数是获取新连接的fd,而这个channel注册的应该是listenfd,所以就导致了一直执行不了后面的语句,因为这里面有个accept一直阻塞获取连接

  • 还没调用 Acceptor::listen() 方法,监听套接字(_listenfd)还没进入监听状态,根本不会有客户端连接。
  • accept() 会进入无限阻塞状态 (默认 accept() 是阻塞的),主线程会卡死在这一步,无法继续完成 Channel 的初始化,更不会打印后续的 DBG_LOG("描述acceptor可读事件")

说明已经完成服务器的初始化

服务器初始化没问题,连接看一下是否有问题,fd数量对不对

baseLoop初始化完成时,文件描述符到了6,012是输入输出错误,3是eventfd用来唤醒当前线程的,4是epollfd用来创建epoll模型的,5是定时器,6是listenfd,所以完成服务器的初始化的时候就是6,那接下来就是线程池,因为只设了一个,说明7用来唤醒当前线程,8是epoll模型,9是定时器,那来一个连接应该是10

那10号是经过我们验证的,11号呢???

原因是因为现代浏览器会发起两次tcp,第一次获取目标路径,第二次获取/favicon.ico 请求
现在看一下静态文件的处理是否正确,请求一下根目录,看一下能否拼接index.html

当访问/目录的时候,也就是web根目录,那就会拼接一个index.html,此时就会去web根目录下返回登陆界面,并且主要要设字符集,否则会出现编码问题

可以看到第二次请求,请求了一个/favicon.ico,这个就是图标,注意这里又发起连接,说明是两次

浏览器发起 /favicon.ico 请求的目的是获取标签页的自定义图标,这个请求是「后台静默发起」的,不会影响主页面的渲染和功能,收到 404 响应后,浏览器的处理行为如下:

  1. 不显示错误提示:不会像主页面请求 404 那样,显示明显的错误页面(比如「无法访问此网站」「404 Not Found」),这个 404 只会在浏览器的「开发者工具 → Network」面板中显示,普通用户不可见。
  2. 显示浏览器默认图标:标签页不会显示你的自定义图标,而是显示浏览器的默认内置图标(比如 Chrome 的彩色圆环、Edge 的蓝绿色波浪、Firefox 的橙色狐狸头等)。
  3. 不会重试请求 :现代浏览器不会因为 /favicon.ico 请求 404 而重复发起请求,即使刷新页面,下次刷新时会重新发起一次 /favicon.ico 请求,若仍无资源,依旧返回 404 并显示默认图标,不会无限重试。
  4. 不影响主页面的正常使用 :主页面如果有合法资源(比如 index.html),会正常渲染、正常交互,/favicon.ico 的 404 仅影响「标签页图标」这一个视觉细节,对核心功能无任何干扰。

现在测试hello

注册应该没问题,发过来的http请求也没问题,但是解析的过程有问题,发现非法路径没有打印出来,并且出现了段错误,说明这个检查合法路径函数有问题

假设 src = "/hello"sep = "/"

  1. 初始 offset = 0pos = src.find("/", 0) = 0
  2. 进入 pos == offset 分支,offset = 0 + 1 = 1continue
  3. 下一次循环,offset = 1 < 5src.size() 是 5),pos = src.find("/", 1) = npos
  4. 进入 pos == npos 分支,arry->push_back(src.substr(1))(添加 "hello")。
  5. 关键:此时没有更新 offsetoffset 依然是 1,循环条件 offset < 5 永远成立,无限循环 ,不断 push_back("hello"),耗尽内存。

并且更新offset的位置也有问题,没有跳过分隔符

现在没问题了,可以正常返回了

并且超时一分钟自动释放连接了

测试超时连接

现在超时连接可以自动释放,如果发送消息就会刷新
接收请求的数据,但是业务处理的时间过长,超过了设置的超时销毁时间(服务器性能达到瓶颈),观察服务端的处理。
预期结果:在⼀次业务处理中耗费太长时间,导致其他连接被连累超时,导致其他的连接有可能会超时释放。
假设超时时间为10s
假设有 12345 描述符就绪了, 在处理1的时候花费了10s处理完,超时了,导致2345描述符因为长时间没有刷新活跃度,则存在两种可能处理结果:

  1. 如果接下来的2345描述符都是通信连接描述符,恰好本次也都就绪了事件,则并不影响,因为等1处理完了,接下来就会进⾏处理并刷新活跃度。
  2. 如果接下来的2号描述符是定时器事件描述符,定时器触发超时,执⾏定时任务,就会将345描述符给释放掉,这时候⼀旦345描述符对应的连接被释放,接下来在处理345事件的时候就会导致程序崩溃(内存访问错误),因此,在任意的事件处理中,都不应该直接对连接进⾏释放,⽽应该将释放操作压⼊到任务池中,等所有连接事件处理完了,然后执⾏任务池中的任务的时候再去进⾏释放。

处理为15s,超时时间是10s

创建进程,进程一直发送http报文,这里创建了5个

可以看到每次处理都是15s,超过了超时时间

导致后面超时任务直接释放掉了后面的连接

所以我们应该把释放任务放到任务池当中执行

解决办法:

就是新增待析构队列,时间轮当中和待析构队列都有智能指针

  1. EventLoop::start() 执行 poll(),拿到就绪事件:[1(业务)、2(timerfd)、3/4/5(连接)

  2. 处理 1 号事件:业务耗时 10s,3/4/5 未刷新活跃度;

  3. 处理 2 号事件(timerfd): - onTime() 读取 times=1 → 执行 runTimerTask(); - 把 3/4/5 对应的 Timer 从桶中转移到 _pendingDeleteTimers(count=1); - 把「清空 _pendingDeleteTimers」压入任务池; - 此时 Timer count=1,未析构,3/4/5 连接未释放; 4. 处理 3/4/5 号事件:连接有效,正常处理并刷新活跃度(每次刷新都会在事件处理完之后)

  4. 执行 EventLoop 任务池: - 清空 _pendingDeleteTimers → 此时并不能真正析构,因为刚刚刷新过,导致时间轮当中又有新的智能指针

这里的核心解法是「把回调 / 释放操作延迟到「所有 IO 事件处理完」后执行

测试Content-Length问题

现在测试如果预期发送字节数和实际字节数不一样会怎么样???

描述中说会发送100,但是实际每次只发送了9字节(注意这个是body部分),这样会导致解析出问题,看一下服务器是否正常返回

因为第一次说body有100,但是此时只接受到9,那就会和后面发送的第二次77+第三次的14(77拆出来一部分)结合成第一次的100,这样第三次的报文就不是完整的了,甚至第二次都没了

可以看到一共发来231字节,前面的所有加起来发了3次77那就是231没问题

但是第三次发77的时候有14字节和前面的body组合了,那就是剩下77-14=63字节,导致这一次解析不成功,后面返回错误

所以会导致后面请求行的时候出错,因为你完全匹配不到正则表达式,所以返回400,然后关闭连接

所以这个测试说明服务器的返回是正确的,在处理不恰当的描述的时候

测试粘包问题/多数据处理能力

同一时间发多次请求,是可以正确返回的

使用PUT上传一个文件测试

使用put请求上传⼀个大文件进行保存,大文件数据的接收会被分在多次请求中接收,然后计算源⽂件和上传后保存的文件的MD5值,判断请求的接收处理是否存在问题。(这里主要观察的是上下文的处理过程是否正常。)

创建了一个hello文件,且大小为100m

接下来用客户端传输,然后服务器再写入

预期结果:用hello文件传输,然后写入到1234.txt,接着检查MD5是否一致,上传完自动断开

md5一样说明成功上传文件

压力测试

Lionbridge Translation & Localization for Global Enterprises

Webbench是知名的网站压力测试⼯具,它是由Lionbridge公司 开发。
webbench的标准测试可以向我们展⽰服务器的两项内容: 每秒钟相应请求数 和 每秒钟传输数据量
webbench测试原理是,创建指定数量的进程,在每个进程中不断创建套接字向服务器发送请求,并通过管道最终将每个进程的结果返回给主进程进⾏数据统计。
性能测试的两个重点衡量标准:吞吐量 & QPS
本次测试环境两核心2GB内存

可以看到允许的最大进程数为6859,由于要模拟上万并发量,所以进程数也需要上万,需要修改配置文件

重启机器或者退出登录重进,让配置文件生效
接下来是webbench下载

bash 复制代码
wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
tar zxvf webbench-1.5.tar.gz
cd webbench-1.5
make && make install

测试结果

100 并发下持续 60 秒、反复发起该请求 ,统计服务器的整体处理能力(能扛多少这样的请求、成功率如何)。

100并发情况下每分钟81752,并且都成功,说明抗压正常未出现失误

QPS≈1362

500并发下持续 60 秒、反复发起该请求 ,统计服务器的整体处理能力(能扛多少这样的请求、成功率如何)。

QPS≈1416,并且0失败

并发数从 100 提升到 500(5 倍),但 QPS 仅从≈1362 小幅涨到≈1416(提升约 4%),说明2 核 CPU 的处理能力已接近饱和

即使并发数拉到 500,请求成功率依然是 100%(85006 次请求全部成功)

5000并发下持续 60 秒、反复发起该请求 ,统计服务器的整体处理能力(能扛多少这样的请求、成功率如何)。

QPS≈1436

说明2 核 CPU 的计算能力已被彻底耗尽------ 此时线程池的 2 个线程始终处于满负载状态,CPU 资源无法支撑更多请求的处理,所以即使并发连接数大幅增加,服务器的请求处理效率也无法再提升。

注意:测试环境,并且这里仅仅是测了index.html,这个文件就4kb左右,一般测试需要测试静态文件,静态接口,动态接口(比如数据库查询等等),这些qps会随着查询时间而下降

结论:请求静态文件,每秒1400QPS,并且高并发下0失败
以上测试中,使⽤浏览器访问服务器,均能流畅获取请求的⻚⾯。但是根据测试结果能够看出,虽然并发量⼀直在提⾼,但是总的请求服务器的数量并没有增加很大幅度,侧面反馈了处理所耗时间更多了,基本上可以根据8.5w/min左右的请求量计算出5000并发量时服务器的极限了
但是这个测试其实意义不大,因为测试客⼾端和服务器都在同⼀台机器上,传输的速度更快,但同时抢占cpu也影响了处理,最好的⽅式就是在两台不同的机器上进⾏测试,这里只是通过这个方法告诉⼤家该如何对服务器进行性能测试。
⽬前受限于设备环境配置,尚未进行更多并发量的测试

相关推荐
*小海豚*2 小时前
在linux服务器上DNS正常,但是java应用调用第三方解析域名报错
java·linux·服务器
C++ 老炮儿的技术栈2 小时前
VS2015 + Qt 实现图形化Hello World(详细步骤)
c语言·开发语言·c++·windows·qt
Once_day2 小时前
C++之《Effective C++》读书总结(4)
c语言·c++·effective c++
柯一梦2 小时前
STL2---深入探索vector的实现
c++
消失的旧时光-19432 小时前
Linux 编辑器入门:nano 与 vim 的区别与选择指南
linux·运维·服务器
MSTcheng.2 小时前
【C++】C++11新特性(二)
java·开发语言·c++·c++11
晓13132 小时前
第七章 【C语言篇:文件】 文件全面解析
linux·c语言·开发语言
愚者游世2 小时前
Delegating Constructor(委托构造函数)各版本异同
开发语言·c++·程序人生·面试·改行学it
小镇敲码人2 小时前
探索华为CANN框架中的ACL仓库
c++·python·华为·acl·cann