局域网环境下工控设备如何快速设置IP(C++)

背景

  • 不同子网 下,设备与主机通信会受阻。
  • 相机本身 不支持 DHCP/Auto IP,因此必须手动指定相机 IP。
  • 为保证通信,需将 相机 IP 与网卡 IP 放在同一子网

需求

  • 自动为相机分配一个 与网卡同一子网 的 IP。
  • 避免与现有设备 IP 冲突。

方案演进

  1. 直接网卡 IP + 1

  • 最简单实现:取 adapterIp + 1 作为候选地址。
  • 在典型场景(单网卡对应单相机)下,简单、快速、无感知
  • 缺点:如果 +1 的地址刚好被占用,会产生冲突风险。
  1. 线性探测(已弃用,太复杂,用不太到)

  • 遍历整个子网(netAddr+1 ~ bcast-1),逐个检测可用性。
  • 优点:理论上能找到真正未被占用的地址。
  • 缺点:大子网下效率极低(如 /16 子网需要探测 65k 地址)。
  1. 最终采用方案:直接 IP + 1 + 有限重试

  • 在“直接 +1”基础上,增加 有限次重试(+2、+3 …)。
  • 可在少量尝试内找到可用 IP,避免线性探测的低效。
  • 适合当前“单网卡对应单设备”的应用环境

具体方案

1. 获取信息

  • 读取 网卡 IP 和子网掩码 (adapterIp, adapterMask)。
  • 读取 相机 IP 和子网掩码 (cameraIp, cameraMask)。

2. 判断是否同网段

bool isSameSubnet = ((adapterIp & adapterMask) == (cameraIp & adapterMask));

if (isSameSubnet) {
    return Success; // 已在同网段,无需修改
}

3. 候选 IP 生成逻辑

1. IP+1 + 有限重试

static unsigned long pickCameraIpNextToAdapter(unsigned long adapterIp, unsigned long adapterMask)
{
    //在做线性探测时,需要注意网络序和主机序的转换
    const quint32 hostMask = ~adapterMask;
    const quint32 netPart = adapterIp & adapterMask;
    const quint32 hostPart = adapterIp & hostMask;
    const quint32 bcastHost = hostMask;         // 广播主机位(全1)
    const quint32 netHost = 0;                // 网络地址主机位(全0)

    // 默认尝试:主机位 +1
    quint32 candHost = (hostPart + 1) & hostMask;

    // 规避保留与冲突:0(网络)、全1(广播)、以及与适配器自身相同
    auto isBad = [&](quint32 h) {
        if (h == netHost)   return true;
        if (h == bcastHost) return true;
        if (h == hostPart)  return true;
        return false;
        };

    if (isBad(candHost)) {
        // 常见直连场景,给 host=2(例如 x.x.x.2)
        candHost = 2;
        if (isBad(candHost)) {
            // 再兜底给 10(更靠前一些,减少撞边界概率)
            candHost = 10;
            if (isBad(candHost)) {
                // 最后线性探测一个可用主机位(理论上单设备不会走到这)
                for (quint32 h = 1; h < bcastHost; ++h) {
                    if (!isBad(h)) { candHost = h; break; }
                }
            }
        }
    }

    return (netPart | candHost);
}

2. 线性探测逻辑(早期方案)

static bool isOccupiedByARP(unsigned long targetIpNetOrder, unsigned long srcIpNetOrder) {
    ULONG mac[2] = { 0 };
    ULONG macLen = 6;
    return SendARP(targetIpNetOrder, srcIpNetOrder, mac, &macLen) == NO_ERROR;
}

static unsigned long findFreeIpForAdapter(unsigned long adapterIp, unsigned long adapterMask,unsigned long cameraIp, unsigned long cameraGateway)
{
    //在做线性探测时,需要注意网络序和主机序的转换
    uint32_t ip_h = ntohl(adapterIp);
    uint32_t mask_h = ntohl(adapterMask);
    uint32_t net_h = ip_h & mask_h;
    uint32_t bcast_h = net_h | (~mask_h);

    // 从 adapterIp+1 开始扫
    for (uint32_t h = ip_h + 1; h < bcast_h; ++h) {
        unsigned long cand = htonl(h);
        if (cand == adapterIp || cand == cameraIp || cand == cameraGateway)
            continue;
        if (!isOccupiedByARP(cand, cam.adapterIp)) {
            return cand; // 找到可用 IP
        }
    }
    return 0; // 没找到
}
  • 优点:保证分配到一个真实空闲的地址。
  • 缺点:遍历整个子网极耗时,不适合大规模/交互场景。

4. 关键实现要点

  1. 字节序htonl/ntohl 必须统一,避免打印/显示异常 IP。

  2. 候选 IP 检查:需跳过网络地址和广播地址。

  3. 有限重试:保证简单快速的同时,兼顾一定容错性。

  4. 线程与刷新:写入前暂停自动刷新,写入后延迟恢复。

阅读剩余
THE END