C ++:将UDP sockaddr传输到新位置

时间:2018-09-16 02:00:47

标签: c++ sockets

概述

我正在用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); 
        }
    }
}

保存并临时发送

处理联接时,我要将收件人保存到其他收件人/用户/连接的内部队列中。为了进行测试,我使用了这种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

1 个答案:

答案 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遍历可能会更好,并且您不必担心端口用完。如果您要构建客户端服务器游戏与点对点游戏,差异可能会很大。