【nmap源码分析】Target 类——目标主机信息管理的核心引擎

Nmap 源码深度解析:Target 类------目标主机信息管理的核心引擎

前言

在网络安全扫描领域,Nmap 无疑是最为强大和广泛使用的工具之一。作为一个功能丰富的网络扫描器,Nmap 能够执行端口扫描、服务识别、操作系统检测等多种任务。然而,要真正理解 Nmap 的工作原理,我们需要深入其源代码,探索其核心组件的设计与实现。

本文将深入剖析 Nmap 中的 Target 类------这个看似简单却至关重要的类,是 Nmap 管理目标主机信息的核心引擎。通过本文,你将了解到:

  • Target 类的设计理念和核心职责
  • Target 类的内部结构和数据组织方式
  • Target 类如何支撑 Nmap 的扫描流程
  • 实际开发中如何使用 Target 类
  • Target 类的设计模式和最佳实践

无论你是想深入理解 Nmap 内部机制的开发者,还是希望学习优秀 C++ 类设计的程序员,本文都将为你提供有价值的见解。


一、Target 类的核心定位与设计理念

1.1 为什么需要 Target 类?

想象一下,当 Nmap 扫描一台主机时,会产生多少信息?

  • 基础网络信息:IP 地址(IPv4/IPv6)、MAC 地址、主机名、网络接口信息
  • 扫描状态信息:主机是否在线、扫描开始/结束时间、是否超时、距离计算
  • 扫描结果信息:开放端口列表、服务指纹、操作系统指纹、Traceroute 路径、脚本扫描结果

这些信息如果散落在程序的各个角落,将会导致:

  • 数据管理混乱,难以维护
  • 模块间耦合度高,难以扩展
  • 代码重复,效率低下

Target 类正是为了解决这些问题而设计的。它就像一个"全能的主机信息档案夹",将所有与目标主机相关的信息集中管理,提供统一的访问接口。

1.2 Target 类的核心职责

Target 类承担着以下核心职责:

  1. 数据封装:将目标主机的所有相关信息封装在一个对象中
  2. 状态管理:跟踪主机的扫描状态(在线/离线、超时等)
  3. 结果存储:保存扫描产生的各种结果数据
  4. 接口提供:为其他模块提供标准化的数据访问接口
  5. 生命周期管理:管理主机信息的创建、更新和销毁

1.3 设计模式分析

Target 类体现了多个经典的设计模式:

  • 封装模式:将复杂的数据结构封装在类内部,对外提供简洁的接口
  • 单一职责原则:专注于目标主机信息的管理,不涉及扫描逻辑本身
  • 访问器模式:通过 getter/setter 方法控制对内部数据的访问

二、Target 类的文件结构

2.1 头文件:Target.h

Target.h 是 Target 类的接口声明文件,它定义了:

  • Target 类的完整结构(成员变量和成员函数)
  • 相关的辅助数据结构(如 TracerouteHophost_timeout_nfo 等)
  • 枚举类型和常量定义

头文件的作用类似于"接口说明书",让 Nmap 的其他模块知道如何使用 Target 类。

2.2 实现文件:Target.cc

Target.cc 包含了 Target 类的具体实现,包括:

  • 构造函数和析构函数的实现
  • 所有成员函数的具体逻辑
  • 内部辅助函数的实现
  • 内存管理和资源清理

三、Target 类的内部结构详解

3.1 公共成员变量:扫描结果的存储

Target 类的公共成员变量主要用于存储扫描结果,这些变量可以被 Nmap 的其他模块直接访问:

cpp 复制代码
struct seq_info seq;                // TCP 序列信息,用于 OS 检测
int distance;                       // 目标主机距离(跳数)
enum dist_calc_method distance_calculation_method; // 距离计算方法
FingerPrintResults *FPR;            // OS 扫描指纹结果指针
PortList ports;                     // 端口列表,存储所有端口扫描结果
std::vector<EarlySvcResponse *> earlySvcResponses; // 早期服务响应数据
int weird_responses;                // 来自其他地址的异常响应计数
int flags;                          // 主机状态标志(HOST_UNKNOWN/HOST_UP/HOST_DOWN)
struct timeout_info to;             // 超时信息配置
char *hostname;                     // 主机名(DNS 反向解析结果)
char *targetname;                   // 目标名称(用户在命令行输入的原始名称)
struct probespec traceroute_probespec; // Traceroute 使用的探测类型
std::list<TracerouteHop> traceroute_hops; // Traceroute 跳点列表
std::list<struct sockaddr_storage> unscanned_addrs; // 未扫描的地址列表
#ifndef NOLUA
ScriptResults scriptResults;        // Lua 脚本扫描结果
#endif
state_reason_t reason;              // 主机状态判定原因
probespec pingprobe;                // 已知能收到响应的 ping 探测类型
int pingprobe_state;                // pingprobe 响应时的端口状态

设计说明

  • 这些变量被设计为公共成员,主要是为了方便 Nmap 的其他模块直接访问扫描结果
  • 指针类型的变量(如 FPR)需要特别注意内存管理
  • hostnametargetname 的区别:前者是 DNS 解析结果,后者是用户输入

3.2 私有成员变量:内部状态的维护

私有成员变量用于维护 Target 类的内部状态,外部无法直接访问:

cpp 复制代码
struct sockaddr_storage targetsock, sourcesock, nexthopsock; // 目标、源、下一跳地址
size_t targetsocklen, sourcesocklen, nexthopsocklen;        // 地址长度
int directly_connected;                                     // 直接连接标志(-1=未设置,0=否,1=是)
char targetipstring[INET6_ADDRSTRLEN];                      // 目标 IP 字符串表示
char sourceipstring[INET6_ADDRSTRLEN];                      // 源 IP 字符串表示
mutable char *nameIPBuf;                                    // 用于 NameIP() 方法的缓冲区
u8 MACaddress[6], SrcMACaddress[6], NextHopMACaddress[6];   // MAC 地址
bool MACaddress_set, SrcMACaddress_set, NextHopMACaddress_set; // MAC 地址设置标志
struct host_timeout_nfo htn;                                // 主机超时信息
devtype interface_type;                                     // 网络接口类型
char devname[32];                                           // 网络设备名称
char devfullname[32];                                       // 完整设备名称
int mtu;                                                    // 最大传输单元(MTU)值
int osscan_flag;                                            // OS 扫描标志(0=未执行,1=已执行,2=已执行但不可靠)

设计说明

  • 使用 sockaddr_storage 结构体可以同时支持 IPv4 和 IPv6 地址
  • directly_connected 的三种状态:-1(未初始化)、0(非直接连接)、1(直接连接)
  • MAC 地址使用 6 字节数组存储,配合布尔标志表示是否已设置
  • nameIPBuf 使用 mutable 关键字,允许在 const 方法中修改

四、Target 类的核心方法详解

4.1 构造与析构:对象的生命周期

cpp 复制代码
Target();   // 构造函数:初始化所有成员变量
~Target();  // 析构函数:释放动态分配的内存

实现要点

  • 构造函数会将所有成员变量初始化为合理的默认值
  • 析构函数会调用 FreeInternal() 方法释放动态分配的内存
  • 需要特别注意指针类型成员变量的内存管理

4.2 地址管理方法:网络地址的获取与设置

获取目标地址信息
cpp 复制代码
int af() const;  // 返回地址族(AF_INET 或 AF_INET6)
int TargetSockAddr(struct sockaddr_storage *ss, size_t *ss_len) const; // 获取目标地址
const struct sockaddr_storage *TargetSockAddr() const;                 // 获取目标地址指针
struct in_addr v4host() const;                                        // 获取 IPv4 地址
const struct in_addr *v4hostip() const;                                // 获取 IPv4 地址指针
const struct in6_addr *v6hostip() const;                               // 获取 IPv6 地址指针
const char *targetipstr() const;                                       // 获取目标 IP 字符串

使用场景

  • af():判断目标使用的是 IPv4 还是 IPv6
  • TargetSockAddr():获取完整的地址结构,用于网络编程
  • v4hostip() / v6hostip():获取特定类型的 IP 地址指针
  • targetipstr():获取 IP 地址的字符串表示,用于日志输出
设置目标地址
cpp 复制代码
void setTargetSockAddr(const struct sockaddr_storage *ss, size_t ss_len);

实现细节

  • 会验证地址长度是否有效
  • 会自动释放之前设置的主机名和目标名称
  • 会调用 GenerateTargetIPString() 生成新的 IP 字符串表示
源地址管理
cpp 复制代码
int SourceSockAddr(struct sockaddr_storage *ss, size_t *ss_len) const;
const struct sockaddr_storage *SourceSockAddr() const;
void setSourceSockAddr(const struct sockaddr_storage *ss, size_t ss_len);
struct sockaddr_storage source() const;
const struct in_addr *v4sourceip() const;
const struct in6_addr *v6sourceip() const;
const char *sourceipstr() const;

设计说明

  • 源地址是指发送扫描包时使用的本地地址
  • 提供了与目标地址类似的方法集合
  • source() 方法返回地址结构的副本,而非指针

4.3 主机名管理:DNS 解析与用户输入

cpp 复制代码
const char *HostName() const;       // 获取反向解析的主机名
void setHostName(const char *name); // 设置反向解析的主机名
const char *TargetName() const;     // 获取命令行输入的目标名称
void setTargetName(const char *name); // 设置命令行输入的目标名称
const char *NameIP(char *buf, size_t buflen) const; // 生成 IP 和主机名的字符串表示
const char *NameIP() const;                          // 生成 IP 和主机名的字符串表示(使用静态缓冲区)

重要区别

  • HostName():DNS 反向解析得到的主机名(如 www.example.com
  • TargetName():用户在命令行输入的原始名称(可能是 IP、域名或主机名)
  • NameIP():组合生成格式化的字符串,如 "www.example.com (192.168.1.1)"

线程安全警告

  • NameIP() 方法使用静态缓冲区,不是线程安全的
  • 多线程环境中应使用 NameIP(char *buf, size_t buflen) 方法

4.4 网络连接信息:拓扑与接口管理

cpp 复制代码
void setDirectlyConnected(bool connected); // 设置是否直接连接
bool directlyConnected() const;           // 获取是否直接连接
int directlyConnectedOrUnset() const;     // 获取是否直接连接或未设置
void setNextHop(const struct sockaddr_storage *next_hop, size_t next_hop_len); // 设置下一跳地址
bool nextHop(struct sockaddr_storage *next_hop, size_t *next_hop_len) const; // 获取下一跳地址
void setMTU(int devmtu);                  // 设置 MTU 值
int MTU(void) const;                      // 获取 MTU 值
void setIfType(devtype iftype);           // 设置接口类型
devtype ifType() const;                   // 获取接口类型

概念解释

  • 直接连接:目标主机是否在本地网络中(无需经过路由器)
  • 下一跳:到达目标主机需要经过的第一个路由器地址
  • MTU:最大传输单元,影响数据包的分片策略
  • 接口类型:网络接口的类型(如以太网、WiFi 等)

使用注意事项

  • directlyConnected() 方法在标志未设置时会断言失败
  • 使用前应确保已调用 setDirectlyConnected() 方法

4.5 超时管理:扫描时间的精确控制

cpp 复制代码
void startTimeOutClock(const struct timeval *now); // 开始超时时钟
void stopTimeOutClock(const struct timeval *now);  // 停止超时时钟
bool timeOutClockRunning() const;                   // 检查超时时钟是否正在运行
bool timedOut(const struct timeval *now) const;     // 检查是否超时
time_t StartTime() const;                          // 获取主机扫描开始时间
time_t EndTime() const;                            // 获取主机扫描结束时间

实现机制

  • 使用 host_timeout_nfo 结构体存储超时相关信息
  • startTimeOutClock() 记录开始时间并启动时钟
  • stopTimeOutClock() 计算已用时间并停止时钟
  • timedOut() 比较当前时间与配置的超时时间

典型使用流程

cpp 复制代码
struct timeval now;
gettimeofday(&now, NULL);
target.startTimeOutClock(&now);

// 执行扫描操作...

gettimeofday(&now, NULL);
if (target.timedOut(&now)) {
    printf("Host scan timed out!\n");
} else {
    target.stopTimeOutClock(&now);
    printf("Scan completed in %lu ms\n", target.htn.msecs_used);
}

4.6 MAC 地址管理:二层网络信息

cpp 复制代码
int setMACAddress(const u8 *addy);          // 设置目标 MAC 地址
int setSrcMACAddress(const u8 *addy);       // 设置源 MAC 地址
int setNextHopMACAddress(const u8 *addy);   // 设置下一跳 MAC 地址
const u8 *MACAddress() const;               // 获取目标 MAC 地址
const u8 *SrcMACAddress() const;            // 获取源 MAC 地址
const u8 *NextHopMACAddress() const;        // 获取下一跳 MAC 地址

应用场景

  • MAC 地址主要用于本地网络扫描
  • 可以用于设备识别和厂商信息查询
  • 在 ARP 扫描和二层发现中非常重要

返回值说明

  • 返回 0 表示成功
  • 返回非零表示失败(如地址无效)

4.7 设备信息管理:网络接口配置

cpp 复制代码
void setDeviceNames(const char *name, const char *fullname); // 设置设备名称
const char *deviceName() const;                               // 获取设备名称
const char *deviceFullName() const;                           // 获取完整设备名称

概念说明

  • deviceName():简短的设备名称(如 "eth0")
  • deviceFullName():完整的设备名称(可能包含路径或其他信息)

4.8 OS 扫描状态管理

cpp 复制代码
int osscanPerformed(void) const;  // 检查是否已执行 OS 扫描
void osscanSetFlag(int flag);     // 设置 OS 扫描标志

标志值说明

  • OS_NOTPERF = 0:未执行 OS 扫描
  • OS_PERF = 1:已执行 OS 扫描
  • OS_PERF_UNREL = 2:已执行 OS 扫描但结果不可靠

五、重要辅助数据结构

5.1 host_timeout_nfo:超时信息管理

cpp 复制代码
struct host_timeout_nfo {
  unsigned long msecs_used;         // 已使用的毫秒数
  bool toclock_running;             // 时钟是否正在运行
  struct timeval toclock_start;     // 时钟开始时间
  time_t host_start, host_end;      // 主机扫描的开始和结束时间
};

使用场景

  • 跟踪单个主机的扫描时间
  • 实现超时控制机制
  • 生成扫描报告中的时间统计信息

5.2 TracerouteHop:路由跳点信息

cpp 复制代码
struct TracerouteHop {
  struct sockaddr_storage tag;      // 跳点标签地址
  bool timedout;                    // 是否超时
  std::string name;                 // 跳点名称(反向解析结果)
  struct sockaddr_storage addr;     // 跳点地址
  int ttl;                          // TTL 值
  float rtt;                        // 往返时间(毫秒)
  int display_name(char *buf, size_t len) const; // 生成跳点的显示名称
};

字段说明

  • tag:用于标识跳点的地址
  • timedout:该跳点是否响应超时
  • name:DNS 反向解析得到的主机名
  • addr:跳点的实际 IP 地址
  • ttl:Time To Live,表示跳数
  • rtt:Round Trip Time,往返时间

应用示例

复制代码
Hop 1: 192.168.1.1 (router.local) - 1.2 ms
Hop 2: 10.0.0.1 (gateway.isp.com) - 15.3 ms
Hop 3: * (timeout)
Hop 4: 203.0.113.1 (target.example.com) - 45.7 ms

5.3 EarlySvcResponse:早期服务响应

cpp 复制代码
struct EarlySvcResponse {
  probespec pspec;                  // 探测类型
  int len;                          // 响应长度
  u8 data[1];                       // 响应数据(可变长度)
};

设计说明

  • 使用柔性数组(flexible array member)实现可变长度数据
  • 存储在端口扫描早期收到的服务响应
  • 用于后续的服务识别和版本检测

5.4 osscan_flags:OS 扫描状态枚举

cpp 复制代码
enum osscan_flags {
  OS_NOTPERF = 0,      // 未执行 OS 扫描
  OS_PERF,            // 已执行 OS 扫描
  OS_PERF_UNREL       // 已执行 OS 扫描但结果不可靠
};

状态转换

复制代码
OS_NOTPERF → OS_PERF(成功执行)
OS_NOTPERF → OS_PERF_UNREL(执行但结果不可靠)

六、实际应用示例

6.1 创建和初始化 Target 对象

cpp 复制代码
// 创建 Target 对象
Target target;

// 设置目标地址(IPv4)
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(0);
sin.sin_addr.s_addr = inet_addr("192.168.1.1");

target.setTargetSockAddr((struct sockaddr_storage *)&sin, sizeof(sin));

// 设置目标名称(用户输入)
target.setTargetName("web-server.example.com");

6.2 获取和显示目标信息

cpp 复制代码
// 获取基本信息
printf("Target IP: %s\n", target.targetipstr());
printf("Address family: %s\n", target.af() == AF_INET ? "IPv4" : "IPv6");

// 获取 IPv4 地址
if (target.v4hostip()) {
    printf("IPv4 address: %s\n", inet_ntoa(*target.v4hostip()));
}

// 获取主机名
if (target.HostName()[0]) {
    printf("Hostname: %s\n", target.HostName());
}

// 生成格式化的名称和 IP
char nameip[128];
target.NameIP(nameip, sizeof(nameip));
printf("Target: %s\n", nameip);

6.3 管理扫描超时

cpp 复制代码
struct timeval now;

// 开始扫描计时
gettimeofday(&now, NULL);
target.startTimeOutClock(&now);

// 执行扫描操作...
// 这里可以调用各种扫描函数

// 检查是否超时
gettimeofday(&now, NULL);
if (target.timedOut(&now)) {
    printf("Host scan timed out!\n");
    // 处理超时情况
} else {
    // 停止计时
    target.stopTimeOutClock(&now);
    printf("Scan completed in %lu ms\n", target.htn.msecs_used);
    printf("Scan started at: %s", ctime(&target.StartTime()));
    printf("Scan ended at: %s", ctime(&target.EndTime()));
}

6.4 设置网络连接信息

cpp 复制代码
// 设置是否直接连接
target.setDirectlyConnected(true);

// 设置下一跳地址(如果需要)
struct sockaddr_in next_hop;
memset(&next_hop, 0, sizeof(next_hop));
next_hop.sin_family = AF_INET;
next_hop.sin_addr.s_addr = inet_addr("192.168.1.254");
target.setNextHop((struct sockaddr_storage *)&next_hop, sizeof(next_hop));

// 设置 MTU
target.setMTU(1500);

// 设置接口类型
target.setIfType(devt_ethernet);

6.5 管理 MAC 地址

cpp 复制代码
// 设置目标 MAC 地址
u8 mac_addr[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
if (target.setMACAddress(mac_addr) == 0) {
    printf("MAC address set successfully\n");
    
    // 获取并显示 MAC 地址
    const u8 *mac = target.MACAddress();
    printf("Target MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
           mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

6.6 管理 OS 扫描状态

cpp 复制代码
// 检查是否已执行 OS 扫描
if (target.osscanPerformed() == OS_NOTPERF) {
    printf("OS scan not performed yet\n");
    
    // 执行 OS 扫描...
    // ...
    
    // 设置 OS 扫描标志
    target.osscanSetFlag(OS_PERF);
} else if (target.osscanPerformed() == OS_PERF) {
    printf("OS scan performed successfully\n");
    
    // 访问 OS 指纹结果
    if (target.FPR) {
        printf("OS fingerprint available\n");
    }
}

七、最佳实践与注意事项

7.1 内存管理

原则

  • Target 类会自动管理内部分配的内存
  • 用户需要确保在使用完 Target 对象后正确删除它
  • 避免内存泄漏和悬空指针

注意事项

  • 当设置新的目标地址时,会自动释放之前设置的主机名和目标名称
  • 指针类型的成员变量(如 FPR)需要特别小心
  • 使用 FreeInternal() 方法可以手动释放内部内存

7.2 线程安全

重要警告

  • NameIP() 方法使用静态缓冲区,不是线程安全的
  • 多线程环境中应使用 NameIP(char *buf, size_t buflen) 方法

建议

  • 在多线程环境中,为每个线程提供独立的缓冲区
  • 避免在多线程中共享同一个 Target 对象的某些方法调用

7.3 地址验证

验证机制

  • Target 类会验证地址长度是否有效
  • 但不会验证地址的格式是否正确

建议

  • 在设置地址前,自行验证地址格式
  • 使用标准的网络编程函数进行地址转换和验证

7.4 直接连接标志

使用注意事项

  • directlyConnected() 方法在标志未设置时会断言失败
  • 使用前应确保已调用 setDirectlyConnected() 方法

推荐做法

cpp 复制代码
// 先设置标志
target.setDirectlyConnected(true);

// 然后才能安全调用
if (target.directlyConnected()) {
    printf("Target is directly connected\n");
}

7.5 IPv4/IPv6 兼容性

兼容性说明

  • Target 类同时支持 IPv4 和 IPv6 地址
  • 某些方法只适用于特定地址类型

方法分类

  • 通用方法:af(), TargetSockAddr(), targetipstr()
  • IPv4 专用:v4host(), v4hostip(), v4sourceip()
  • IPv6 专用:v6hostip(), v6sourceip()

使用建议

cpp 复制代码
// 先检查地址类型
if (target.af() == AF_INET) {
    // 使用 IPv4 专用方法
    const struct in_addr *ipv4 = target.v4hostip();
    // ...
} else if (target.af() == AF_INET6) {
    // 使用 IPv6 专用方法
    const struct in6_addr *ipv6 = target.v6hostip();
    // ...
}

八、Target 类在 Nmap 扫描流程中的作用

8.1 扫描初始化阶段

复制代码
1. 创建 Target 对象
   ↓
2. 设置目标地址和名称
   ↓
3. 配置扫描参数(ping 探测类型等)
   ↓
4. 初始化网络接口信息

8.2 扫描执行阶段

复制代码
1. 开始超时时钟
   ↓
2. 执行端口扫描
   ↓
3. 收集服务响应
   ↓
4. 执行 OS 检测
   ↓
5. 执行 Traceroute
   ↓
6. 运行脚本扫描
   ↓
7. 停止超时时钟

8.3 结果输出阶段

复制代码
1. 从 Target 对象读取所有信息
   ↓
2. 格式化扫描结果
   ↓
3. 生成报告(命令行、XML、grepable 等)
   ↓
4. 清理资源

8.4 数据流示意图

复制代码
用户输入 → Target 对象 → 扫描引擎 → Target 对象 → 结果输出
   ↓           ↓            ↓           ↓           ↓
  IP地址    存储目标信息   执行扫描    存储结果    生成报告

九、总结与展望

9.1 核心要点回顾

通过本文的深入分析,我们了解到:

  1. Target 类的核心价值

    • 集中管理目标主机的所有信息
    • 提供统一的访问接口
    • 支撑 Nmap 的整个扫描流程
  2. 设计理念

    • 封装复杂的数据结构
    • 遵循单一职责原则
    • 提供清晰的接口
  3. 实际应用

    • 简化了主机信息的操作
    • 提高了代码的可维护性
    • 降低了模块间的耦合度

9.2 学习价值

理解 Target 类对于以下方面都有重要意义:

  • Nmap 开发:理解 Nmap 的内部工作机制
  • C++ 设计:学习优秀的类设计模式
  • 网络编程:理解网络扫描的数据组织方式
  • 系统架构:学习如何设计可扩展的系统组件

9.3 扩展阅读

如果你想进一步深入了解 Nmap 的源代码,建议阅读:

9.4 实践建议

  1. 阅读源代码:结合本文的分析,阅读 Target.cc 的实现
  2. 调试实验:使用调试器跟踪 Target 对象的生命周期
  3. 扩展开发:尝试基于 Target 类开发 Nmap 扩展
  4. 性能优化:分析 Target 类的性能瓶颈,提出优化建议

结语

Target 类虽然只是 Nmap 源代码中的一个类,但它体现了优秀的软件设计原则和实践。通过深入理解 Target 类,我们不仅能够更好地使用 Nmap,还能学习到如何设计清晰、高效、可维护的 C++ 类。

希望本文能够帮助你深入理解 Nmap 的内部机制,并在你的学习和工作中提供有价值的参考。如果你有任何问题或建议,欢迎交流讨论。


相关文档

作者注:本文基于 Nmap 7.98 版本的源代码进行分析,不同版本可能存在细微差异。

相关推荐
人间打气筒(Ada)1 小时前
Linux学习~日志文件参考
linux·运维·服务器·学习·日志·log·问题修复
x***r1512 小时前
VMware17安装步骤详解(附虚拟机创建与常见问题解决)
windows
微八度3 小时前
.Net Web API应用部署成windows服务
windows·.net·web api·winddows服务
脆皮瞎4 小时前
openclaw本地部署(windows)
windows·ai·openclaw
筱白爱学习5 小时前
RestHighLevelClient详细使用手册
linux·服务器·php
石油人单挑所有5 小时前
ProtoBuf编写网络版本通讯录时遇到问题及解决方案
运维·服务器
Andy6 小时前
分流设备的测试报告
运维·服务器
枷锁—sha6 小时前
【CTFshow-pwn系列】03_栈溢出【pwn 045】详解:Ret2Libc 之 32位动态泄露(补充本地 Libc 手动加载指南)
服务器·网络·网络安全·系统安全
xdpcxq10297 小时前
EF Core实体追踪Entry中记录的数据
服务器·数据库·oracle