The source code of all examples is included in the TcpJLib distribution.
Example 4: Synchronizing node events In a multi-user game application it may be important that at any time all nodes reflects the same state as close as possible. This is a major challenge due to the delays when transmitting data over the internet. What you need is a clock in every node that runs synchronized as precise as possible with all other node clocks in absolute time (with an arbitrary zero). TcpJLib provides such a synchronization facility that is accurate to about 50 ms in most circumstances. This is achieved as follows: The relay server runs a clock with microsecond precision and a node synchronizes its clock by calling a synchronization request. A separate TCP/IP socket is opened (on port 80) and the relay reports its clock time as a high-priority HTTP reply. The local clock is then set to the reported time corrected by half the measured time interval between sending the request and getting the reply. This assumes that the data transfer times from the node to the relay and back are equal and that the time to handle the request by the relay may be neglected. Our tests show synchronization errors in the order of 10 - 20 ms with nodes that are at different sites using different internet connections. (We used telephone links between nodes to establish an "absolute" timing. Better would be a RF link or a synchronization using GPS clocks.) In the following example we want to compare the reaction time of different players. A game server "drops" apples that must be hit by the players to be removed. The first user that hits the apple gets the score and the apple disappears. To make the game fair, it is important that an apple is presented to every player at the same time as accurate as possible. The game server sends the DROP command consecutively to all the connected clients (via the relay). Without synchronization, the delay of the arrival at the clients nodes depends on the relay load, the number of participants and the internet transmission speed. It may be as large as some 100 ms, too much for a fair game. To circumvent this problem, the game server sends the DROP command together with a time stamp that informs the clients to delay the dropping action until the specified absolute time is reached. The client reports the hit to the game server that sends a command to all players to remove the apple and to inform who got the hit. Only the first hit report is accepted. This may be incorrect because of different transmission times from the client to the server, but the error is in the order of some tens of milliseconds. As you see in the following code, managing the connections and disconnections by the game server is very simple due to the notification callback notifyAgentConnection(). We use an ArrayList players of the nickname of all connected players that is used to send the data by sendCommand(). Data from agents is received by the callback pipeRequest(). The protocol is defined in the interface Command using integers: interface Command When a player hits the apple, the HIT command is sent to the server that just registers the name of the client and disables further treatment of HIT commands by setting isRequestEnabled to false. To measure the reaction time, the client could determine it and transfer it together with the HIT command to the server. Look at the dropToAll() method to see how the synchronization of dropping all apples together is performed: We request the current clock time and add a delay that is big enough to make sure that the DROP command arrived at all clients. The clients delay dropping the apple until the drop time sent by the server is reached. The DROP commands are generated by the server application thread. After a DROP command is sent, the thread is paused using Monitor.putSleep(). When a hit is reported by the pipeRequest() callback, the thread is requested to run and send the next DROP command by calling Monitor.wakeUp(). For debugging and demonstration purposes a server log is displayed in a Console window (from the package ch.aplu.util).
When you connect to the relay, it replies by giving you a "connection list" that contains all agents already connected. The first entry is the personal name attributed to your node. The personal name is your requested nickname eventually appended by a (n) trailer, if the nickname is already in use. Because all agents use the same nickname "HitMePlayer", this happens when more than one player connects. To make the game more individually, you may ask for a user name at the program start.
Execute the game server program first locally using WebStart. (These applications are slightly replenished with more complete user score information and the measurement of the reaction time. See the source code in the TcpJLib distribution.)
Typical application based on the technique explained above: AppleShooter Wilhelm Tell is a folk hero of Switzerland. His legend is recorded in a late 15th century. On November 1307, Tell split an apple on his son's head with a bolt from his crossbow. This is a training application if you want to follow in Tell's footsteps. Execute the game server program first using WebStart. Enter a session ID that is unique for your game. Execute the player program for two players using WebStart. Use the same session ID as the server. The two players and the server may be located anywhere on the world.
| ||||