You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Enet的多人2D射击游戏互联网连接失败问题求助

Enet P2P混合架构跨局域网连接失败问题排查

我正在用Enet开发一款多人2D射击游戏,采用类似《我的世界》LAN世界的P2P与P2C混合架构(玩家可作为主机创建游戏)。尝试通过好友提供的IP连接其主机时,客户端始终无法成功连接,参考Assault Cube的网络实现也没找到解决办法,以下是我的连接实现代码:

bool connectToServer(ENetHost *&client, ENetPeer *&server, int32_t &cid, std::string ip, char *playerName)
{
    ENetAddress adress;
    ENetEvent event;

    if (ip.empty())
    {
        enet_address_set_host(&adress, "127.0.0.1");
    }
    else
    {
        enet_address_set_host(&adress, ip.c_str());
    }
    //enet_address_set_host(&adress, "95.76.249.14");
    //enet_address_set_host(&adress, "192.168.1.11");
    adress.port = 7778;

    //client, adress, channels, data to send rightAway
    server = enet_host_connect(client, &adress, SERVER_CHANNELS, 0);

    if (server == nullptr)
    {
        return false;
    }

    //see if we got events by server
    //client, event, ms to wait(0 means that we don't wait)
    if (enet_host_service(client, &event, 5000) > 0
        && event.type == ENET_EVENT_TYPE_CONNECT)
    {
        std::cout << "connected\n";
    }
    else
    {
        enet_peer_reset(server);
        return false;
    }


    if (enet_host_service(client, &event, 5000) > 0
        && event.type == ENET_EVENT_TYPE_RECEIVE)
    {
        Packet p = {};
        size_t size;
        auto data = parsePacket(event, p, size);
                // Handshake
        if (p.header != headerReceiveCIDAndData)
        {
            enet_peer_reset(server);
            return false;
        }

        cid = p.cid;

        glm::vec3 color = *(glm::vec3 *)data;
        auto e = phisics::Entity();
        e.pos = getSpawnPosition();
        e.lastPos = e.pos;
        e.color = color;
        memcpy(e.name, playerName, playerNameSize);
        players[cid] = e;

        sendPlayerData(e, true);

        std::cout << "received cid: " << cid << "\n";
        enet_packet_destroy(event.packet);
        return true;
    }
    else
    {
        enet_peer_reset(server);
        return 0;
    }

    return true;
}

排查与修复建议

1. 先确认网络连通性

  • 如果是外网连接:主机必须在路由器上做7778端口的端口转发,并且确保你使用的是主机的公网IP(不是192.168.x.x这类内网IP)。
  • 如果是局域网连接:确认双方在同一网段,关闭双方的防火墙/杀毒软件,或者手动把7778端口加入允许列表。
  • 用ping [目标IP]测试基础连通,再用telnet [目标IP] 7778测试端口是否开放——telnet连不上的话,问题出在网络层面,和代码无关。

2. 检查Enet初始化配置

  • 客户端的ENetHost初始化要符合客户端模式:enet_host_create(NULL, 1, SERVER_CHANNELS, 0, 0),最大连接数设为1即可。
  • 主机端的ENetHost必须绑定到ENET_HOST_ANY,不能只绑定127.0.0.1,否则只有本机能连接。

3. 修复连接逻辑的缺陷

你的代码分两次调用enet_host_service,很容易错过事件导致超时失败,应该用循环统一处理事件:

bool connectToServer(ENetHost *&client, ENetPeer *&server, int32_t &cid, std::string ip, char *playerName)
{
    ENetAddress adress;
    ENetEvent event;

    if (ip.empty())
    {
        enet_address_set_host(&adress, "127.0.0.1");
    }
    else
    {
        enet_address_set_host(&adress, ip.c_str());
    }
    adress.port = 7778;

    server = enet_host_connect(client, &adress, SERVER_CHANNELS, 0);
    if (server == nullptr) return false;

    bool connected = false;
    bool handshakeCompleted = false;
    uint32_t totalWaitMs = 0;
    const uint32_t maxWaitMs = 10000; // 总超时10秒

    while (totalWaitMs < maxWaitMs)
    {
        int eventResult = enet_host_service(client, &event, 100);
        if (eventResult <= 0)
        {
            totalWaitMs += 100;
            continue;
        }

        switch (event.type)
        {
            case ENET_EVENT_TYPE_CONNECT:
                std::cout << "已连接到主机\n";
                connected = true;
                break;
            case ENET_EVENT_TYPE_RECEIVE:
                if (!connected) break; // 未连接先收到包,直接重置
                Packet p = {};
                size_t size;
                auto data = parsePacket(event, p, size);
                if (p.header == headerReceiveCIDAndData)
                {
                    cid = p.cid;
                    glm::vec3 color = *(glm::vec3 *)data;
                    phisics::Entity e;
                    e.pos = getSpawnPosition();
                    e.lastPos = e.pos;
                    e.color = color;
                    memcpy(e.name, playerName, playerNameSize);
                    players[cid] = e;

                    sendPlayerData(e, true);
                    std::cout << "收到CID: " << cid << "\n";
                    enet_packet_destroy(event.packet);
                    handshakeCompleted = true;
                }
                else
                {
                    enet_peer_reset(server);
                    return false;
                }
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                enet_peer_reset(server);
                return false;
        }

        if (connected && handshakeCompleted) break;
    }

    if (!handshakeCompleted)
    {
        enet_peer_reset(server);
        return false;
    }

    return true;
}
  • 确保主机和客户端的SERVER_CHANNELS定义一致,通道数不匹配会直接导致连接失败。
  • 原代码末尾的return true;属于冗余分支,可以删除。

4. 增加调试日志

  • 主机端添加日志,确认是否收到客户端的连接请求——如果主机没收到,是网络问题;如果收到但没发握手包,是主机逻辑问题。
  • 客户端在调用enet_address_set_host后,打印解析后的IP,确认输入的IP没有解析错误。

内容的提问来源于stack exchange,提问作者Aspis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 21:44:50