You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用Indy触发Bonjour/mDNS/ZeroConf消息并发现指定mDNS设备

解决Indy组件触发特定mDNS设备查询的问题

看起来你已经能接收mDNS消息,但卡在了主动触发查询这一步——问题出在TIdDNSResolver的默认行为并不完全符合mDNS的规范要求。mDNS查询有特定的格式和传输规则,而TIdDNSResolver主要是为传统单播DNS设计的,即使你把Host指向多播地址,它的请求格式可能不被lwIP实现的mDNS设备识别。

下面是针对你的需求的分步解决方案,完全基于Indy组件,不需要依赖Bonjour等外部库:

核心问题分析

mDNS查询必须满足以下关键要求,而TIdDNSResolver默认没有做到:

  • 查询必须发送到多播地址224.0.0.251:5353(IPv4)
  • DNS消息的RD(递归请求)位必须设为0(mDNS不支持递归查询)
  • 查询名称必须是product._tcp.local.(注意末尾的点,这是mDNS规范要求的完全限定域名格式)
  • QClass字段需要设置0x8001(IN类 + mDNS查询标志位),这是设备识别查询的关键

解决方案:用TIdUDPClient构造并发送标准mDNS查询

我们直接用TIdUDPClient来构造符合mDNS规范的查询报文,这样能确保lwIP设备接收并响应。

步骤1:添加TIdUDPClient组件到表单

配置组件的关键属性:

object IdUDPClient1: TIdUDPClient
  Host = '224.0.0.251'
  Port = 5353
  Bindings = <
    item
      IP = '0.0.0.0'
      Port = 0  // 随机端口发送
    end>
  ReuseSocket = rsTrue
  MulticastTTL = 1  // 限制多播范围在本地网络
end

步骤2:构造mDNS查询报文

编写一个函数来生成符合规范的DNS查询字节流:

function BuildMDNSQuery(const QName: string): TIdBytes;
var
  QNameParts: TStringList;
  I: Integer;
  TransactionID: Word;
begin
  // 初始化空字节数组
  SetLength(Result, 0);

  // 1. 事务ID(随机生成,可固定但建议随机)
  TransactionID := Random($FFFF);
  AppendByte(Result, Byte(TransactionID shr 8));
  AppendByte(Result, Byte(TransactionID and $FF));

  // 2. 标志位:QR=0(查询), OPCODE=0(标准查询), AA=0, TC=0, RD=0, RA=0, Z=0, AD=0, CD=0
  AppendByte(Result, $00);  // 高8位:00000000
  AppendByte(Result, $00);  // 低8位:00000000

  // 3. 问题数:1
  AppendByte(Result, $00);
  AppendByte(Result, $01);

  // 4. 回答数、授权资源数、附加资源数都为0
  AppendByte(Result, $00);
  AppendByte(Result, $00);
  AppendByte(Result, $00);
  AppendByte(Result, $00);
  AppendByte(Result, $00);
  AppendByte(Result, $00);

  // 5. 问题部分:QName(格式为长度+字节,最后以0结尾)
  QNameParts := TStringList.Create;
  try
    QNameParts.Delimiter := '.';
    QNameParts.DelimitedText := QName;
    for I := 0 to QNameParts.Count - 1 do
    begin
      if QNameParts[I] <> '' then
      begin
        AppendByte(Result, Byte(QNameParts[I].Length));
        AppendBytes(Result, TEncoding.ASCII.GetBytes(QNameParts[I]));
      end;
    end;
    AppendByte(Result, $00);  // QName结束标记
  finally
    QNameParts.Free;
  end;

  // 6. QType:PTR记录(12)
  AppendByte(Result, $00);
  AppendByte(Result, $0C);

  // 7. QClass:IN(1)+ mDNS查询标志位(0x8000)= 0x8001
  AppendByte(Result, $80);
  AppendByte(Result, $01);
end;

步骤3:发送查询并处理响应

修改你的按钮点击事件,发送查询:

procedure TfrmETMain.btnmDNSSearchClick(Sender: TObject);
var
  QueryBytes: TIdBytes;
begin
  // 先确保多播客户端处于活跃状态
  if not IdIPMCastClient1.Active then
    IdIPMCastClient1.Active := True;

  // 构造查询:目标是product._tcp.local.
  QueryBytes := BuildMDNSQuery('product._tcp.local.');

  // 发送多播查询
  IdUDPClient1.Send(QueryBytes);

  ShowMessage('mDNS查询已发送,等待设备响应...');
end;

步骤4:解析接收到的响应

在你的IdIPMCastClient1IPMCastRead事件中,需要解析DNS响应报文,提取PTR记录对应的设备名称,之后还可以进一步查询SRV和A记录获取设备的IP和端口。这里提供一个简化的解析逻辑框架:

procedure TfrmETMain.IdIPMCastClient1IPMCastRead(AThread: TIdListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  // 可以使用Indy自带的DNS解析类来处理
  DNSHeader: TIdDNSHeader;
  DNSQuery: TIdDNSQuery;
  I: Integer;
begin
  DNSHeader := TIdDNSHeader.Create;
  DNSQuery := TIdDNSQuery.Create;
  try
    // 解析DNS头部
    DNSHeader.ReadBuffer(AData, 0);
    
    // 解析查询和响应记录
    for I := 0 to DNSHeader.QDCount - 1 do
    begin
      DNSQuery.ReadBuffer(AData, 12); // 跳过头部的12字节
      // 提取查询名称、类型等信息
      Memo1.Lines.Add('查询目标:' + DNSQuery.QName);
    end;

    // 进一步解析回答记录(PTR、SRV、A等)
    // 可参考Indy的TIdDNSResolver源码实现完整解析
  finally
    DNSQuery.Free;
    DNSHeader.Free;
  end;
end;

跨平台注意事项

  • Windows:确保防火墙允许UDP 5353端口的多播通信
  • macOS:需要授予应用网络权限,Indy的套接字设置在macOS下通常能自动适配,但要保证ReuseSocket属性设为rsTrue
  • lwIP设备适配:部分lwIP的mDNS实现对查询格式要求严格,务必保证QClass设置为0x8001,RD位为0

为什么TIdDNSResolver不行?

TIdDNSResolver的Resolve方法默认会设置RD(递归请求)位为1,而mDNS规范明确要求RD位为0;另外,它没有设置QClass的mDNS标志位(0x8000),导致lwIP设备无法识别这个查询请求。直接构造UDP报文能完全控制这些细节,确保符合mDNS规范。

内容的提问来源于stack exchange,提问作者msohn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:51:13