2

I'm porting a game to use Google Play Game Services with multiplayer support. I'm using RealTimeSocket instead of realtime message because the game already has socket support.

To get the socket I call GamesClient.getRealTimeSocketForParticipant, and then I could get input and output streams as use it as a usual socket.

My problem is that if a device receives data before the call to getRealTimeSocketForParticipant, I will not be able to read this data. For instance:

Device A calls getRealTimeSocketForParticipant.
Device A sends "Hello".
Device B calls getRealTimeSocketForParticipant.
Device B receives nothing.
Device A sends "World".
Device B receives "World".

I have modified one of the example projects (ButtonClicker) and replicated the problem here. I have modified the code to use realtime socket, and modified the startGame method to this:

String mReceivedData = "";
byte mNextByteToSend = 0;

void startGame(boolean multiplayer)
{
    mMultiplayer = multiplayer;
    updateScoreDisplay();
    switchToScreen(R.id.screen_game);

    findViewById(R.id.button_click_me).setVisibility(View.VISIBLE);

    GamesClient client = getGamesClient();

    String myid = mActiveRoom.getParticipantId(client.getCurrentPlayerId());
    ArrayList<String> ids = mActiveRoom.getParticipantIds();
    String remoteId = null;
    for(int i=0; i<ids.size(); i++)
    {
        String test = ids.get(i);
        if( !test.equals(myid) )
        {
            remoteId = test;
            break;
        }
    }

    //One of devices should sleep in 5 seconds before start
    if( myid.compareTo(remoteId) > 0 )
    {
        try
        {
            //The device that sleeps will loose the first bytes.
            Log.d(TAG, "Sleeping in 5 seconds...");
            Thread.sleep(5*1000);
        }
        catch(Exception e)
        {

        }
    }
    else
    {
        Log.d(TAG, "No sleep, getting socket now.");
    }

    try
    {
        final RealTimeSocket rts = client.getRealTimeSocketForParticipant(mRoomId, remoteId);
        final InputStream inStream = rts.getInputStream();
        final OutputStream outStream = rts.getOutputStream();

        final TextView textView =((TextView) findViewById(R.id.score0));
        //Thread.sleep(5*1000); Having a sleep here instead minimizes the risk to get the problem.

        final Handler h = new Handler();
        h.postDelayed(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    int byteToRead = inStream.available();

                    for(int i=0; i<byteToRead; i++)
                    {
                        mReceivedData += " " + inStream.read(); 
                    }

                    if( byteToRead > 0 )
                    {
                        Log.d(TAG, "Received data: " + mReceivedData);
                        textView.setText(mReceivedData);
                    }

                    Log.d(TAG, "Sending: " + mNextByteToSend);
                    outStream.write(mNextByteToSend);
                    mNextByteToSend++;

                    h.postDelayed(this, 1000);
                }
                catch(Exception e)
                {

                }
            }
        }, 1000);
    }
    catch(Exception e)
    {
        Log.e(TAG, "Some error: " + e.getMessage(), e);
    }
}

The code ensures that one of the two devices sleeps 5 seconds before the call to getRealTimeSocketForParticipant. For the device that doesn't sleep the output will be something like:

No sleep, getting socket now.
Sending: 0
Sending: 1
Sending: 2
Sending: 3
Sending: 4
Received data:  0
Sending: 5
Received data:  0 1
Sending: 6
Received data:  0 1 2

That's expected, no data lost. But for the other device I get this:

Sleeping in 5 seconds...
Received data:  4
Sending: 0
Received data:  4 5
Sending: 1
Received data:  4 5 6
Sending: 2
Received data:  4 5 6 7
Sending: 3

The first bytes are lost. Is there anyway to avoid this?

PEK
  • 3,688
  • 2
  • 31
  • 49

1 Answers1

2

If i'm understanding the API correctly, the messages exchanged through a real time socket are unrealiable, so you can't always have assurance that all players received all messages you sent. I couldn't find info about the network protocol used by RealTimeSocket, but I suspect it's UDP.

If that's really the case, I'm afraid there's little you can do short of implementing some sort of handshake yourself. Choose one device (ex.: the one with the lowest ID) to be the "synchronizer", and have it create a set with every other device. Send a message ("SYN") such as "where are you? x y z" (not literally, of course) every second, until the others respond "I'm here! (y)" ("ACK"). Remove from the set the devices that sent a response, until the set is empty. At this point, send everyone a "game's starting!" and go on.

Note that any of these messages can be lost: if the "ACK" is lost, next time the "SYN" is sent the device should answer again. If the "game's starting" message is lost, tough luck, the device will keep waiting until it receives a different message, at such point it should consider itself free to start (though delayed).

One last note: even if the underlying protocol is TCP, it's still not 100% reliable, no protocol is. See this question for more info, if you don't know this fact already.

Community
  • 1
  • 1
mgibsonbr
  • 21,755
  • 7
  • 70
  • 112
  • I have the same expression. The [documentation](https://developers.google.com/games/services/android/multiplayer#implementing_socket-based_messaging) however doesn’t mention anything specific about this case, so I was hoping I missed something :-). My original implementation is built on TCP-sockets so it assumes that data is never lost or received in the wrong order. It however seems to work good enough with RealTimeSockets except for the handshaking, which is an annoying but solvable problem. – PEK Jun 17 '13 at 07:00
  • @user1883479 That's right, you can't have the same assumptions in this case. Quoting your linked docs: "Sockets are an unreliable message transport, so packets may or may not arrive at the destination. However, integrity is guaranteed. That is, individual packets may be lost, but a packet will not arrive corrupted, or truncated. Packets will rarely arrive out of order." – mgibsonbr Jun 17 '13 at 13:20