【发布时间】:2020-03-11 01:30:18
【问题描述】:
我已经搜索了 2 天,发现很多很多问题/答案似乎是同一个问题,虽然有一些差异,但似乎没有一个真正提供解决方案。
我正在实现一个库,用于在没有 OEM 控制器的情况下直接控制 DMX 系统(ColorKinetics 设备)。这涉及通过驱动照明设备的路由器与连接到我家 LAN 的支持以太网的电源 (PDS) 进行通信。 PDS 在特定端口 (6038) 上运行,并响应通过网络广播的格式正确的数据报。
我可以成功广播一个简单的 DMX 消息(标题 + DMX 数据),它被 PDS 拾取并应用于连接的照明设备,因此单向通信不是问题。
我的问题是我现在正在尝试实现设备发现功能来检测 LAN 上的 PDS 和连接的灯,并且我无法接收(绝对)从 PDS 发回的数据报.我可以成功传输指示设备回复的数据报,并且可以在 WireShark 中看到回复,但我的应用程序没有检测到回复。
我还尝试在另一台机器上运行一个简单的侦听器应用程序,它可以检测到初始广播,但也听不到返回的数据报,但是我认为这不起作用,因为返回的数据包是发给原始发送者 IP地址。
我最初尝试通过UdpClient 实现,然后通过Sockets 实现,无论我指定什么选项和参数,两者都会产生相同的结果。
这是我目前用于测试功能的非常简单的代码,目前使用Sockets。
byte[] datagram = new CkPacket_DiscoverPDSRequestHeader().ToPacket();
Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint ep = new IPEndPoint(IPAddress.Parse("192.168.1.149"), 6039);
public Start()
{
// Start listener
new Thread(() =>
{
Receive();
}).Start();
sender.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
sender.EnableBroadcast = true;
// Bind the sender to known local IP and port 6039
sender.Bind(ep);
}
public void Send()
{
// Broadcast the datagram to port 6038
sender.SendTo(datagram, new IPEndPoint(IPAddress.Broadcast, 6038));
}
public void Receive()
{
Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
receiver.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
receiver.EnableBroadcast = true;
// Bind the receiver to known local IP and port 6039 (same as sender)
IPEndPoint EndPt = new IPEndPoint(IPAddress.Parse("192.168.1.149"),6039);
receiver.Bind(EndPt);
// Listen
while (true)
{
byte[] receivedData = new byte[256];
// Get the data
int rec = receiver.Receive(receivedData);
// Write to console the number of bytes received
Console.WriteLine($"Received {rec} bytes");
}
}
发送者和接收者绑定到一个IPEndPoint,本地IP和端口为6039。我这样做是因为我可以看到每次我初始化一个新的UdpClient时,系统会动态分配一个传出端口,这PDS 会将数据发送回。这样做,我可以说侦听器肯定在侦听应该接收 PDS 响应 (6039) 的端口。我相信,由于我将选项 ReuseAddress 设置为 true,这应该不是问题(不会引发异常)。
Start() 创建一个新线程来包含侦听器,并初始化发送客户端上的选项。
Send()成功广播PDS在6038端口接收到的16字节数据报,并在6039端口产生回复(见WireShark)
Receive() 没有收到数据报。如果我将监听器绑定到 6038 端口,它将接收原始的 16 字节数据报广播。
这是 WireShark 的数据:
正如许多答案所建议的那样,我已经研究过使用像 SharpPCap 这样的库,但在最新版本中似乎存在一些我不够聪明无法规避的兼容性问题,这会阻止基本示例正常运行在我的系统上。似乎这种基本功能不需要那种类型的外部依赖。我还看到了许多其他类似问题的问题/答案,但是通过为Socket 或UdpClient 设置这个或那个参数来解决它,其中我尝试了所有组合都无济于事。
我还启用了通过windows防火墙的访问权限,允许端口使用,甚至完全禁用了防火墙,但没有成功。我不认为问题出在我的路由器上,因为消息正在发送到 Wireshark。
更新 1
根据建议,我相信我将侦听器 Socket 置于混杂模式如下:
Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
receiver.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
receiver.EnableBroadcast = true;
IPEndPoint EndPt = new IPEndPoint(IPAddress.Parse("192.168.1.149"), 0);
receiver.Bind(EndPt);
receiver.IOControl(IOControlCode.ReceiveAll, new byte[] { 1, 0, 0, 0 }, null);
这导致监听器接收到各种网络流量,包括出站请求,但仍然没有收到回复。
更新 2
正如 Viet 所建议的,请求数据报中存在某种寻址问题,其格式如下:
public class CkPacket_DiscoverPDSRequest : BytePacket
{
public uint magic = 0x0401dc4a;
public ushort version = 0x0100;
public ushort type = 0x0100;
public uint sequence = 0x00000000;
public uint command = 0xffffffff;
}
如果我将command 字段更改为我的广播地址192.168.1.149' or192.168.255.255`,我的监听器开始检测返回的数据包。我承认我不知道这个字段应该代表什么,我最初的猜测是只输入一个广播地址,因为数据报的目的是发现网络上的所有设备。显然不是这样,尽管我仍然不确定它的确切意义。
不管怎样,谢谢你的帮助,这就是进步。
【问题讨论】:
-
有几件事:首先,从不使用
ReuseAddress。如果您正在使用它,那么您做错了,特别是如果您是套接字编程的新手。其次,在您发布的代码中,两个套接字都绑定到端口 6039,但您发送到端口 6038。似乎仅此一项就可以解释您无法接收任何数据报。不幸的是,您的问题不够清楚,无法回答。它没有提供好的minimal reproducible example,并且不清楚您是需要简单演示程序的帮助(即minimal reproducible example)还是需要实际网络设备的帮助。后者不太可能导致答案。 -
感谢您的回复;你能澄清为什么
ReuseAddress不是一个好主意吗?在我所见过的类似程序的每个示例中都有。 -
至于绑定socket 6039,我想我可能还不清楚;我正在与之通信的 PDS 硬连线以在端口 6038 上进行通信,因此它仍在接收数据包,并且它回复用于发送的任何端口(在本例中为 6039)。将我这边的两个 Socket 绑定到 6039 可确保侦听器正在侦听预期响应的正确端口,因为否则该端口将动态分配给发送 Socket,我将不得不找出另一种方法来侦听该端口在运行时(至少是我的想法)。
-
我不确定我的示例中缺少什么,它本质上是我目前正在运行的确切程序,没有依赖项列表,它的行为与我描述的完全一样。它通过端口 6039 和目标端口 6038 发送数据报,PDS 接收它,发送一个目标为 6039 的回复(在 Wireshark 中查看),侦听器在端口 6039 上没有收到它。就像我说的,这是本来是一个非常简单的测试程序,可以帮助我弄清楚这种类型的编程需要如何工作,但实际计划的实现并不复杂。