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

线程中使用UDP Socket后自动关闭问题求助(Boost+游戏开发)

问题排查:UDP Socket接收数据后报错“Socket operation on non-socket”

问题描述

开发多人射击游戏项目时,使用Boost库线程通过UDP Socket接收服务器数据,首次接收服务器指令正常,但第二次接收后Socket自动关闭,报错“Socket operation on non-socket”。

核心问题分析

1. 悬空引用导致的未定义行为

  • 在rtype函数中,buf和sender_endpoint是局部变量,handleReceived中的lambda通过引用捕获了这两个变量,而线程被detach后,rtype函数执行完毕时局部变量会被销毁,线程后续继续引用这些已销毁的变量,会破坏内存结构,进而导致Socket内部状态异常。
  • 另外,handleReceived的buf参数是传值方式,lambda引用的是这个函数参数(同样是局部变量),函数返回后参数销毁,同样造成悬空引用。

2. 重复创建接收线程引发的资源竞争

如果主循环多次进入rtype函数,每次都会调用handleReceived创建新的分离线程,多个线程同时操作同一个Socket,会引发资源竞争,导致Socket操作出现错误。

3. 队列的线程安全问题

received_data_queue被接收线程和游戏线程同时读写,没有任何同步机制,会导致队列数据结构被破坏,间接引发程序异常。

解决方法

1. 修复悬空引用问题

将接收所需的缓冲区和端点移到线程内部创建,避免引用外部局部变量:

#include <mutex>
std::mutex queue_mutex;
std::queue<std::string> received_data_queue;

void handleReceived(boost::asio::ip::udp::socket &socket)
{
    std::thread ([&socket]() {
        boost::array<char, 30> buf; // 线程内部创建,生命周期与线程绑定
        boost::asio::ip::udp::endpoint sender_endpoint;
        while (true) {
            boost::system::error_code error;
            std::size_t bytes_transferred = socket.receive_from(boost::asio::buffer(buf), sender_endpoint, 0, error);
            if (!error) {
                std::string received_data(buf.begin(), buf.begin() + bytes_transferred);
                std::cout << "Data received from server: " << received_data << std::endl;
                // 加锁保护队列操作
                std::lock_guard<std::mutex> lock(queue_mutex);
                received_data_queue.push(received_data);
            } else {
                std::cout << "Error: " << error.message() << std::endl;
                break;
            }
        }
    }).detach();
}

调用时只需传入Socket,无需传递缓冲区和端点。

2. 避免重复创建线程

将接收线程的创建移到主函数中,确保只创建一次:

int main(int argc, char* args[])
{
    // ... 初始化SDL、窗口、Socket等代码 ...

    // 仅创建一次接收线程
    handleReceived(socket);

    while (gameRunning) // 主游戏循环
    {
        frameStart = SDL_GetTicks();
        game(window, server_endpoint, socket);
        frameTime = SDL_GetTicks() - frameStart;
        if (frameDelay > frameTime)
        {
            SDL_Delay(frameDelay - frameTime);
        }
    }

    // ... 清理资源代码 ...
    return 0;
}

3. 给队列添加线程安全保护

修改getReceivedData函数,使用互斥锁保护队列的读写操作:

std::string getReceivedData()
{
    std::lock_guard<std::mutex> lock(queue_mutex);
    if (!received_data_queue.empty()) {
        std::string received_data = received_data_queue.front();
        received_data_queue.pop();
        return received_data;
    } else {
        return "";
    }
}

4. 确保Socket生命周期与线程匹配

添加全局停止标志,让接收线程可以主动退出,避免Socket提前销毁:

#include <atomic>
std::atomic<bool> stop_receive(false);

void handleReceived(boost::asio::ip::udp::socket &socket)
{
    std::thread ([&socket]() {
        boost::array<char, 30> buf;
        boost::asio::ip::udp::endpoint sender_endpoint;
        while (!stop_receive.load()) {
            boost::system::error_code error;
            std::size_t bytes_transferred = socket.receive_from(boost::asio::buffer(buf), sender_endpoint, 0, error);
            if (!error) {
                std::string received_data(buf.begin(), buf.begin() + bytes_transferred);
                std::cout << "Data received from server: " << received_data << std::endl;
                std::lock_guard<std::mutex> lock(queue_mutex);
                received_data_queue.push(received_data);
            } else {
                std::cout << "Error: " << error.message() << std::endl;
                break;
            }
        }
        // 安全关闭Socket
        boost::system::error_code ec;
        socket.close(ec);
    }).detach();
}

在主函数退出前设置stop_receive = true;,确保线程有机会正常退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 18:01:20