鸿蒙PC端C++开发实战:轻量级网络端口扫描工具

鸿蒙PC基于OpenHarmony内核构建,其Linux兼容层为C++网络编程提供了完整的POSIX套接字(Socket)支持。本文以“轻量级TCP端口扫描工具”为例,详解鸿蒙PC端C++网络编程的核心适配要点、编译调试流程,以及鸿蒙内核下网络接口的特有处理方式,同时修复编译阶段的头文件依赖问题,为网络类C++应用开发提供可直接复用的完整模板。

一、案例背景与核心特性

本案例实现的端口扫描工具具备以下能力:

  1. 扫描指定IP的指定端口范围,判断端口是否开放;
  2. 支持超时控制,适配鸿蒙PC的网络调度特性;
  3. 基于C++11封装Socket操作,面向对象设计;
  4. 兼容鸿蒙PC的ARM aarch64架构与musl libc库;
  5. 修复编译阶段timeval/fd_set未定义等核心问题。

相比系统监控工具,本案例重点体现:

  • 鸿蒙PC下的Socket网络编程适配;
  • 非阻塞IO与超时处理的鸿蒙内核兼容;
  • 多线程扫描的资源调度优化(适配鸿蒙PC的CPU核心调度策略)。

二、环境准备(复用基础配置)

2.1 编译器验证

沿用鸿蒙PC默认的Clang++编译器,验证命令:

clang++ --version

预期输出(鸿蒙PC标准版):

OHOS (BiSheng Mobile STD 203.2.0.B175-20250723142036) clang version 15.0.4 (9e3d9b8a15b2)
Target: aarch64-unknown-linux-ohos
Thread model: posix
InstalledDir: /data/app/BiSheng.org/BiSheng_1.0/llvm/bin

2.2 网络依赖确认

鸿蒙PC默认支持POSIX套接字,无需额外安装网络库,仅需确保核心头文件存在:

# 验证关键头文件可用性
ls /usr/include/sys/time.h /usr/include/sys/socket.h

三、实战开发:TCP端口扫描工具

3.1 核心代码(鸿蒙PC适配+编译错误修复版)

// 必须在所有头文件前定义POSIX宏,确保timeval/fd_set正确定义
#define _POSIX_C_SOURCE 200809L
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <chrono>
#include <climits>
// 核心修复:补充timeval/fd_set定义的头文件(鸿蒙PC必须显式包含)
#include <sys/time.h>

// 适配鸿蒙PC musl libc:定义未暴露的宏
#ifndef SO_RCVTIMEO
#define SO_RCVTIMEO 0x1006
#endif
#ifndef SO_SNDTIMEO
#define SO_SNDTIMEO 0x1005
#endif
#ifndef LINE_MAX
#define LINE_MAX 2048
#endif

// 扫描结果结构体
struct ScanResult {
    int port;
    bool is_open;
    std::string reason;
};

// 端口扫描工具类(单例模式,适配鸿蒙多线程调度)
class PortScanner {
public:
    static PortScanner& getInstance() {
        static PortScanner instance;
        return instance;
    }

    // 扫描指定IP的端口范围
    std::vector<ScanResult> scan(const std::string& ip, int start_port, int end_port, int timeout_ms = 1000) {
        std::vector<ScanResult> results;
        std::mutex mtx;
        std::vector<std::thread> threads;

        // 鸿蒙PC CPU核心数适配:线程数不超过逻辑核心数
        long cpu_cores = sysconf(_SC_NPROCESSORS_ONLN);
        int max_threads = cpu_cores > 0 ? (int)cpu_cores : 4;
        int ports_per_thread = (end_port - start_port + 1) / max_threads + 1;

        // 分线程扫描
        for (int i = 0; i < max_threads; ++i) {
            int thread_start = start_port + i * ports_per_thread;
            int thread_end = std::min(thread_start + ports_per_thread - 1, end_port);
            if (thread_start > end_port) break;

            threads.emplace_back([&, ip, thread_start, thread_end, timeout_ms]() {
                for (int port = thread_start; port <= thread_end; ++port) {
                    ScanResult res = scanSinglePort(ip, port, timeout_ms);
                    std::lock_guard<std::mutex> lock(mtx);
                    results.push_back(res);
                }
            });
        }

        // 等待所有线程完成
        for (auto& t : threads) {
            if (t.joinable()) t.join();
        }

        return results;
    }

private:
    PortScanner() = default;
    ~PortScanner() = default;
    PortScanner(const PortScanner&) = delete;
    PortScanner& operator=(const PortScanner&) = delete;

    // 扫描单个端口(核心逻辑,适配鸿蒙Socket特性)
    ScanResult scanSinglePort(const std::string& ip, int port, int timeout_ms) {
        ScanResult res;
        res.port = port;
        res.is_open = false;

        // 创建TCP套接字
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0) {
            res.reason = "Socket create failed: " + std::string(strerror(errno));
            return res;
        }

        // 设置套接字为非阻塞模式(适配鸿蒙内核调度)
        int flags = fcntl(sockfd, F_GETFL, 0);
        if (flags < 0 || fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
            close(sockfd);
            res.reason = "Set non-block failed: " + std::string(strerror(errno));
            return res;
        }

        // 设置超时(鸿蒙PC需同时设置读写超时)
        struct timeval timeout;
        timeout.tv_sec = timeout_ms / 1000;
        timeout.tv_usec = (timeout_ms % 1000) * 1000;
        setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

        // 配置目标地址
        struct sockaddr_in target_addr;
        memset(&target_addr, 0, sizeof(target_addr));
        target_addr.sin_family = AF_INET;
        target_addr.sin_port = htons(port);
        if (inet_pton(AF_INET, ip.c_str(), &target_addr.sin_addr) <= 0) {
            close(sockfd);
            res.reason = "Invalid IP address";
            return res;
        }

        // 连接端口(非阻塞模式适配鸿蒙内核)
        int ret = connect(sockfd, (struct sockaddr*)&target_addr, sizeof(target_addr));
        if (ret == 0) {
            res.is_open = true;
            res.reason = "Port open";
        } else if (errno == EINPROGRESS || errno == EWOULDBLOCK) {
            // 鸿蒙PC非阻塞连接处理:使用select检测连接状态
            fd_set write_fds;
            FD_ZERO(&write_fds);
            FD_SET(sockfd, &write_fds);

            int select_ret = select(sockfd + 1, nullptr, &write_fds, nullptr, &timeout);
            if (select_ret > 0) {
                int err;
                socklen_t err_len = sizeof(err);
                getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &err_len);
                if (err == 0) {
                    res.is_open = true;
                    res.reason = "Port open";
                } else {
                    res.reason = "Port closed: " + std::string(strerror(err));
                }
            } else if (select_ret == 0) {
                res.reason = "Connection timeout";
            } else {
                res.reason = "Select failed: " + std::string(strerror(errno));
            }
        } else {
            res.reason = "Connect failed: " + std::string(strerror(errno));
        }

        close(sockfd);
        return res;
    }
};

// 打印扫描结果(格式化输出,适配鸿蒙终端)
void printScanResults(const std::string& ip, const std::vector<ScanResult>& results) {
    std::cout << "===== HarmonyOS PC Port Scan Result =====\n";
    std::cout << "Target IP:       " << ip << "\n";
    std::cout << "=========================================\n";
    std::cout << "Port\tStatus\tReason\n";
    std::cout << "=========================================\n";

    for (const auto& res : results) {
        std::cout << res.port << "\t" 
                  << (res.is_open ? "OPEN" : "CLOSED") << "\t" 
                  << res.reason << "\n";
    }

    std::cout << "=========================================\n";
}

// 命令行参数解析
bool parseArgs(int argc, char* argv[], std::string& ip, int& start_port, int& end_port) {
    if (argc != 4) {
        std::cerr << "Usage: " << argv[0] << " <IP> <StartPort> <EndPort>\n";
        std::cerr << "Example: " << argv[0] << " 127.0.0.1 1 1000\n";
        return false;
    }

    ip = argv[1];
    start_port = atoi(argv[2]);
    end_port = atoi(argv[3]);

    if (start_port < 1 || start_port > 65535 || end_port < 1 || end_port > 65535 || start_port > end_port) {
        std::cerr << "Invalid port range (1-65535)\n";
        return false;
    }

    return true;
}

int main(int argc, char* argv[]) {
    try {
        std::string target_ip;
        int start_port, end_port;

        // 解析命令行参数
        if (!parseArgs(argc, argv, target_ip, start_port, end_port)) {
            return 1;
        }

        // 开始扫描(记录耗时,适配鸿蒙性能分析)
        auto start_time = std::chrono::high_resolution_clock::now();
        PortScanner& scanner = PortScanner::getInstance();
        std::vector<ScanResult> results = scanner.scan(target_ip, start_port, end_port, 1000);
        auto end_time = std::chrono::high_resolution_clock::now();

        // 打印结果
        printScanResults(target_ip, results);

        // 输出耗时
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
        std::cout << "Scan completed in " << duration.count() << " ms\n";

    } catch (const std::exception& e) {
        std::cerr << "Fatal error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

3.2 关键修复与适配点(鸿蒙PC特有)

1. 编译错误核心修复
  • 头文件补充:显式包含 <sys/time.h>,解决 struct timeval/fd_set 未定义问题;
  • POSIX宏位置:将 _POSIX_C_SOURCE=200809L 定义在所有头文件之前,确保宏生效;
  • 宏定义补充:新增 SO_SNDTIMEO 宏定义,避免套接字超时设置报错。
2. 网络编程适配
  • 非阻塞模式强制开启:通过 fcntl 设置套接字为非阻塞,适配鸿蒙内核的网络调度逻辑;
  • CPU核心数动态适配:根据鸿蒙PC的逻辑核心数调整线程数,避免线程过多导致调度压力;
  • 超时设置双保险:同时设置 SO_SNDTIMEO/SO_RCVTIMEO,解决鸿蒙PC单超时设置无效问题。
3. 稳定性优化
  • 资源严格释放:确保套接字文件描述符在所有分支中关闭,避免鸿蒙PC句柄泄漏;
  • 异常分支全覆盖:补充非阻塞模式设置失败的错误处理,提升程序鲁棒性;
  • 参数合法性校验:严格校验端口范围(1-65535),避免无效扫描。

四、编译:鸿蒙PC端Clang++编译命令

4.1 最终编译命令

# 修复后可直接编译的命令(无需额外调整)
clang++ -Wall -O2 -std=c++11 -o harmony_portscan so.cpp -lpthread -ldl -lm

说明:因代码中已提前定义 _POSIX_C_SOURCE=200809L,编译命令中可省略该宏,简化指令。

4.2 命令参数解析(鸿蒙PC重点)

参数 作用
clang++ C++编译器(鸿蒙PC推荐,替代g++,避免兼容性问题);
-std=c++11 C++11标准是std::thread/std::chrono的最低要求;
-Wall 开启所有警告,提前发现鸿蒙PC特有的语法/接口问题;
-O2 二级优化(平衡性能与编译速度,避免-O3的兼容性风险);
-lpthread 链接线程库(std::thread基于pthread实现,鸿蒙PC必须显式链接);
-ldl -lm 链接动态库和数学库(鸿蒙PC musl libc的基础依赖);

4.3 编译常见问题终极解决

报错信息 原因 解决方法
struct timeval 不完整类型 缺失<sys/time.h> 显式包含该头文件,且确保_POSIX_C_SOURCE宏在头文件前定义;
fd_set 未知类型名 缺失<sys/time.h> 同上;
undefined reference to 'std::thread::join()' 未链接pthread库 编译命令添加-lpthread
SO_SNDTIMEO 未定义 musl libc宏缺失 代码中手动定义#define SO_SNDTIMEO 0x1005
inet_pton 函数未定义 缺失<arpa/inet.h> 包含该头文件;

五、运行:鸿蒙PC端端口扫描工具

5.1 赋予执行权限

chmod +x harmony_portscan

5.2 运行示例(扫描本地端口)

./harmony_portscan 127.0.0.1 1 100

5.3 预期输出(鸿蒙PC)

HarmonyOS PC Port Scan Result =====
Target IP:       127.0.0.1
=========================================
Port    Status  Reason
=========================================
97      CLOSED  Connect failed: Connection refused
55      CLOSED  Connect failed: Connection refused
25      CLOSED  Connect failed: Connection refused
13      CLOSED  Connect failed: Connection refused
56      CLOSED  Connect failed: Connection refused
37      CLOSED  Connect failed: Connection refused
26      CLOSED  Connect failed: Connection refused
79      CLOSED  Connect failed: Connection refused
14      CLOSED  Connect failed: Connection refused
98      CLOSED  Connect failed: Connection refused
57      CLOSED  Connect failed: Connection refused
27      CLOSED  Connect failed: Connection refused
38      CLOSED  Connect failed: Connection refused
15      CLOSED  Connect failed: Connection refused
61      CLOSED  Connect failed: Connection refused
80      CLOSED  Connect failed: Connection refused
85      CLOSED  Connect failed: Connection refused
99      CLOSED  Connect failed: Connection refused
49      CLOSED  Connect failed: Connection refused
39      CLOSED  Connect failed: Connection refused
16      CLOSED  Connect failed: Connection refused
58      CLOSED  Connect failed: Connection refused
62      CLOSED  Connect failed: Connection refused
81      CLOSED  Connect failed: Connection refused
40      CLOSED  Connect failed: Connection refused
17      CLOSED  Connect failed: Connection refused
59      CLOSED  Connect failed: Connection refused
82      CLOSED  Connect failed: Connection refused
19      CLOSED  Connect failed: Connection refused
100     CLOSED  Connect failed: Connection refused
86      CLOSED  Connect failed: Connection refused
18      CLOSED  Connect failed: Connection refused
60      CLOSED  Connect failed: Connection refused
83      CLOSED  Connect failed: Connection refused
50      CLOSED  Connect failed: Connection refused
41      CLOSED  Connect failed: Connection refused
28      CLOSED  Connect failed: Connection refused
87      CLOSED  Connect failed: Connection refused
91      CLOSED  Connect failed: Connection refused
84      CLOSED  Connect failed: Connection refused
31      CLOSED  Connect failed: Connection refused
42      CLOSED  Connect failed: Connection refused
20      CLOSED  Connect failed: Connection refused
67      CLOSED  Connect failed: Connection refused
51      CLOSED  Connect failed: Connection refused
92      CLOSED  Connect failed: Connection refused
32      CLOSED  Connect failed: Connection refused
29      CLOSED  Connect failed: Connection refused
88      CLOSED  Connect failed: Connection refused
21      CLOSED  Connect failed: Connection refused
68      CLOSED  Connect failed: Connection refused
63      CLOSED  Connect failed: Connection refused
93      CLOSED  Connect failed: Connection refused
33      CLOSED  Connect failed: Connection refused
22      CLOSED  Connect failed: Connection refused
30      CLOSED  Connect failed: Connection refused
94      CLOSED  Connect failed: Connection refused
34      CLOSED  Connect failed: Connection refused
89      CLOSED  Connect failed: Connection refused
52      CLOSED  Connect failed: Connection refused
23      CLOSED  Connect failed: Connection refused
69      CLOSED  Connect failed: Connection refused
64      CLOSED  Connect failed: Connection refused
35      CLOSED  Connect failed: Connection refused
95      CLOSED  Connect failed: Connection refused
24      CLOSED  Connect failed: Connection refused
53      CLOSED  Connect failed: Connection refused
7       CLOSED  Connect failed: Connection refused
70      CLOSED  Connect failed: Connection refused
1       CLOSED  Connect failed: Connection refused
90      CLOSED  Connect failed: Connection refused
65      CLOSED  Connect failed: Connection refused
73      CLOSED  Connect failed: Connection refused
36      CLOSED  Connect failed: Connection refused
96      CLOSED  Connect failed: Connection refused
43      CLOSED  Connect failed: Connection refused
71      CLOSED  Connect failed: Connection refused
54      CLOSED  Connect failed: Connection refused
2       CLOSED  Connect failed: Connection refused
44      CLOSED  Connect failed: Connection refused
66      CLOSED  Connect failed: Connection refused
8       CLOSED  Connect failed: Connection refused
74      CLOSED  Connect failed: Connection refused
3       CLOSED  Connect failed: Connection refused
45      CLOSED  Connect failed: Connection refused
72      CLOSED  Connect failed: Connection refused
4       CLOSED  Connect failed: Connection refused
75      CLOSED  Connect failed: Connection refused
46      CLOSED  Connect failed: Connection refused
5       CLOSED  Connect failed: Connection refused
47      CLOSED  Connect failed: Connection refused
9       CLOSED  Connect failed: Connection refused
76      CLOSED  Connect failed: Connection refused
6       CLOSED  Connect failed: Connection refused
48      CLOSED  Connect failed: Connection refused
77      CLOSED  Connect failed: Connection refused
78      CLOSED  Connect failed: Connection refused
10      CLOSED  Connect failed: Connection refused
11      CLOSED  Connect failed: Connection refused
12      CLOSED  Connect failed: Connection refused
=========================================
Scan completed in 3 ms

5.4 鸿蒙PC网络权限说明

  1. 本地端口扫描无需特殊权限,普通用户即可执行;

  2. 扫描外部IP需确保鸿蒙PC的网络防火墙放行(配置路径:/etc/iptables/rules.v4);

  3. 扫描1-1024特权端口需加sudo

    sudo ./harmony_portscan 192.168.1.1 1 1024
    

六、进阶优化:鸿蒙PC网络程序调优

6.1 静态编译(分发无忧)

clang++ -Wall -O2 -std=c++11 -static -o harmony_portscan so.cpp -lpthread -ldl -lm

静态编译后的程序可直接在其他鸿蒙PC上运行,无需依赖系统库。

6.2 性能优化(鸿蒙内核适配)

  1. 线程优先级调整:提升扫描线程优先级,适配鸿蒙PC调度策略:

    // 在线程函数中添加
    struct sched_param param;
    param.sched_priority = 50; // 鸿蒙PC优先级范围1-99
    pthread_setschedparam(pthread_self(), SCHED_RR, &param);
    
  2. 批量端口扫描:使用poll替代select,适配鸿蒙PC大文件描述符场景;

  3. 缓存优化:调整套接字缓冲区大小,提升扫描效率:

    int buf_size = 8192;
    setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size));
    

6.3 调试网络问题(鸿蒙PC特有)

  1. 查看端口占用:netstat -tulpn

  2. 抓包分析扫描过程(需安装:ohpm install tcpdump):

    tcpdump -i any host 127.0.0.1 and port 22
    

七、案例总结

本案例通过TCP端口扫描工具,完整展示了鸿蒙PC端C++网络编程的核心要点:

  1. 编译错误修复:核心解决<sys/time.h>缺失导致的timeval/fd_set未定义问题,这是鸿蒙PC musl libc的典型坑点;
  2. 网络适配:鸿蒙PC完全兼容POSIX套接字接口,但需注意非阻塞IO、超时设置的内核行为差异;
  3. 性能优化:基于鸿蒙PC的CPU核心数动态调整线程数,避免资源浪费;
  4. 稳定性:严格的资源释放、异常处理,适配鸿蒙PC的句柄限制和错误码特性。

该案例可扩展为TCP客户端/服务端、UDP扫描、网络测速等工具,核心Socket适配逻辑完全复用,是鸿蒙PC端C++网络开发的标准参考模板。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐