如何在SFML中通过UDP Socket读取最新数据?
SFML UDP Socket 读取最新数据解决方案
问题背景
客户端通过SFML UDP Socket接收数据时,总是读取到旧数据。尝试用循环读取清空缓冲区的方式(while (socket.receive(_,_,_) != sf::socket::status::error))会导致客户端无限挂起,因为服务器发送数据的速度远快于客户端处理速度。即使客户端卡顿,后续也只会继续读取缓存中的旧数据,无法直接获取最新的数据包。此外sf::UdpSocket没有原生的缓冲区清空接口。
曾考虑使用ACK应答机制,让服务器等待客户端确认后再发送新数据,但这种方案会导致服务器依赖客户端状态,无客户端连接时服务器无法独立运行。同一机器测试多客户端的端口冲突问题为次要问题。
原代码
客户端代码
#include <SFML/Graphics.hpp> #include <SFML/Network.hpp> #include <SFML/Network/Packet.hpp> #include <SFML/Network/UdpSocket.hpp> #include <cstdint> #include <iostream> class myData { public: float x; float y; }; sf::Packet& operator <<(sf::Packet& packet, const myData& data) { return packet << data.x << data.y; } sf::Packet& operator >> (sf::Packet& packet, myData& data) { return packet >> data.x >> data.y; } int main() { sf::RenderWindow window(sf::VideoMode({ 1000,1000 }), "SFML works!"); sf::RectangleShape rectangle({ 50.f, 50.f }); sf::UdpSocket socket; myData data{}; std::optional<sf::IpAddress> server; unsigned short portNo = 54280; unsigned short receiverPort = 54000; if (sf::Socket::Status::Done != socket.bind(portNo)) { return EXIT_FAILURE; } while (window.isOpen()) { while (const std::optional event = window.pollEvent()) { if (event->is<sf::Event::Closed>()) window.close(); } //Read from socket. sf::Packet packet; if (sf::Socket::Status::Done == socket.receive(packet, server, receiverPort)) { packet >> data; } window.clear(); sf::Vector2f position(data.x, data.y); rectangle.setPosition(position); window.draw(rectangle); window.display(); } }
服务器代码
#include <SFML/Network.hpp> #include <SFML/Network/Packet.hpp> #include <SFML/Network/UdpSocket.hpp> #include <cstdint> #include <iostream> class myData { public: float x; float y; }; sf::Packet& operator <<(sf::Packet& packet, const myData& data) { return packet << data.x << data.y; } sf::Packet& operator >> (sf::Packet& packet, myData& data) { return packet >> data.x >> data.y; } int main() { sf::UdpSocket socket; if (socket.bind(54000) != sf::Socket::Status::Done) { exit(EXIT_FAILURE); } myData myData{}; myData.x = 0; myData.y = 0; sf::IpAddress client(127, 0, 0, 1); while (true) { myData.x += 0.005; myData.y += 0.005; sf::Packet pack; pack << myData; std::cout << "Data.x" << myData.x << ", data.y" << myData.y << std::endl; socket.send(pack, client,54280); } }
解决方案
核心思路是将UDP Socket设置为非阻塞模式,然后循环读取所有缓存中的数据包,只保留最后一次读取到的数据,这样就能确保客户端每次渲染时使用的是最新的服务器数据。
修改后的客户端代码
#include <SFML/Graphics.hpp> #include <SFML/Network.hpp> #include <SFML/Network/Packet.hpp> #include <SFML/Network/UdpSocket.hpp> #include <cstdint> #include <iostream> class myData { public: float x; float y; }; sf::Packet& operator <<(sf::Packet& packet, const myData& data) { return packet << data.x << data.y; } sf::Packet& operator >> (sf::Packet& packet, myData& data) { return packet >> data.x >> data.y; } int main() { sf::RenderWindow window(sf::VideoMode({ 1000,1000 }), "SFML works!"); sf::RectangleShape rectangle({ 50.f, 50.f }); sf::UdpSocket socket; myData data{}; std::optional<sf::IpAddress> server; unsigned short portNo = 54280; unsigned short receiverPort = 54000; if (sf::Socket::Status::Done != socket.bind(portNo)) { return EXIT_FAILURE; } // 设置Socket为非阻塞模式,避免读取时挂起 socket.setBlocking(false); while (window.isOpen()) { while (const std::optional event = window.pollEvent()) { if (event->is<sf::Event::Closed>()) window.close(); } // 循环读取所有可用数据包,只保留最后一个 sf::Packet packet; sf::Socket::Status status; while ((status = socket.receive(packet, server, receiverPort)) == sf::Socket::Status::Done) { packet >> data; // 每次读取成功后清空packet,准备接收下一个 packet.clear(); } // 忽略NotReady(无数据)和其他非致命错误 window.clear(); sf::Vector2f position(data.x, data.y); rectangle.setPosition(position); window.draw(rectangle); window.display(); } }
修改说明
- 非阻塞模式设置:调用
socket.setBlocking(false),这样receive方法在没有可用数据时会立即返回sf::Socket::Status::NotReady,不会阻塞客户端线程。 - 循环读取所有缓存:通过
while循环读取所有当前可用的UDP数据包,每次读取成功就更新data变量,最后一次更新的data就是服务器发送的最新数据。 - 不影响渲染流程:读取逻辑完全融入客户端主循环,不会因为网络操作阻塞窗口渲染,保证帧率稳定。
服务器代码无需修改,依然可以独立运行,无需依赖客户端状态。
内容的提问来源于stack exchange,提问作者sea
相关产品推荐
相关产品推荐

