在同一台机器上向多个客户端发送组播消息

10

我正在尝试编写一个服务器/服务,每秒钟在局域网上广播一条消息,有点像服务发现。

这条消息需要被多个客户端程序接收,可以在同一台机器上或不同的机器上。但是每台机器上可能会同时运行多个程序。

我使用Delphi7和Indy 9.0.18。

我遇到的问题是我应该使用UDP(TIdUDPClient / Server)还是IP Multicast(TIdIPMCastClient / Server),或者是否可能使用两者...

我已成功地使用IP Multicast与每台机器上的一个客户端,但即使尝试了不同的绑定...最大/最小端口等,我似乎也找不到解决方案。

5个回答

8
我认为您正在寻找SO_REUSEADDR套接字选项。在套接字上设置该选项允许多个套接字侦听同一端口。对于多播,Windows保证消息将传递到所有套接字(否则消息只会随机发送到一个套接字)。通常通过调用setsockopt来实现此操作,但我不是Delphi开发人员,所以不确定您的API是什么样子。这个问题似乎展示了一个人在Delphi中做类似事情的例子。

太好了,SO_REUSEADDR 这就是我需要的提示。 - Christopher Chase
1
在mac OSX上,我必须添加:sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True) - Shane Holloway

5
我从未做过这个,但似乎你需要使用“邮槽”。它会在本地网络上广播消息,并接收其他工作站的回复。这就是流行的“armadillo”许可证管理器的工作原理(用于确保注册密钥没有“超额订阅”)。 我的应用程序(ClipMate)使用Armadillo作为保护包装器(共享软件包装器)。当注册用户运行应用程序时,它会检查是否有其他机器在同一网络上使用相同的密钥。它基本上会说:“我正在使用许可证1234,你呢?”它等待回复(我在启动期间使用单独的线程执行此操作,因此不会阻塞我的启动)。如果其他工作站报告他们正在使用相同的密钥,我将根据许可证中包含的座位数检查计数。我不确定它在Windows7上是否如此强大...

1
一个简单的邮槽实现可以在http://www.funkypuppy.com/找到,它在Delphi 7中运行良好。 - skamradt
邮槽听起来很有前途。我尝试了funkypuppy的实现,但似乎不能让同一台电脑上有超过2个程序接收消息。也许我做错了? - Christopher Chase
尽管Windows邮槽被宣传为广播机制,但这指的是不同机器上的接收者。在单台机器上,只有一个进程可以拥有并读取特定名称的邮槽。 - Ian Goldby

2
“肯定是可能的。”
关于“UDP或多播”,你现在谈论的是两个不同的概念。多播是一个IP概念,因此你可以使用多播IP或广播IP来进行UDP通信。
如果你可以接受所有客户端都在本地链路上(路由器等通常不会转发广播数据包)这一限制,我建议你选择广播。TIdUdpBase.Broadcast将会是你的好帮手。
更新:使用多播或广播,你只能将一个套接字绑定到任何特定的IP/端口对。因此,如果你想让多个客户端都监听相同的广播/多播,我认为你需要一个额外的调度客户端。该调度客户端接收广播并通知机器上的每个客户端。
在每个客户端内部,你都有一个小型的注册过程,该过程说:“尝试绑定到发送广播的端口。如果可以,请在该端口上设置调度客户端。如果不能,请说明调度已经创建,并向该调度注册自己。”
该注册过程可以简单地绑定到本地主机IP上的任何可用端口,并告诉调度程序“请将广播发送到此IP/端口”。
更新:Christopher Chase 的想法是正确的。我刚刚完成了与他几乎完全相同的解决方案,只是我修补了 IdIPMCastClient,添加了一个属性 ReuseAddr: Boolean,并通过添加 TIdIPMCastClient.GetBinding 进行了更改。
if Self.ReuseAddr then begin
  SetReuseAddr := Id_SO_True;
  Bindings[i].SetSockOpt(Id_SOL_SOCKET, Id_SO_REUSEADDR, @SetReuseAddr, Sizeof(SetReuseAddr));
end;

在调用AllocateSocket和Bind之间(其中SetReuseAddr:Integer)。

谢谢您的答案。实际上需要使用UDP和多播的限制,即我不希望消息离开局域网等。但我似乎无法解决的问题是,在同一台计算机上有两个客户端正在侦听广播。 - Christopher Chase
我在Indy 10的TIdIPMCastClient中添加了一个新的ReuseSocket属性。 - Remy Lebeau

1

在shf301的提示下,这是我让代码正常工作的代码

我创建了一个新的TIdIPMCastClient

 TIdReUseIPMCastClient = class(TIdIPMCastClient)
  private
    procedure SetReUseAddr(InBinding: TIdSocketHandle; const Value: boolean);
  protected
    function GetBinding: TIdSocketHandle; override;
  public
  end;

添加了过程

procedure TIdReUseIPMCastClient.SetReUseAddr(InBinding: TIdSocketHandle; const Value: boolean);
var
  tempi: integer;
begin
  if Assigned(InBinding) and InBinding.HandleAllocated then
    begin
    tempi := iif(Value, 1, 0);
    InBinding.SetSockOpt(Id_SOL_SOCKET, Id_SO_REUSEADDR, PChar(@tempi), SizeOf(tempi));
    end;
end;

复制了 TIdIPMCastClient 中的 GetBinding 代码,并在绑定之前添加了 SetReUseAddr。

  Bindings[i].AllocateSocket(Id_SOCK_DGRAM);
  SetReUseAddr(Bindings[i], True);
  Bindings[i].Bind;

1
我在 Indy 10 中的 TIdIPMCastClient 添加了一个新的 ReuseSocket 属性。 - Remy Lebeau

1

RemObjects有一个很好的解决方案:ROZeroConf

在那之前,我用RemObjects SDK的TROBroadcastChannel(基于UDP和Indy)自己做了类似的东西。 在该组件内部,它调用TIdUDPBase.Broadcast来发送和TIdUDPClient.ReceiveBuffer来接收响应。

(顺便说一下,UDP广播只在同一网络/子网上起作用,ROZeroConf是更好的解决方案)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接