UDP 广播正在发送,但未收到
我正在为游戏开发一个非常简单的大厅系统。每个客户端定期通过 UDP 广播两个数据包,以初步发现其他客户端并传输用户信息、准备情况等。该游戏正在为 Windows 和 Linux(32 和 64 位)开发。
在 Windows 端,我已经让大厅系统完美运行。当我在一台 Windows 计算机上进入大厅时,该人会在其他计算机上弹出。同样,立即检测到就绪检查和断开连接。换句话说,它有效。
现在的问题是:Linux。网络代码实际上是相同的,只有一些必要的特定于平台的更改。我首先尝试了 Windows<->Linux。使用Wireshark,我发现Linux端确实在广播数据包并从Windows盒子接收数据包,但游戏从未捕获到数据包。我在 select 语句中发现了一个错误(套接字而不是套接字 + 1),但修复它并没有帮助。 Windows 机器正在广播数据包,但它根本没有从 Linux 机器接收数据包!
然后我尝试了 Linux<->Linux,但发现即使两台机器都在广播和接收(再次通过 Wireshark 确认),两台机器上的游戏都无法“看到”数据包。
我很确定这不是防火墙问题(在任一平台上都关闭了所有内容,进行了测试,然后重新打开所有内容,没有任何更改)并且网络连接似乎没问题(能够手动 ping 每个主机)。我还检查以确保端口确实可用(它们确实可用)。
下面是广播数据包的代码:
void NetworkLinux::BroadcastMessage(const std::string &msg,
const char prefix)
{
string data(prefix + msg);
if (sendto(linuxSocket, data.c_str(), static_cast<int>(data.length()), 0,
reinterpret_cast<sockaddr*>(&broadcastAddr), sizeof(broadcastAddr)) == -1)
{
Display_PError("sendto");
}
}
以及接收数据包的代码:
const Message NetworkLinux::ReceiveMessage()
{
char buffer[recvBufferLength];
fill(buffer, buffer + recvBufferLength, 0);
sockaddr_in sender;
int senderLen = sizeof(sender);
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(linuxSocket, &read_fds);
timeval time;
time.tv_sec = 0;
time.tv_usec = 16667; // microseconds, so this is ~1/60 sec
int selectResult = select(linuxSocket + 1, &read_fds,
nullptr, nullptr, &time);
if (selectResult == -1)
{
Display_PError("select");
}
else if (selectResult > 0) // 0 means it timed-out
{
int receivedBytes = recvfrom(linuxSocket, buffer,
recvBufferLength, 0, reinterpret_cast<sockaddr*>(&sender),
reinterpret_cast<socklen_t*>(&senderLen));
if (receivedBytes == -1)
{
Display_PError("recvfrom");
}
else if (receivedBytes > 0)
{
Message msg;
msg.prefix = buffer[0];
msg.msg = string(buffer + 1, buffer + receivedBytes);
msg.address = sender.sin_addr;
return msg;
}
}
Message m;
m.prefix = 'N';
return m;
}
为什么当我看到数据包到达时,select()
总是返回 0?而且,为什么在 Windows<->Windows 场景下可以工作,而在 Linux<->Linux 或 Linux<->Windows 场景下就不行呢?
编辑:这是根据要求的套接字创建/设置代码。计算出的示例 IP/广播地址为:192.168.1.3/192.168.1.255、192.168.1.5/192.168.1.255,与 Windows 端生成和使用的内容相匹配。
bool NetworkLinux::StartUp()
{
// zero addr structures
memset(&machineAddr, 0, sizeof machineAddr);
memset(&broadcastAddr, 0, sizeof broadcastAddr);
// get this machine's IP and store it
machineAddr.sin_family = AF_INET;
machineAddr.sin_port = htons(portNumber);
inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr));
// get the netmask and calculate/store the correct broadcast address
broadcastAddr.sin_family = AF_INET;
broadcastAddr.sin_port = htons(portNumber);
GetNetMask();
broadcastAddr.sin_addr.s_addr = machineAddr.sin_addr.s_addr | ~netmask;
char bufIP[INET_ADDRSTRLEN], bufBroadcast[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &machineAddr.sin_addr, bufIP, INET_ADDRSTRLEN);
inet_ntop(AF_INET, &broadcastAddr.sin_addr, bufBroadcast,
INET_ADDRSTRLEN);
Log("IP is: " + string(bufIP) + "\nBroadcast address is: "
+ string(bufBroadcast));
// create socket
linuxSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (linuxSocket == -1)
{
Display_PError("socket");
return false;
}
Log("Socket created.");
// switch to broadcast mode
int broadcast = 1;
if (setsockopt(linuxSocket, SOL_SOCKET, SO_BROADCAST, &broadcast,
sizeof broadcast) == -1)
{
Display_PError("setsockopt");
close(linuxSocket);
return false;
}
Log("Socket switched to broadcast mode.");
// bind it (this simplifies things by making sure everyone is using the same port)
if (bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr),
sizeof(machineAddr)) == -1)
{
Display_PError("bind");
close(linuxSocket);
return false;
}
Log("Socket bound.");
return true;
}
I'm working on a very simple lobby system for a game. Each client broadcasts two packets via UDP at regular intervals to initially discover other clients and transmit user info, readiness, etc. The game is being developed for both Windows and Linux (32 & 64 bit).
On the Windows side, I've gotten the lobby system working flawlessly. When I enter the lobby on one Windows machine, the person pops up in the other machines. Similarly, ready checks and disconnects are detected right away. In other words, it works.
Now the problem: Linux. The network code is virtually identical, with only a few necessary platform-specific changes. I first tried Windows<->Linux. Using Wireshark, I found that the Linux side was indeed broadcasting packets and receiving them from the Windows box, but the game never caught the packets. I found a bug in my select statement (socket instead of socket + 1), but fixing it didn't help. The Windows box was broadcasting packets, but it wasn't receiving packets from the Linux box at all!
I then tried Linux<->Linux, but found that even though both machines were broadcasting and receiving (again, confirmed via Wireshark), the games on both machines couldn't "see" the packets.
I'm pretty sure it's not a firewall issue (turned everything off, tested, turned everything back on, no change, on either platform) and network connectivity seems ok (was able to ping each host manually). I also checked to make sure the ports were indeed available (they were).
Below is the code for broadcasting packets:
void NetworkLinux::BroadcastMessage(const std::string &msg,
const char prefix)
{
string data(prefix + msg);
if (sendto(linuxSocket, data.c_str(), static_cast<int>(data.length()), 0,
reinterpret_cast<sockaddr*>(&broadcastAddr), sizeof(broadcastAddr)) == -1)
{
Display_PError("sendto");
}
}
And the code for receiving packets:
const Message NetworkLinux::ReceiveMessage()
{
char buffer[recvBufferLength];
fill(buffer, buffer + recvBufferLength, 0);
sockaddr_in sender;
int senderLen = sizeof(sender);
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(linuxSocket, &read_fds);
timeval time;
time.tv_sec = 0;
time.tv_usec = 16667; // microseconds, so this is ~1/60 sec
int selectResult = select(linuxSocket + 1, &read_fds,
nullptr, nullptr, &time);
if (selectResult == -1)
{
Display_PError("select");
}
else if (selectResult > 0) // 0 means it timed-out
{
int receivedBytes = recvfrom(linuxSocket, buffer,
recvBufferLength, 0, reinterpret_cast<sockaddr*>(&sender),
reinterpret_cast<socklen_t*>(&senderLen));
if (receivedBytes == -1)
{
Display_PError("recvfrom");
}
else if (receivedBytes > 0)
{
Message msg;
msg.prefix = buffer[0];
msg.msg = string(buffer + 1, buffer + receivedBytes);
msg.address = sender.sin_addr;
return msg;
}
}
Message m;
m.prefix = 'N';
return m;
}
Why does select()
keep coming back with 0 when I can see packets arriving? Moreover, why does it work in the Windows<->Windows scenario, but not Linux<->Linux or Linux<->Windows?
Edit: Here is the socket creation/setup code, as requested. Sample IPs/broadcast addresses calculated are: 192.168.1.3/192.168.1.255, 192.168.1.5/192.168.1.255, which match what the Windows side generated and used.
bool NetworkLinux::StartUp()
{
// zero addr structures
memset(&machineAddr, 0, sizeof machineAddr);
memset(&broadcastAddr, 0, sizeof broadcastAddr);
// get this machine's IP and store it
machineAddr.sin_family = AF_INET;
machineAddr.sin_port = htons(portNumber);
inet_pton(AF_INET, GetIP().c_str(), &(machineAddr.sin_addr));
// get the netmask and calculate/store the correct broadcast address
broadcastAddr.sin_family = AF_INET;
broadcastAddr.sin_port = htons(portNumber);
GetNetMask();
broadcastAddr.sin_addr.s_addr = machineAddr.sin_addr.s_addr | ~netmask;
char bufIP[INET_ADDRSTRLEN], bufBroadcast[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &machineAddr.sin_addr, bufIP, INET_ADDRSTRLEN);
inet_ntop(AF_INET, &broadcastAddr.sin_addr, bufBroadcast,
INET_ADDRSTRLEN);
Log("IP is: " + string(bufIP) + "\nBroadcast address is: "
+ string(bufBroadcast));
// create socket
linuxSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (linuxSocket == -1)
{
Display_PError("socket");
return false;
}
Log("Socket created.");
// switch to broadcast mode
int broadcast = 1;
if (setsockopt(linuxSocket, SOL_SOCKET, SO_BROADCAST, &broadcast,
sizeof broadcast) == -1)
{
Display_PError("setsockopt");
close(linuxSocket);
return false;
}
Log("Socket switched to broadcast mode.");
// bind it (this simplifies things by making sure everyone is using the same port)
if (bind(linuxSocket, reinterpret_cast<sockaddr*>(&machineAddr),
sizeof(machineAddr)) == -1)
{
Display_PError("bind");
close(linuxSocket);
return false;
}
Log("Socket bound.");
return true;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
:
这会将套接字绑定为仅接受发送到由
GetIP
返回的机器地址处的portNumber
的数据包,这可能不是您想要的,因为您还希望接收发送的数据包到广播地址的端口。您可能希望将sin_addr
设置为INADDR_ANY
(通配符地址),这将允许套接字接收发送到以某种方式到达机器的任何地址的端口的数据包。:
This binds the socket to only accept packets sent to
portNumber
at the machine address returned byGetIP
, which is probably not what you want, as you also want to receive packets sent to the port at the broadcast address. You probably want to setsin_addr
to beINADDR_ANY
, the wildcard address, which will allow the socket to receive packets sent to the port at any address that gets to the machine somehow.猜测您忘记设置 SO_BROADCAST 套接字选项并且广播数据包被过滤掉。
Guess you forgot to set SO_BROADCAST socket option and broadcast packets are filtered out.