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


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


假设
src = "/hello",sep = "/":
- 初始
offset = 0,pos = src.find("/", 0) = 0。- 进入
pos == offset分支,offset = 0 + 1 = 1,continue。- 下一次循环,
offset = 1 < 5(src.size()是 5),pos = src.find("/", 1) = npos。- 进入
pos == npos分支,arry->push_back(src.substr(1))(添加 "hello")。- 关键:此时没有更新
offset,offset依然是 1,循环条件offset < 5永远成立,无限循环 ,不断push_back("hello"),耗尽内存。并且更新offset的位置也有问题,没有跳过分隔符

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


并且超时一分钟自动释放连接了
测试超时连接
现在超时连接可以自动释放,如果发送消息就会刷新
接收请求的数据,但是业务处理的时间过长,超过了设置的超时销毁时间(服务器性能达到瓶颈),观察服务端的处理。
预期结果:在⼀次业务处理中耗费太长时间,导致其他连接被连累超时,导致其他的连接有可能会超时释放。
假设超时时间为10s
假设有 12345 描述符就绪了, 在处理1的时候花费了10s处理完,超时了,导致2345描述符因为长时间没有刷新活跃度,则存在两种可能处理结果:
- 如果接下来的2345描述符都是通信连接描述符,恰好本次也都就绪了事件,则并不影响,因为等1处理完了,接下来就会进⾏处理并刷新活跃度。
- 如果接下来的2号描述符是定时器事件描述符,定时器触发超时,执⾏定时任务,就会将345描述符给释放掉,这时候⼀旦345描述符对应的连接被释放,接下来在处理345事件的时候就会导致程序崩溃(内存访问错误),因此,在任意的事件处理中,都不应该直接对连接进⾏释放,⽽应该将释放操作压⼊到任务池中,等所有连接事件处理完了,然后执⾏任务池中的任务的时候再去进⾏释放。
处理为15s,超时时间是10s
创建进程,进程一直发送http报文,这里创建了5个
可以看到每次处理都是15s,超过了超时时间
导致后面超时任务直接释放掉了后面的连接
所以我们应该把释放任务放到任务池当中执行
解决办法:
就是新增待析构队列,时间轮当中和待析构队列都有智能指针
EventLoop::start() 执行 poll(),拿到就绪事件:[1(业务)、2(timerfd)、3/4/5(连接)
处理 1 号事件:业务耗时 10s,3/4/5 未刷新活跃度;
处理 2 号事件(timerfd): - onTime() 读取 times=1 → 执行 runTimerTask(); - 把 3/4/5 对应的 Timer 从桶中转移到 _pendingDeleteTimers(count=1); - 把「清空 _pendingDeleteTimers」压入任务池; - 此时 Timer count=1,未析构,3/4/5 连接未释放; 4. 处理 3/4/5 号事件:连接有效,正常处理并刷新活跃度(每次刷新都会在事件处理完之后);
执行 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下载
bashwget 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也影响了处理,最好的⽅式就是在两台不同的机器上进⾏测试,这里只是通过这个方法告诉⼤家该如何对服务器进行性能测试。
⽬前受限于设备环境配置,尚未进行更多并发量的测试

























