CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
  • C++语言面试问题集锦
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • 🔥C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 🔥从零用C语言写一个Redis
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 🔥使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 第1章高频C++11重难点知识解析

  • 第2章Linux GDB高级调试指南

  • 第3章C++多线程编程从入门到进阶

  • 第4章C++网络编程重难点解析

  • 第5章网络通信故障排查常用命令

  • 第6章高性能网络通信协议设计精要

  • 第7章高性能服务结构设计

  • 第8章Redis 网络通信模块源码分析

  • 第9章后端服务重要模块设计探索

    • 9.1 如何设计断线自动重连机制
    • 9.2 保活机制与心跳包
    • 9.3 日志模块的设计
    • 9.4 错误码系统的设计
    • 9.5 监控端口
  • C++后端开发进阶
  • 第9章后端服务重要模块设计探索
zhangxf
2023-04-05

9.5 监控端口

在实际的项目中,出于定位问题和统计数据的要求,往往会给一些正在运行的服务开一些对外的“口子”,技术和运维人员可以给正在运行的服务发一些指定得到一些想要的结果。

发送的这些指令即可以是短连接的形式(如发送一个 http 请求)或者长连接的形式,为了方便使用,我们通常不会专门开发一些工具去做这件事,而是利用一些现成的命令如 nc、telnet。

我在我的即时通讯软件 Flamingo 的 chatserver 中实现了这样一个监控功能,使用的是长连接。我们来看一下效果。我的 chatserver 的地址是 127.0.0.1,端口号是 8888。可以使用 nc -v 127.0.0.1 8888 连上服务器,效果如下:

连接成功后,服务器会输出 5 条命令,每条命令都有相应的解释,输入对应的命令可以执行相应的操作。

help 查看所有整个监控端口支持哪些命令;

ul 显示当前内存中在线的用户信息,

su 输出指定用户的信息,需要使用 su userid 格式来指定某个用户的 userid;

elpb 和 dlpb 是启用和禁用是否将网络包的二进制格式写入日志文件,用于排查网络包问题。当然,这两个命令可以合二为一,通过不同的参数来指定。

执行上述命令效果如下:

监控命令的实际用途就是在服务运行过程中查询和修改服务内存中的一些信息。其实现原理也很简单,在即某个端口(这里例子中是 8888)上开启一个侦听,然后接收连接,处理连接上来的端口发送的监控命令。由于我这里使用的是 nc 命令作为监控端口的客户端工具,因此其每条指令都是以 \n 结束的,我们把 \n 作为每个数据包的结束标志。

实现逻辑如下:

void MonitorSession::onRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
{
    std::string buf;
    std::string substr;
    size_t pos;
    size_t totalsize = 0;
    while (true)
    {
        //xxx\nyyy\nuuuu\njjjjj
        buf.clear();
        buf = pBuffer->toStringPiece();
        while (true)
        {
            pos = buf.find("\n");
            if (pos != std::string::npos)
            {
                if (pos == 0)
                    substr = "\n";
                else
                    substr = buf.substr(0, pos);
                totalsize += substr.length();
                buf = buf.substr(pos + 1);
                LOGI("recv cmd: %s", substr.c_str());
                //LOGI << "buf: " << substr;
                process(conn, substr);
            }
            else
            {
                if (totalsize > 0)
                    pBuffer->retrieve(totalsize);
                return;
            }             
        }// end inner while-loop     
    }// end outer while-loop
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

MonitorSession::onRead 方法中每收到一段含 \n 标志的数据,就会将其当作客户端的一个监控命令,然后调用 process 方法对这条命令进行解析(代码 25 行),process 方法实现如下:

bool MonitorSession::process(const std::shared_ptr<TcpConnection>& conn, const std::string& inbuf)
{
    if (inbuf == "\n")
        return false;

    std::vector<std::string> v;
    StringUtil::split(inbuf, v, " ");

    if (v.empty())
        return false;
    else
    {
        if (v[0] == g_helpInfo[0].cmd)
            showHelp();
        else if (v[0] == g_helpInfo[1].cmd)
        {
            if (v.size() >= 2)
                showOnlineUserList(v[1]);
            else
                showOnlineUserList("");
        }
        else if (v[0] == g_helpInfo[2].cmd)
        {
            if (v.size() < 2)
            {
                char tip[32] = { "please specify userid.\n" };
                send(tip, strlen(tip));
            }
            else
            {
                showSpecifiedUserInfoByID(atoi(v[1].c_str()));
            }
                
        }
        else if (v[0] == g_helpInfo[3].cmd)
        {
            //开启日志数据包打印二进制字节
            Singleton<ChatServer>::Instance().enableLogPackageBinary(true);

            char tip[32] = { "OK.\n" };
            send(tip, strlen(tip));
        }
        else if (v[0] == g_helpInfo[4].cmd)
        {
            //开启日志数据包打印二进制字节
            Singleton<ChatServer>::Instance().enableLogPackageBinary(false);

            char tip[32] = { "OK.\n" };
            send(tip, strlen(tip));
        }
        else
        {
            //客户端发送不支持的命令
            char tip[32] = { "cmd not support\n" };
            send(tip, strlen(tip));
        }
    }

    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

process 方法的逻辑其实就是简单的字符串匹配,匹配哪条指令,执行哪条指令,执行完了之后将执行结果或状态返回给监控客户端(调用 send 方法)。所有的命令说明定义在全局数据 g_helpInfo 中:

struct HelpInfo
{
    std::string cmd;
    std::string tip;
};

const HelpInfo g_helpInfo[] = {
    { "help", "show help info" },
    { "ul",   "show online user list" },
    { "su", "show userinfo specified by userid: su [userid]" },
    { "elpb", "enable log package binary data" },
    { "dlpb", "disable log package binary data" }
};
1
2
3
4
5
6
7
8
9
10
11
12
13

如果客户端发送一个不支持的命令,服务器会返回 “cmd not support” 的信息:

关于监控端口我还有几点要提醒下读者:

  1. 为了服务器的安全性,监控端口的 ip 地址和端口号通常应该配置成只允许内网访问,不建议暴露在公网上;
  2. 监控端口输出的信息不应该暴露出敏感信息,例如用户的密码,如果必须要访问用户敏感信息,必须通过进一步的指令权限认证才可以访问。
  3. 监控端口在获取内存中某些数据时,如果这些数据会被多个线程访问,应尽快将这些数据复制一份副本出来,以减小锁的粒度,不要影响程序中正常运行的线程处理这些数据的效率。

关于监控端口的原理很简单,灵活使用它可以为上线后的服务维护工作带来很多便利。

9.4 错误码系统的设计

← 9.4 错误码系统的设计

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
C++语言面试问题集锦 目录与说明
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式