线程中使用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
相关产品推荐
相关产品推荐

