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

如何在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();
    }
}

修改说明

  1. 非阻塞模式设置:调用socket.setBlocking(false),这样receive方法在没有可用数据时会立即返回sf::Socket::Status::NotReady,不会阻塞客户端线程。
  2. 循环读取所有缓存:通过while循环读取所有当前可用的UDP数据包,每次读取成功就更新data变量,最后一次更新的data就是服务器发送的最新数据。
  3. 不影响渲染流程:读取逻辑完全融入客户端主循环,不会因为网络操作阻塞窗口渲染,保证帧率稳定。

服务器代码无需修改,依然可以独立运行,无需依赖客户端状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:34:51