当前位置: 首页 > news >正文

问题排查|记录一次基于mymuduo库开发的服务器错误排查(回响服务器无法正常工作)

问题背景:

服务器程序如下:

#include <mymuduo/TcpServer.h>
#include <mymuduo/Logger.h>#include <string>
#include <functional>class EchoServer
{
public:EchoServer(EventLoop *loop,const InetAddress &addr, const std::string &name): server_(loop, addr, name), loop_(loop){// 注册回调函数server_.setConnectionCallback(std::bind(&EchoServer::onConnection, this, std::placeholders::_1));server_.setMessageCallback(std::bind(&EchoServer::onMessage, this,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));// 设置合适的loop线程数量 loopthreadserver_.setThreadNum(2);}void start(){server_.start();}
private:// 连接建立或者断开的回调void onConnection(const TcpConnectionPtr &conn){if (conn->connected()){LOG_INFO("Connection UP : %s", conn->peerAddress().toIpPort().c_str());}else{LOG_INFO("Connection DOWN : %s", conn->peerAddress().toIpPort().c_str());}}// 可读写事件回调void onMessage(const TcpConnectionPtr &conn,Buffer *buf,Timestamp time){std::string msg = buf->retrieveAllAsString();conn->send(msg);conn->shutdown(); // 写端   EPOLLHUP =》 closeCallback_}EventLoop *loop_;TcpServer server_;
};int main()
{EventLoop loop;InetAddress addr(8000);EchoServer server(&loop, addr, "EchoServer-01"); // Acceptor non-blocking listenfd  create bind server.start(); // listen  loopthread  listenfd => acceptChannel => mainLoop =>loop.loop(); // 启动mainLoop的底层Pollerreturn 0;
}

另起一个终端,其中使用命令:

sudo telnet 127.0.0.1 8000

会导致服务器端有以下错误:

[INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
[INFO]2024/05/31 21:51:16: 1 events happened 

服务器一直循环打印如下内容

问题分析

epoll_wait 一直返回并触发 EPOLLIN 事件,这通常是因为某个文件描述符一直处于可读状态,但没有正确处理。这种情况最常见的原因是未正确读取客户端发送的数据,导致 epoll 一直认为有数据可读,从而不断触发可读事件。

分析和调试步骤

首先使用gdb调试,然后把整个服务跑起来,随后起另一个终端:

sudo telnet 127.0.0.1 8000

连接之后复现出问题,

随后在gdb中中断程序,成功捕捉到出问题的地方,这里大概就是在EventLoop::loop(),然后在EpollPoller::poll()处,

现在我们可以分析问题了,本来应该在新连接建立后,就由TcpConnection来进行连接的管理了,现在在循环打印poll中的内容,说明epoll_wait根本就没有呗阻塞。

再一个,就算我们把telnet连接断开,服务器仍然会不断打印EpollPoller::poll()的内容,那么抓到问题了,肯定是channel的原因。

那么我们就开始差channel吧,首先需要关注的是文件描述符是否正常,设置的感兴趣的事件是否正常,最后就是被激活的事件是否正常。

打印之后发现确实没啥毛病。

这么说的话,我们需要重新审视该问题,如果一个channel不停得在循环打印EpollPoller::poll()得内容,并且channel中封装的文件描述符也都正常,那最主要的问题就在于channel相关的回调函数没有被正确设置,或者逻辑有重大问题。

后来顺着channel执行回调的地方顺逻辑,也没发现什么问题;

然后我突然想到新连接建立的时候,那个channel是由Acceptor打包的,它的主要任务不就是监听新用户连接,打包成channel,然后把它们分发给subloop吗?

直接先看Acceptor的构造函数:

acceptChannel_.setErrorCallback(std::bind(&Acceptor::handleRead, this));

在构造函数中我竟然没有设置读回调,直接大错特错,而且Acceptor::handleRead本来就是它的读回调啊!!!

把它改成:

acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));

至此,问题解决。

相关文章:

  • Solidworks 提取模型中的零件,并组合成一个新的零件,放入特征库
  • 浅谈配置元件之计数器
  • HarmonyOS鸿蒙学习笔记(25)相对布局 RelativeContainer详细说明
  • 探索大模型技术及其前沿应用——TextIn文档解析技术
  • Python魔术方法
  • liunx配置网络的命令
  • mac电脑鼠标键盘共享软件:ShareMouse for Mac 激活版
  • CV每日论文--2024.6.4
  • 【干货】超详细域名申请和备案流程,分别需要哪些资料?
  • 性能测试学习-基本使用-元件组件介绍(二)
  • CSS - 元素竖向百分比的基准值是什么?
  • 平板显示LED背光芯片OC6700,输入3.6V~60V,升压型 LED 恒流驱动器
  • Linux设备驱动platform驱动
  • Springboot JVM监控 通过Promethus
  • PS怎么编程:深入探索Photoshop的编程奥秘
  • 345-反转字符串中的元音字母
  • Angular2开发踩坑系列-生产环境编译
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JavaScript对象详解
  • JDK 6和JDK 7中的substring()方法
  • js中的正则表达式入门
  • Lsb图片隐写
  • MySQL几个简单SQL的优化
  • NSTimer学习笔记
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • vagrant 添加本地 box 安装 laravel homestead
  • Webpack 4 学习01(基础配置)
  • 大整数乘法-表格法
  • 猴子数据域名防封接口降低小说被封的风险
  • 简单易用的leetcode开发测试工具(npm)
  • 坑!为什么View.startAnimation不起作用?
  • 区块链将重新定义世界
  • 如何合理的规划jvm性能调优
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​​​【收录 Hello 算法】9.4 小结
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)Oracle存储过程编写经验和优化措施
  • (转)ORM
  • **CI中自动类加载的用法总结
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .axf 转化 .bin文件 的方法
  • .gitignore文件---让git自动忽略指定文件
  • .java 9 找不到符号_java找不到符号
  • .Net mvc总结
  • .NET构架之我见
  • .Net面试题4
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • :如何用SQL脚本保存存储过程返回的结果集
  • @SpringBootApplication 包含的三个注解及其含义