我正在用C ++编写一个简单的测试游戏服务器。当前,我有一个绑定套接字在侦听任何人,并且当某种数据包类型到达时,我想将sockaddr结构保存到一个对象中,该对象将传递给一个单独的线程。该单独的线程将遍历所有套接字并尝试向它们写入数据。
出于测试目的,我只想执行以下操作:
当前,这是配置全局侦听器的方式:
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);
}
}
}
处理联接时,我要将收件人保存到其他收件人/用户/连接的内部队列中。为了进行测试,我使用了这种join方法,该方法将简单地创建新套接字,将其绑定并发送一些数据,以作为概念证明。
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
填充的struct sockaddr_in时,由于某些原因,尽管通过另一个端口和另一个套接字,我却无法再发送消息了,为什么?奇怪的是,我发现当客户端和服务器必须通过本地路由器时,这是行不通的,但是如果客户端和服务器不必通过本地路由器,则实际上是这样!可能是我需要收集其他数据包/报头信息吗?还是路由器正在做(或缺少)会限制消息的事情?可能是某些奇怪的端口转发问题,而不是在这里影响软件问题吗?
请务必注意,目前lobbyactions_thread()
方法中的所有内容均正常工作,即接收,处理和发送。就是问题所在的join()
方法。
更新的代码,部分来自自己的发现和用户@selbie
答案 0 :(得分:1)
您的bind
呼叫失败有两个原因。一种,您尝试将多个套接字绑定到同一端口。如果第一个绑定成功在端口8810上进行,则在同一端口上的第二个套接字绑定尝试将失败。此外,您尝试绑定到一个远程地址,而不是本地地址,这将始终失败。
我认为您可能会混淆bind
函数的作用。对于UDP套接字,bind调用指定在哪个端口(和哪个本地IP地址)上发送和接收数据报消息。标准行为是绑定到IP的INADDR_ANY(所有具有IP地址的适配器)。就像您有大厅代码一样。
当播放器通过UDP消息加入时,您不想绑定到新套接字的远程地址,因为这确实会失败。而是要绑定到INADDR_ANY作为IP和端口0(以获取自动分配的可用端口)。
所以改变这个:
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遍历可能会更好,并且您不必担心端口用完。如果您要构建客户端服务器游戏与点对点游戏,差异可能会很大。