C++:将 UDP 袜子转移到新位置

C++: Transferring UDP sockaddr to new location

本文关键字:新位置 位置 转移 UDP C++      更新时间:2023-10-16

概述

我正在用C++编写一个简单的测试游戏服务器。目前,我有一个绑定套接字侦听任何人,当某种数据包类型到达时,我想将 sockaddr 结构保存到一个对象中,该对象将被传递给一个单独的线程。该单独的线程将遍历所有套接字并尝试向它们写入数据。

出于测试目的,我只想执行以下操作:

  1. 侦听数据包并确定其类型(完成(
  2. 将源地址信息传递给单独的函数(完成(
  3. 创建一个新套接字,并将其绑定到新端口上(已完成,但这里可能有问题?
  4. 使用之前传入的sockaddr_in通过新绑定/创建的套接字发送数据(完成?

设置

接收

目前,全局侦听器的配置方式如下:

void lobbyactions_thread() {
struct sockaddr_in any;
memset(&any, 0, sizeof(any));
any.sin_family = AF_INET;
any.sin_port = htons(LOBBYACTIONS_PORT);
any.sin_addr.s_addr = htonl(INADDR_ANY);
unsigned int any_sz = sizeof(any);
packets::LOBBYACTION_PACKET jpacket;
int socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
bind(socketfd, (struct sockaddr*) &any, sizeof(any))
while (server_manager::STAY_ALIVE) {
struct sockaddr_in* new_con = new struct sockaddr_in;
memset(new_con, 0, sizeof(*new_con));
unsigned int new_con_size = sizeof(*new_con);
recvfrom(socketfd, &jpacket, packets::LOBBYACTION_PACKET_SIZE, 0, (sockaddr*)new_con, &new_con_size);
bool save = false;
if (jpacket.type == JOIN) { save = true; server_manager::join(jpacket.id, new_con); }
// ...
jpacket.okay = true;
if (!save) { 
sendto(socketfd, &jpacket, packets::LOBBYACTION_PACKET_SIZE, 0, (sockaddr*)&any, any_sz);
free(any); 
}
}
}

保存和临时发送

处理加入时,我想将收件人保存到其他收件人/用户/连接的内部队列中。出于测试目的,我有这个连接方法,它将简单地创建新套接字,绑定它,并发送一些数据作为概念证明。

struct Slot {
int playerSocket;
int playerAssignedPort;
struct sockaddr_in* sourceAddress;
unsigned int sourceAddressSize;
};
void join(int id, struct sockaddr_in* raddr) {
Slot* ps = new Slot();
ps->playerAssignedPort = 8810 // temporary
ps->playerSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(ps->playerAssignedPort);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
ps->sourceAddressSize = sizeof(addr);
bind(ps->playerSocket, (struct sockaddr*)&addr, ps->sourceAddressSize);
ps->sourceAddress = raddr;
ps->sourceAddress->sin_port = htons(ps->playerAssignedPort);
// ... 
sendto(ps->playerSocket, &updatePacket, packets::GAMESTATE_PACKET_SIZE, 0, (sockaddr*)(ps->sourceAddress), ps->sourceAddressSize);
// ...
}

问题

为什么我不能绑定到这个套接字?我是否经过了错误的any部分?还是这真的是不可能的?任何帮助或指示(可能是可怕的双关语(都会很棒。

为什么当我保存由recvfrom填充的结构sockaddr_in时,由于某种原因我可以不再发送消息,尽管是通过另一个端口和另一个套接字?奇怪的是,我发现当客户端和服务器必须通过本地路由器时,这是行不通的,但如果客户端和服务器不必通过本地路由器,这实际上有效!可能是我需要收集其他数据包/标头信息吗?还是路由器正在做一些(或缺乏(会限制消息的事情?可能是有一些奇怪的端口转发问题,而不是这里实际上的软件问题吗?

编辑

需要注意的是,目前在lobbyactions_thread()方法中,即接收、处理和发送,一切都在工作。问题在于join()方法。

更新的代码,部分来自自己的发现和用户@selbie

您的bind调用失败有两个原因。 首先,您正在尝试将多个套接字绑定到同一端口。如果端口 8810 上的第一个绑定成功,则同一端口上的第二个套接字绑定尝试将失败。 此外,您正在尝试绑定到远程地址,而不是本地地址 - 这将始终失败。

我认为您可能会混淆bind函数的意图。 对于 UDP 套接字,绑定调用指定在哪个端口(以及哪个本地 IP 地址(上发送和接收数据报消息。标准行为是绑定到 IP 的INADDR_ANY(所有具有 IP 地址的适配器(。 就像您拥有大厅代码一样。

当玩家通过 UDP 消息加入时,您不希望绑定到新套接字的远程地址,因为这确实会失败。 相反,您希望以 IP 和端口 0 的形式绑定到INADDR_ANY(以获得自动分配的可用端口(。

所以改变这个:

bind(ps->playerSocket, (struct sockaddr*)&addr, ps->sourceAddressSize); // <- Wont 

捆!

对此:

sockaddr_in addrTemp = {}; // zero-init the local bind address (INADDR_ANY and port 0)
addrTemp.sin_family = AF_INET;
int result = bind(ps->playerSocket, (sockaddr*)&addrTemp, sizeof(addrTemp));

您可以稍后调用getsockname以获取由于在绑定调用中使用端口 0 而分配给套接字的端口号。

此外,请考虑一种设计,即在专用端口号上为所有播放器使用单个插槽。这可能更适合 NAT 遍历,您不必担心端口用完。 如果您正在构建客户端-服务器游戏与点对点游戏,则可能会有很大差异。