0

I'm trying to make a simple multiplayer example in C++ & SFML (Graphics & Sockets). MY goal at the moment is to make a demo where you can connect to a server, when you do, you receive the position of all the other "players" on the server.

I want to try it with non-blocking UDP, as non-blocking UDP is probably what would be used in a "real" game.

So far I have no real-time synchronization and am just trying to send the state of all the "players" presently on the server to a new client (please excuse the state of the code, its in "tweaking"/"throw things at the wall to see what sticks" mode).

My "protocol" goes like this:

  • Client: RequestConnect
  • Server: Acknowlege
  • Upon receiving Acknowlege Client goes to blocking mode to get a reliable read this one time
  • Server temporarily goes to blocking mode to send the state of all the other clients (sends a count as an 8-bit integer, and floating point x and y components for positions)

My problem is between RequestConnect and Acknowlege, the server checks every frame for a connection from a UDP socket in non-blocking mode (note: it makes it a bit more complex, but I have left some comments in to show some things I have tried, and I may refer too them):


std::vector<Request<>> RecieveRequests(
        unsigned short int port//, 
        //const std::atomic<bool>& keepReading, 
        //const std::atomic<bool>& gameRunning
    )
{
    sf::UdpSocket listener;
    listener.setBlocking(false);
    listener.bind(port);
    std::vector<Request<>> queue;
    //while(keepReading.load() == true)
    for(size_t ii = 0; ii < 1000; ++ii)
    {
        sf::IpAddress client{};
        sf::Packet incomingPacket;
        Request<> request;
        sf::Socket::Status recieveStatus = listener.receive(incomingPacket, client, port);
        //count += 1;
        if(client == sf::IpAddress::None)
            return queue;
            //break;
        std::cout << "Recieved a request from " << client << "\n";
        incomingPacket >> request;

        if(recieveStatus == sf::Socket::Status::Done)
        {
            std::cout << "Reciving was successful processing\n";
            std::cout << "Got header: " << std::string_view{request.header} << "\n";
            queue.push_back(request);
        }
        else if(recieveStatus == sf::Socket::Status::Partial)
            std::cerr << "Recieved only PARTIAL packet\n";
        else {
            std::cerr << "ERROR: BAD CONNECTION\n";
            return queue;
            //break;
        }
    }
    //std::cout << "Listened for: " << count << " requestes\n";
    return queue;
}

A single message does not seem to get through to the server, so I repeat the message from the client:


template<size_t ColorCountParameterConstant>
std::optional<Game> Connect(
        sf::IpAddress server, 
        unsigned short port, 
        const std::array<sf::Color, ColorCountParameterConstant>& playerColorList, 
        float retryDelayInSeconds = .001f
    )
{
    sf::Clock clock;
    bool acknowleged = false;
    sf::UdpSocket handshakeSocket;
    sf::UdpSocket acknowlegeSocket;
    while(acknowleged == false)
    {
        sf::Time startTime = clock.getElapsedTime();
        sf::Packet connectionRequest;
        Request<> request;
        std::strcpy(request.header, Request<>::requestConnect);
        request.address = sf::IpAddress::getLocalAddress();
        connectionRequest << request;
        handshakeSocket.setBlocking(true);
        for(size_t ii = 0; ii < 1000; ++ii) {
            auto status = handshakeSocket.send(connectionRequest, server, port);// != sf::Socket::Done) {
            std::cout << status << "\n";
        }
        //if(status != sf::Socket::Done) {
        //  std::cerr << "ERROR: Failed to SEND connection request to server!\n";
        //  return std::nullopt;
        //}
        //std::cout << "Done sending request\n";
        handshakeSocket.setBlocking(false);
        sf::Packet acknowlegement;
        //while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds);
        //for(size_t ii = 0; ii < 1000; ++ii)
            //handshakeSocket.receive(acknowlegement, server, port);
        Request<> acknowlegementReply;
        acknowlegement >> acknowlegementReply;
        if(std::string_view{acknowlegementReply.header} == std::string_view{Request<>::acknowlege})
        {
            std::cout << "GOT AWKNOWLEGEMENT!!!\n";
            if(std::string_view{acknowlegementReply.header} == std::string_view{Request<>::acknowlege}) {
                acknowleged = true;
                std::cout << "AWKNOWLEGED WAITING FOR REPLY\n";
            }
        }
        //else
            //std::cout << "Did not recieve acknowlegement\n";
        //while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds);
    }
    sf::Packet initialData;
    if(handshakeSocket.receive(initialData, server, port) != sf::Socket::Done) {
        std::cerr << "ERROR: Never RECIEVE handshake from server!\n";
        return std::nullopt;
    }
    std::cout << "Recieved handshake\n";
    InitialHandshake initialHandShake;
    initialData >> initialHandShake;
    return GameFromHandshake(initialHandShake, port, playerColorList);
}

Basically here I fire (send) RequestConnect (either in blocking or non-blocking mode) then immediately try to read a Acknowlege, however I notice a few things:

  1. I get a status 0 (Done I think judging by SFML's doc/source, [I think it may be implementation defined in this case]), when I do not read after I send
  2. I get a (sending) status 4 (Disconnected I think) when I read after I send
  3. If I get status 0 (sending), I do receive a connection on the server side
  4. If I do not get status 0 (sending), I do not receive a connection on the server side
  5. Without repeating the loop every-frame for(size_t ii = 0; ii < 1000; ++ii) the server looks for a connection, connection takes several seconds (is this normal?) this is very similar to a blocking call, as I could use a deterministically timed selector
  6. I get status 1 for read (NotReady it looks like)

It follows that if I can only receive connections on the server when status is 0 and I can only get status 0 when I don't read immediately after sending a message too the server, and I need to do a non-blocking read (because if I don't, and the server did not get my RequestConnection I need to send it again), then I cant receive an acknowledge request and don't know when to wait for the server to send the data on the game state.

I have looked for the problem of using a non-blocking UDP socket to write, then read getting an error code, and I have not found anyone else with the same problem.

Now for this part of the program (sharing initial state) it may be fine to use blocking sockets to set up the game state and "hot join". However, it is not fine when trying to share player state, I need to send and receive player state and know all inputs of all players every frame, that requires sending and receiving. And it cant take a long time, (like it does without the for(size_t ii = 0; ii < 1000; ++ii) in RecieveRequests. I am unsure if this delay is a symptom of the of the problem or not, but I thought it might be for some reason.

My question is

  1. Why cant I read after write to a non-blocking udp socket?
  2. Am I doing something fundamentally wrong?

The source code for SFML's UDP sockets is relativity simple seems mostly to abstract over some platform differences.

NOTE: One bit of commented out code I wanted to point out was: while((clock.getElapsedTime() - startTime).asSeconds() < retryDelayInSeconds); I have two of these, I put these where I did because I thought that the socket had not finished writing when it was reading or vise-versa, or the resource was not released by the OS. All this does it make connecting to the server take longer when not receiving and does not solve the problem.

You may also notice const std::atomic<bool>& keepReading I had RecieveRequests running on a separate thread at one point, as it seems to work when in blocking mode, but unfortunately because the thread needs to join I cant modify the state of the game before a client connects, or after (unless I tell it too stop after a client connects) without some more heavy modifications to account for blocking sockets.

Thanks!

UPDATE:

I tried

for(
    sf::Socket::Status status; 
    (status = handshakeSocket.send(connectionRequest, server, port)) != sf::Socket::Done; 
    std::cout << status
);

and

for(
    sf::Socket::Status status; 
    (status = handshakeSocket.receive(acknowlegement, server, port)) != sf::Socket::Done; 
    std::cout << status
);

It seems to get past send, but then gets to receive and prints a bunch of 1's (which I think is NotReady), and error 4 (disconnected) if I try to make a second socket.

Aamir
  • 1,974
  • 1
  • 14
  • 18

1 Answers1

0

Okay I figured it out after perusing some other questions! I realized two things

  1. Use different ports (on different sides) for reading/writing
  2. Bind any socket you read from

I did not bind sockets I was receiving from before because the server socket would complain that it failed to bind.

To make keeping track of the sockets for easy, at least for testing, what I did was do any sends from the server with +1 to the port, and any binds for the client with a +1 for the port (so the server may send on 12345 and the client receive on 12345, the client send on 12344 and the server receive on 12344).

It probably seems pretty basic to anyone with experience with UDP, but to anyone else who is also learning I hope this is helpful too you! :)