Developing a couple of screens with some networking code that can make them talk to each other sounds like a simple enough task on the surface, but when you set out to develop multiplayer menus that need to handle every kind of situation that can happen on a mobile device as well as be able to understand and respond to future protocol versions, it's a much more complicated job. I definitely underestimated this task, giving myself just two days to get it done. It ended up taking closer to six, and there are still a few problems that will need to be fixed before the final release. I believe that the design I used is sound and this code will hold up against the test of time.
Day 35 - What was done:
Added screens for multiplayer join and host
Added host spinner options
Started working on first-level netcode/service
Day 36 - What was done:
Created Service definitions in AIDL
Implemented basic Host and Join activities
Started defining control protocol
Implemented much of service
Day 37 - What was done:
Got broadcast working for hosted game
Got join-activity to see the broadcasts and maintain a list of hosts
Day 38 - What was done:
Made both hosting and joining solid with orientation changes, cancels and rebinds.
Added notification for hosting service.
Designed a simple control protocol.
Implements most of the client and host protocol, interactions and callbacks.
Day 39 - What was done:
Added multiplayer client screen - hooked it up with join screen
Got control protocol working
Ran into issue with UDP / firewall - unresolved, using hack for testing
Got preferences working with the host screen, live updates work over control stream
Day 40 - What was done:
Fixed most networking issues.
Got host-kick to work.
Got client disconnect to work.
The basic design approach I took was to have a MultiplayerService that would handle all network communication with itself, running on another device. This means that in a 3-way situation, there will be 3 devices, each with a MultiplayerService. One service will be running in a host mode and the other two will be running as clients. Since I put all of the heavy lifting network code into the service, the activities are very clean and it's easy to find and debug problems, because they are generally only in one spot.
Here's the AIDL for my service:
void registerCallback(IMultiplayerServiceCallback cb);
void unregisterCallback(IMultiplayerServiceCallback cb);
// host side
void updateGameConfiguration(in MultiplayerGameConfiguration mpGameConfig);
// client side
void joinGame(String url, String userName);
And for the callback AIDL:
Since my activities only have to deal with these methods, working with them was easy and straight forward. Getting the network code to work was much more difficult.
I ended up using UDP broadcasts to 255.255.255.255 that announce when a game is being hosted. The join activity, which lists games it sees, listens on 0.0.0.0 for the broadcasts. It runs a thread that prunes the list if it hasn't seen a broadcast in over 3 seconds. Since broadcasts are to be made every second, this works fairly well, although I may have to increase it to 5 to account for udp packet loss. To make that work, I had to use my actual phone as the broadcaster and the emulator as the client (listener). On the emulator, I had to telnet to the local emulator port and run redir add udp:
The client knows what server it is by looking at the address on the DatagramPacket, but that presents a problem with the emulator. Since the emulator is using a bridge for networking, it is natting everything and the source host gets rewritten to be the nat source, which is 10.0.2.2. I don't think this will be a problem in the real world but for now I have hard coded the source address to make things work. I'm sure I'll find another solution for this later.
The host is broadcasting its hosted game but also listening for client control connections on a TCP port. The client connects to the TCP port at the address it saw the UDP packet coming from and the protocol begins. I used Object streams (serialization) for the control protocol because speed and efficiency do not matter, which is why it gets its own port. The data for the game will run on another port because it needs to be a smaller, faster protocol.
The host has the ability to change any parameter of the game. Clients can join or quit. The host can kick a client. When the client joins, it is taken from the join activity and brought to the client activity, which displays live information about the configuration of the game. It is here that the game itself will launch, clients will connect to the host's data port and the fun begins.
That's where I start coding tomorrow.
Sorry, no screenshots or videos at this time. There will be more as the game gets closer to completion. I have no specific date at this point.