Java IoT Authors: Elizabeth White, Liz McMillan, Zakia Bouachraoui, Yeshim Deniz, Pat Romanski

Related Topics: Java IoT

Java IoT: Article

Java and The MahJong Server

Java and The MahJong Server

I had conceived of an Internet server for the Chinese game of MahJong a long time ago, but had never gotten very far trying to implement it in C and Xlib. In January 1996, I learned of Java, and decided to start anew. Java's unique capabilities: multithreading, portability, and AWT just to name a few, proved to be extremely useful for such a project. In three weeks, I got the alpha version running, and soon I had dozens of people playing on my site.

Just like okbridge and the chess servers, my application provides a virtual casino for MahJong fans on the Internet. It actually consists of two parts: a server and a client. The server is a Java application that keeps running twenty-four hours a day. The client is a Java applet that runs under a user's webbrowser. A web page on the server's host machine gives access to the client applet and provides documentation and other information to the user. In this article, I will explain the design and implementation of both the server and the client. There's nothing special about MahJong; the same principles can be applied to many other multi-user Internet applications.

Packets and Client/Server Communication
The nature of a client/server application requires data packets to be sent between the clients and the server. In a typical C implementation (e.g. in Netrek), we define several types of packets, each corresponding to a C structure, and use a byte tag to distinguish different packets apart. To send a packet through a socket connection, we simply coarse the structure into an array of bytes, and reverse the process when we want to read it back in. However, this approach is not possible in a strongly typed language like Java, because we cannot perform pointer casting. Therefore we must first get this technicality out of our way.

My approach is only a slight deviation from what we would normally do in C. Each type of packet is defined to be a class that extends a base abstract class Packet. Its field variables contain information the packet carries. We need two constructors: one takes as its arguments values for the field variables, the other a DataInputStream from which the packet should be read in. We also need a method that converts the packet into a binary form that can be sent out to a DataOutputStream and read back in by the constructor mentioned above.

I chose to use DataInputStream and DataOutputStream because they offer the ability to send binary data which reduces network traffic, and they also provide a pretty complete set of methods that deals with many commonly used data types like integers and strings. It is still tedious to call those methods for all the field variables for all the packet classes, but I see no easy way around it. It is possible, however, to speed up sending the same packet to multiple clients by using a format() method to write the binary data into a byte array first.

One question remains: how do we discern all the packets being sent to us, and how do we act accordingly? The answer is a packet dispatcher along with handlers for all the packets. The dispatcher is a thread that does an infinite loop reading in a single byte from the input stream. This byte would be a tag that marks which packet follows it. Then we can use a switch()statement to call the constructor of the actual packet to read it in, and call an appropriate method that handles that packet. There are also more elegant solutions, but this approach works well for our purposes.

Listing 1 shows some packet examples. We define here a packet that carries a player's identity and a response from the server.

The Client
Now we are ready to turn to writing the client. This is a program that interacts with both the user and the server, but actually it is the easy part of the whole application. Here I will only give an outline.

Unlike most Java applets, the MahJong client needs to live in its own window (Frame in AWT parlence). There are two reasons for this. First, a user is likely to spend a lot of time playing the game with other people, so we want to give him the ability to browse other web sites without interrupting his game. Second, the client needs to display a lot of stuff, including lists of all the players and tables, a communication panel and a big panel that draws all the tiles on his table. Therefore, we are going to need all the screen real estate we can get. Thus we are led to defining two classes: a Greeting class that extends java.applet.Applet and a Client class that extends java.awt.Frame. Since it's the Client class that does all the real work, we should also let it implement Runnable. See Listing 2 for a sketch of Greeting and Client.

The class Greeting simply presents a button and waits for input. When a user wants to connect to the server, he presses the button, and Greeting tries to open a socket connection to the server. We get the host name of the server from an applet parameter embedded in the <applet> tag in our main web page. Upon successful connection, Greeting creates a new instance of Client, and we are in business.

The Client object first spawns a thread from its run() method. The thread is none other than a packet dispatcher that reads in packets from the server and calls the appropriate handler methods in Client. Then we can open our new window (recall that Client extends Frame) with the complete GUI in it. In the sample code, I present the simplest possible login window. A real working client will likely have a lot more GUI elements and other classes.

There is a convention I want to recommend, that all user actions should only cause packets to be sent to the server for processing (possibly with some minimal error-checking on the client side). Only the packet dispatcher thread that reads server packets can update the status variables in the client. For example, although the client keeps a local copy of all the players and which tables they are on, when the user requests to join a table, the client only sends a join-table packet to the server. The server then processes this request and sends back a join-table packet to the client if the request is granted, and only then does the client change its internal data and graphic display to match the action. This convention vastly reduces the care that needs to be taken for client/server synchronization, and also makes future changes in the client/server protocol less likely to mess things up.

The Basic Server
The server is the central piece that ties everything together, so it's expected to be a very complicated program. However, we do have a lot of technical means at our disposal, notably Java's built-in multithreading capability. To begin with, we will first sketch an outline: the classes we will define, and what methods we need to operate on them.

First of all, we need a Server class, which will have a main() method to start the whole thing. It first creates a ServerSocket and listens on a predefined port number. Each time it accepts a connection, it will create a new instance of a Player class.

The Player class stores all the information about a particular player, like his socket connection, character name, total score, table, etc. In particular, we need a DataInputStream and a DataOutputStream, which we can figure out from the socket connection. To receive packets from the player's client, the Player class should extend Thread, have a run() method that is a packet dispatcher, and implement a bunch of packet handler methods. Let's call this thread the player's input thread.

We will also need to send server packets to each player's client, so the Player class should have an output (Packet p) method. Let's think for a moment what this method should do. The naive implementation is to just take the Packet p that we want to send out, and call its output() method with our DataOutputStream. But which threads will be calling Player. output()? And what might happen during the output process? Let's imagine that one player, A, wants to send a text message to a player B (this is actually not allowed on the MahJong server to prevent fraud, but there are many other similar circumstances). So player A's input thread reads in a text message packet and finds out it's intended for player B, and it proceeds to call B's output() method with a server-side text message packet. Thus, in effect, any player's input thread could call any other player's output thread. However, this may cause a problem, because as we know, writing data into a socket can take a long time to finish, or may not finish at all. Thus A's input thread may be blocked indefinitely trying to send packets to B.

My solution is to introduce another class per Player, namely PlayerOutput, that also extends Thread. Let's call it the output thread. Each output thread maintains a queue of preformatted (i.e. converted to byte[] by the format() method) packets to be sent out. The output thread simply waits for a packet to appear on its queue, pops it off the queue, sends it out, and repeats this process. Now, each Player's output() method simply pushes the packet onto the associated PlayerOutput's queue. Writing the Queue class is a simple exercise using linked lists and monitors. We present a version in Listing 3.

The MahJong server provides a bunch of MahJong tables, each table supporting four players and several spectators. We can make a Table class to encapsulate all the table-related data and methods. A typical method of a Table object is to handle some game play made by one of the players on that table. To simplify things, I chose not to implement Table as a Thread. Instead, it's the various Player input threads that read in game-play packets and call the appropriate Table methods to handle them. To make this plan work, the various Table methods should be declared synchronized, and take only a short time to execute. This last requirement is easily satisfied for games like MahJong, bridge, or chess, where the game status can change only when some player makes an action, and the exact effect of a play can be easily figured out. If you are writing a real-time game where the game status changes independently of player actions, you will need to use a dedicated thread that updates the game status periodically. Now we basically have a skeleton of the server. We show it in Listing 4.

Making a Robust Application
We will now explore some more subtle issues. They mostly have to do with synchronizations and dead socket connections. If we don't take care of them, we won't be able to run our application on the Internet for an hour before either the server or the clients crash!

Here's the first problem. In the server, we keep a list of all the players connected to our server. This is simply an array (or Hashtable) of Player objects. Now, when some player joins or leaves our server, we need to update this global list. We also need to occasionally send all the players' names to some player, or perhaps send a particular packet to everyone. Theoretically, these operations may happen at the same time in many threads. There is a similar situation for the list of all open MahJong tables.

The reader may recognize this as the classical "reader-writer" problem: we have a document (the list of all players), many readers (threads that try to send packets to everyone or query everyone's name,) and many writers (threads that handle players joining or leaving the server). An arbitrary number of readers are allowed to read at the same time, but reading and writing are mutually exclusive, and all the writers are mutually exclusive. It's also a good idea to give writers higher priority than readers.

We solve this problem with a class RWMonitor, that solves the "reader-writer" problem in general. The code is in Listing 5. To use it on the player list, for example, we need to create a new RWMonitor object first, call it rw_mon. Whenever we need read (or write) access to the player list, we first call rw_mon.inRead() (or rw_mon.inWrite()) to enter a critical region. Then we can proceed, but remember never to modify the player list in a read-only critical region! After we are done, we simply call rw_mon.outRead() (or rw_mon.outWrite()) to exit the critical region. Listing 4 shows some typical usage of the RWMonitor class.

Granted, our MahJong server spends most of its time waiting for packets to arrive, but race conditions could occur, and it almost certainly will if the server is to run for an extended period of time. It's never a good idea to ignore possible synchronization problems, however unlikely they are to happen.

The other problem I want to mention has to do with dead socket connections. As we know, the Internet is far from being robust. Routers probably go down at a rate faster than car accidents occur. Now it would be all the same if Java's socket library could tell us that some link is dead as soon as it happens, but unfortunately it doesn't all the time. On the alpha version of my server some dead connections simply didn't get reported to my program. No exceptions. The read and write calls simply hang there. And in Java, there is currently no way to specify a time-out on read and write calls.

There is a solution using so-called ping packets. These are packets that are sent from the client (by a dedicated thread) regularly, say at intervals of thirty seconds. When the Player object on the server side receives any packet, it always increases a ping counter. This way, even if the user is idle, his ping counter always increases at least once every thirty seconds. On the server side, we make another Thread, called WatchDog, that is usually sleeping but wakes up once every minute or so, to check the ping counters of all the players on the server. If some player's ping counter is zero, that means the connection between the server and the client must be dead, so our WatchDog proceeds to clear this player's slot. In any case, WatchDog will reset all players' ping counters to zero before it goes to sleep again. We won't go into more details here, as the implementation of such a method depends on the actual client/server protocol. Sample code can be found in my MahJong Server distribution.

This method has the added bonus of being able to gauge a user's idle time. If a player is on a table playing a game, and the ping counter reaches two before we receive any game-play packet from his client, we know that the player is probably answering the phone or caught by his boss, and we can choose to kick him off the table to let someone else on.

The last advice I want to give may sound like a cliche. That is, always follow the practices of object-oriented programming! Do not save on classes. Put methods where they belong. In my beta version I had a persisting dead-lock problem. Eventually I tracked it down to a synchronized method that really belonged to another class, but I had put it in the wrong place in order to save myself some typing!

I hope my article can help you write your own Internet game. The source code listing is brief but self-contained and runs as is, so it can be used as a starting point. Happy Java programming!

The MahJong server: has kept moving for various reasons. When I do find a permanent home for it, I'll put a link in my personal home page. The source code is available under GNU Public License (GPL) at "http://ftp.princeton.edu/pub/MJ_dist.tar.gz".

More Stories By Thomas Z. Feng

Thomas Z. Feng is a Ph.D. student in the Mathematics Department at Princeton University. He is working on Algebraic Geometry, and loves to write programs for fun.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.

IoT & Smart Cities Stories
Early Bird Registration Discount Expires on August 31, 2018 Conference Registration Link ▸ HERE. Pick from all 200 sessions in all 10 tracks, plus 22 Keynotes & General Sessions! Lunch is served two days. EXPIRES AUGUST 31, 2018. Ticket prices: ($1,295-Aug 31) ($1,495-Oct 31) ($1,995-Nov 12) ($2,500-Walk-in)
Andrew Keys is Co-Founder of ConsenSys Enterprise. He comes to ConsenSys Enterprise with capital markets, technology and entrepreneurial experience. Previously, he worked for UBS investment bank in equities analysis. Later, he was responsible for the creation and distribution of life settlement products to hedge funds and investment banks. After, he co-founded a revenue cycle management company where he learned about Bitcoin and eventually Ethereal. Andrew's role at ConsenSys Enterprise is a mul...
Nicolas Fierro is CEO of MIMIR Blockchain Solutions. He is a programmer, technologist, and operations dev who has worked with Ethereum and blockchain since 2014. His knowledge in blockchain dates to when he performed dev ops services to the Ethereum Foundation as one the privileged few developers to work with the original core team in Switzerland.
René Bostic is the Technical VP of the IBM Cloud Unit in North America. Enjoying her career with IBM during the modern millennial technological era, she is an expert in cloud computing, DevOps and emerging cloud technologies such as Blockchain. Her strengths and core competencies include a proven record of accomplishments in consensus building at all levels to assess, plan, and implement enterprise and cloud computing solutions. René is a member of the Society of Women Engineers (SWE) and a m...
Digital Transformation and Disruption, Amazon Style - What You Can Learn. Chris Kocher is a co-founder of Grey Heron, a management and strategic marketing consulting firm. He has 25+ years in both strategic and hands-on operating experience helping executives and investors build revenues and shareholder value. He has consulted with over 130 companies on innovating with new business models, product strategies and monetization. Chris has held management positions at HP and Symantec in addition to ...
The challenges of aggregating data from consumer-oriented devices, such as wearable technologies and smart thermostats, are fairly well-understood. However, there are a new set of challenges for IoT devices that generate megabytes or gigabytes of data per second. Certainly, the infrastructure will have to change, as those volumes of data will likely overwhelm the available bandwidth for aggregating the data into a central repository. Ochandarena discusses a whole new way to think about your next...
CloudEXPO | DevOpsSUMMIT | DXWorldEXPO are the world's most influential, independent events where Cloud Computing was coined and where technology buyers and vendors meet to experience and discuss the big picture of Digital Transformation and all of the strategies, tactics, and tools they need to realize their goals. Sponsors of DXWorldEXPO | CloudEXPO benefit from unmatched branding, profile building and lead generation opportunities.
Dynatrace is an application performance management software company with products for the information technology departments and digital business owners of medium and large businesses. Building the Future of Monitoring with Artificial Intelligence. Today we can collect lots and lots of performance data. We build beautiful dashboards and even have fancy query languages to access and transform the data. Still performance data is a secret language only a couple of people understand. The more busine...
All in Mobile is a place where we continually maximize their impact by fostering understanding, empathy, insights, creativity and joy. They believe that a truly useful and desirable mobile app doesn't need the brightest idea or the most advanced technology. A great product begins with understanding people. It's easy to think that customers will love your app, but can you justify it? They make sure your final app is something that users truly want and need. The only way to do this is by ...
DXWorldEXPO LLC announced today that Big Data Federation to Exhibit at the 22nd International CloudEXPO, colocated with DevOpsSUMMIT and DXWorldEXPO, November 12-13, 2018 in New York City. Big Data Federation, Inc. develops and applies artificial intelligence to predict financial and economic events that matter. The company uncovers patterns and precise drivers of performance and outcomes with the aid of machine-learning algorithms, big data, and fundamental analysis. Their products are deployed...