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

打印 上一主题 下一主题

主题 704|帖子 704|积分 2112

问题背景:

服务器程序如下:
  1. #include <mymuduo/TcpServer.h>
  2. #include <mymuduo/Logger.h>
  3. #include <string>
  4. #include <functional>
  5. class EchoServer
  6. {
  7. public:
  8.     EchoServer(EventLoop *loop,
  9.             const InetAddress &addr,
  10.             const std::string &name)
  11.         : server_(loop, addr, name)
  12.         , loop_(loop)
  13.     {
  14.         // 注册回调函数
  15.         server_.setConnectionCallback(
  16.             std::bind(&EchoServer::onConnection, this, std::placeholders::_1)
  17.         );
  18.         server_.setMessageCallback(
  19.             std::bind(&EchoServer::onMessage, this,
  20.                 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
  21.         );
  22.         // 设置合适的loop线程数量 loopthread
  23.         server_.setThreadNum(2);
  24.     }
  25.     void start()
  26.     {
  27.         server_.start();
  28.     }
  29. private:
  30.     // 连接建立或者断开的回调
  31.     void onConnection(const TcpConnectionPtr &conn)
  32.     {
  33.         if (conn->connected())
  34.         {
  35.             LOG_INFO("Connection UP : %s", conn->peerAddress().toIpPort().c_str());
  36.         }
  37.         else
  38.         {
  39.             LOG_INFO("Connection DOWN : %s", conn->peerAddress().toIpPort().c_str());
  40.         }
  41.     }
  42.     // 可读写事件回调
  43.     void onMessage(const TcpConnectionPtr &conn,
  44.                 Buffer *buf,
  45.                 Timestamp time)
  46.     {
  47.         std::string msg = buf->retrieveAllAsString();
  48.         conn->send(msg);
  49.         conn->shutdown(); // 写端   EPOLLHUP =》 closeCallback_
  50.     }
  51.     EventLoop *loop_;
  52.     TcpServer server_;
  53. };
  54. int main()
  55. {
  56.     EventLoop loop;
  57.     InetAddress addr(8000);
  58.     EchoServer server(&loop, addr, "EchoServer-01"); // Acceptor non-blocking listenfd  create bind
  59.     server.start(); // listen  loopthread  listenfd => acceptChannel => mainLoop =>
  60.     loop.loop(); // 启动mainLoop的底层Poller
  61.     return 0;
  62. }
复制代码
另起一个终端,其中使用命令:
  1. sudo telnet 127.0.0.1 8000
复制代码
会导致服务器端有以下错误:
  1. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  2. [INFO]2024/05/31 21:51:16: 1 events happened
  3. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  4. [INFO]2024/05/31 21:51:16: 1 events happened
  5. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  6. [INFO]2024/05/31 21:51:16: 1 events happened
  7. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  8. [INFO]2024/05/31 21:51:16: 1 events happened
  9. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  10. [INFO]2024/05/31 21:51:16: 1 events happened
  11. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  12. [INFO]2024/05/31 21:51:16: 1 events happened
  13. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  14. [INFO]2024/05/31 21:51:16: 1 events happened
  15. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  16. [INFO]2024/05/31 21:51:16: 1 events happened
  17. [INFO]2024/05/31 21:51:16: channel handleEvent revents: 1
  18. [INFO]2024/05/31 21:51:16: 1 events happened
复制代码
服务器一直循环打印如下内容
问题分析

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

首先使用gdb调试,然后把整个服务跑起来,随后起另一个终端:
  1. 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的构造函数:
  1. acceptChannel_.setErrorCallback(std::bind(&Acceptor::handleRead, this));
复制代码
在构造函数中我竟然没有设置读回调,直接大错特错,而且Acceptor::handleRead本来就是它的读回调啊!!!
把它改成:
  1. acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
复制代码
至此,问题解决。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

勿忘初心做自己

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表