Without doubt, Java is one of the more spectacular products to hit the computer market in recent years. Inasmuch as its initial support came from academia, most initial Java applets have been limited to decorative roles. However, now that Javas popularity has increased, Java has recently been utilized for more practical purposes. Applets have been used to enhance the speed of search engines and to create interactive environments for such purposes as retrieving financial information. In essence, by employing the power of Java in such applets, users are now able to do much of what they would think they should be able to do on the Internet.
Nevertheless, one of the more exciting powers of Java is that it gives programmers the ability to create multiuser environments in which many users can interact and share information. In a simple multiuser environment (see Figure 35.1), several users are connected to each other through a server running on a mutually accessible host. As a result, all actions by one user can instantaneously be displayed on the screens of other users across the worldwithout any requirement that the users know each other beforehand.
FIGURE 35.1.A multiuser environment.
This feature enables Java programmers to accomplish feats never before possible in a Web page. A business can now set up such a system so that people who desire information could talk with a customer service representative directly on their Web page, rather than sending e-mail to a service department. Additionally, an enterprising company could allow people from around the world to participate in a real-time live auction. In general, by facilitating multiple-user environments that enable such sharing of information, Java has the power to revolutionize the Web.
In this chapter we discuss the elements required to create such a multiuser environment in Java. Although no entirely new topics in Java are presented, this chapter shows you how to bring several powerful aspects of Java together and highlights some of the more interesting applications of Java. Through explanation of the processes as well as sample code, this chapter enables you, the Java programmer, to code and establish your own multiuser environment. This chapter deals with subjects including the handling of the connections to the server, the graphical interface, and various animation and threading techniques necessary to make your applet Web-worthy. While each of these topics is illustrated with code, keep in mind that the essence of a multiuser environment is what you do, not how you do it. Furthermore, the code for each multiuser application is heavily dependent on the environment itself, and therefore significantly different in each. Consequently, while the code offered here can supply you with a suitable framework, it may be advisable to envision your own multiuser application. As each topic is presented, imagine how you would deal with each of the issues involved. Remember that in programming, and Java in particular, the limits of the language are the limits of your imagination. Be creative and have fun!
In this chapter we develop a multiuser environment for a museum that is opening an exhibition of Haphazard Art. The museum believes that the most beautiful art is created by human-controlled whims, and thus has hired you to create an environment through which users from around the world can weave a quilt. The user should be able to access the Web page, select a color and a blank tile, and paint the tile. Not only should this tile change colors on the screen of the user, but it also should instantaneously become painted on the screens of all the users around the world who are working on that quilt. (The museum will then save these designs and display them at its upcoming exhibit.)
While this is a rather simplistic example of the power of multiuser environments, it is an excellent model for the explanation of the concepts involved.
Although it is not of our direct concern, the server in this environment plays an extremely large role. Because this server can be written in virtually any language, we wont spend much time dealing with it here. However, it is necessary that we discuss the essentials for the server in a multiuser environment.
As you can see from the Figure 35.1, the server acts as the intermediate agent between the various users. Thus, the server must be able to do the following:
As the application becomes more involved, it is necessary to add additional functionality to the server. Even in the simple example of the museum quilt, its necessary that the server keep track of the color of all the tiles. Furthermore, if a new client begins work on an in-progress quilt, its necessary for the server to inform the client of the current status of the quilt.
Nevertheless, these subjects are extremely context-dependent and deal more with computer science than Java. Therefore, we now move on to more exciting matters, such as the socket communication required to provide the interaction.
For an effective implementation, its necessary to create a class that handles all the interaction with the server. This class manages such responsibilities as connecting and disconnecting from the server as well as the actual sending and receiving of data. By encapsulating this functionality in a separate class, we are able to deal with our problem in a more effective and logical manner.
A more important reason for encapsulating this functionality in a separate class, however, is the fact that in a multiuser environment the applet must do two things at the exact same time: listen for user interactions (such as a mouse click) and listen for new information from the server. The best way to do this is to create a separate class to handle the server interaction, and make that class a thread. (See Chapter 15, Threads and Multithreading, for details on creating threads.) This threaded class will continually run for the lifetime of the applet, updating the quilt when required to do so. With this problem out of our hands, we can allow the applet class to respond to user interactions without worrying about the socket connection.
Assuming that we have a suitable server running, the actual communication is not a very complicated process. In essence, the applet need only to connect to the server and read and write information to the appropriate streams. Conveniently, Sun has wrapped most of these methods and variables in the java.net.Socket class.
Here is the beginning of our Client class that handles the actual communication:
import java.net.Socket; import java.io.*; public class Client extends Thread { private Socket soc; public void connect () { String host = www.museum.com; int port = 2600; soc = new Socket(host, port); } }
Note that it is necessary for you to know both the name of the host on which the server is running as well as the port on which the server can accept connections.
While in theory the previous method is sufficient to connect to a server on a known port, any experienced programmer knows that what works in theory does not always work in practice. Consequently, it is good practice to place all communication statements in a try-catch block. (Those unfamiliar with the throw and try-catch constructs can refer to Chapter 16, Exception Handling.) In fact, forgetting to place all statements that deal with the server in a try-catch block will produce a compile-time error with the Java compiler.
Consequently, the precious method should actually look like this:
import java.net.Socket; import java.io.*; public class Client extends Thread { private Socket soc; public boolean connect () { String host = www.museum.com; int port = 2600; try { soc = new Socket(host, port); } catch (Exception e) return(false); return(true); }
Note that the method now returns a boolean variable in accordance to whether or not it was successful in connecting.
Inasmuch as different situations require different constructs, Java supplies us with several options for the syntax of communication with a server. While all such approaches can work, some are more useful and flexible than others. Consequently, what follows is a rather generic method of communicating with a server, but keep in mind that there are some nice wrapper methods in the java.io.InputStream subclasses that you may wish to use to simplify the processing of the parsing involved with the communication.
Once we have established our connection, the output is then passed through the java.net.Socket.outputStream, which is a java.io.outputStream. Because this OutputStream is a protected variable, however, we cannot reference it as simply as one might imagine. In order to access this stream we must employ the java.net.Socket.getOutputStream() method. Thus, a simple method to send data would resemble the following:
public boolean SendInfo(int choice) { OutputStream out; try { out = soc.getOutputStream(); out.write(choice); out.flush(); } catch (Exception e) return(false); return(true); }
Although the preceding code is sufficient to send information across a socket stream, com-munication will not be that simple in a true multiuser environment. Because we will have multiple clients in addition to the server, it is necessary to define a common protocol that allparties will speak. While this may be as simple as a specific order to a series of integers, it is nevertheless vital to the success of the environment.
In the case of our quilt-making applet for the museum, each update packet will consist of three things: the x-coordinate of the recently painted tile, the y-coordinate of the recently painted tile, and the color that is to be applied (which could also be represented by an integer). Additionally, we will use the newline character as our terminating character. All of this can be accomplished with the following method:
public boolean SendInfo(int x, int y, int hue) { OutputStream out; try { out = soc.getOutputStream(); out.write(x); out.write(y); out.write(hue); out.write(\n); out.flush(); } catch (Exception e) return(false); return(true); }
Another factor that complicates the reading of information from the socket is the fact that we have absolutely no idea when new information will travel across the stream. When we send data, we are in complete control of the stream and thus can employ a rather simple method as we did earlier. When reading from the socket, however, we do not know when new information will arrive. Thus we need to employ a method that will run constantly in a loop. As discussed previously, the best way to do this is to place our code in the run() method of a threaded class.
What results is the method shown in Listing 35.1. In reading it, pay attention to how we are able to stop the method from reading from the socket once the user is done; how the method would deal with garbled input; and how the method returns information to the main applet. (You will be quizzed later!).
public void run() { int spot = 0; int stroke[]; stroke = new int[10]; // an array for storing the commands DataInputStream in; in = new DataInputStream(soc.getInputStream()); while (running) { do { // reads the information try { stroke[spot] = in.readByte(); } catch (Exception e) stroke[spot] = \n; // restarts read on error } while ( (stroke[spot] != \n) && (++spot < 9) ); // reads until the newline flag or limit of array spot = 0; // resets the counter quilt.sow(stroke[0],stroke[1],stroke[2]); } }
Okay. Time is up. Here are the answers.
First of all, we must remember that we are designing this class to serve within a larger applet. It would be rather difficult for the Client class to decide when the painting session is over for it has no idea what is going on inside the applet. Thus, the applet, not the Client class, must have control of when the class will be looking for information. The best way to retain control of the run() method is to create a boolean field (running) inside the Client class itself. This variable can be set to true on connection to the server and can be set to false when the applet decides to close the connection. Thus, once the user has decided to close the connection, the run() method will exit the while loop and run through to its completion.
Second, we must remember that while we plan on receiving three integers followed by a newline character, in real life things do not always work as we plan them. Inasmuch as we do not know what exactly the error will be, there is no ideal way of handling garbled information. The previous method uses the newline character as its guide, and will continue to read until it reaches one. If, however, there are nine characters before a newline character, it will exit the do loop regardless and call the sow() method. This means that the sow() method in our main applet must be well constructed to handle such errors.
Third, and most important, is the means by which the class returns data to the applet. As we discussed earlier, this Client class will be a subclass of our main applet. Consequently, we will be able to call all public methods of the applet class. (In this example, the applet is referenced by the variable quilt.) While we will discuss this process in more detail later, keep in mind that the last line of code
quilt.sow(stroke[0],stroke[1],stroke[2]);
simply passes the appropriate values to a method in the applet that will in turn process them.
So far, we have been able to connect to the server and communicate with it. From the point of view of the user, we have begun the application and have successfully painted our quilt. We therefore have only one essential functionality that we havent included in our Client class thus far: the capacity to disconnect from the server.
While the capacity to disconnect from the server may appear trivial, it is in fact very essential to a successful multiuser environment. A server can be constructed to accept numerous clients, but due to hardware constraints, there is an inherent limit to the number of clients that a server can have connected at the same time. Consequently, if the socket connections remain open even when the clients leave, the server will eventually become saturated with lingering sockets. In general, its good practice to close sockets, for it benefits all aspects of the environment, ranging from the Internet-access software that the user is running to the person managing the server.
Without further ado, here is an appropriate disconnect() method for our Client class:
public boolean disconnect () { running = false; try { soc.close() } catch (Exception e) return(false); return(true); }
While the code itself is nothing extraordinary, do note the use of the Boolean variable running (also used in the run() method). You will remember that the function of the running variable was to serve as a flag that allowed the client to listen to the socket stream. Inasmuch as we do not want the client to be listening to the socket stream after the user has decided to disconnect, we set the variable to be false in this method. Also note that, the running = false statement comes before the soc.close() statement. This is because regardless of the success of the disconnect statement, we no longer wish to listen to the stream.
Now that we have developed the framework of our Client subclass, lets switch gears for a moment to develop the applet class that will act as the heart of our application. While not extremely intricate, the applet must be able to respond to user input and present the user with a friendly and attractive display. Fortunately, as a Web-based programming language, Java is very well suited for this. Each applet that you create inherits various methods that enable it to respond to virtually anything that the user can do. Furthermore, the java.awt package provides us with some excellent classes for creating good-looking interactive features such as buttons and text areas.
The most important aspect of a graphical interface is its capacity to monitor the users responses to the system. As you have seen in Chapter 22, The Windowing Package, Java supplies us with an excellent system for doing this in the form of the java.awt.Event class. Additionally, the java.awt.Component class, of which java.applet.Applet is a subclass, provides us with many methods that we may use to catch and process events. These methods will seize control of the applet under appropriate conditions, thereby enabling the applet to react to specific user events.
For our museum quilt applet, the user must be able to change the current color, select a tile, and quit the application. If we decide that the user will select the tile through a mouse click and change colors and use the keyboard to quit, we would require two interactive methods from the java.awt.Component class: keyDown(Event, int) and mouseDown(Event, int, int).
In this setup, the keyboard has two purposes: to enable the user to change colors and to enable him or her to quit. If we can choose to present the user with a pallet of numbered colors (0 to 9, for example), from which he or she can select, our keyDown() method could be as simple as
public boolean keyDown(Event evt, int key) { if ( (key >= 0) && (key <= 9) ) { // if a valid key current_color = key - 48; // converts ASCII key to numeric return(true); // equivalent } else if ( key = Q) { leave(); // a method that will handle cleanup return(true); } return(false); }
where current_color is a field that keeps track of the currently selected color.
If we declare museum to be an instance of the Client socket class (that we have almost completed), the mouseDown() method could be accomplished with the following code:
public boolean mouseDown(Event evt, int x, int y) { int x_cord, y_cord; if ( (x >= xoff) && (x =< xoff + xsize) && (y >= yoff) && (y =< yoff + ysize) { // checks to see if the click was within the grid x_cord = (x - xoff)/scale; // determines the x and y coordinates y_cord = (y- yoff)/scale; // of the click museum.sendInfo(x_cord, y_cord, current_color); // makes use of the // sendInfo method to send the data return(true); // the click was valid } return(false); // the click was outside the bounds of the grid }
Note that in the previous method it is necessary to have already declared the x and y offsets, the size of each tile (scale), and the size of the grid itself as global fields of the class.
In Chapter 23, The Applet Package and Graphics, you were given an overview of how to create a background and fill it in with whatever you would like. In a dynamic graphical interface, however, there will be various properties that will change and thus must be tracked in some manner. Our quilt application may consist of nothing more than a single colored background on which tiles of different colors are drawn. Consequently, the only information that we are concerned with is the color (if any) that has been assigned to the individual tiles.
A simple way of doing this is to create an array of java.awt.Color (hues[ ]) and assign each color to be used a given value (hues[1] = Color.blue, for example). Thus, we can store the design in simple array of integers: tiles[ ][ ], with each integer element representing a color. If we do so, our paint() method can be accomplished by the following code:
public void paint(Graphics g) { for (int i = 0; i < size; i++) { for(int j = 0; j < size; j++) { g.setColor(hues[ (tiles[i][j]) ]); g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale); } } }
Note that in the preceding example, g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale); will paint squares with length and width equal to a final field, scale, that are located on the grid with respect to the initial x and y offsets.
Thus far, we have been successful in creating the interface that the user will find on our Web page, as well as some of the methods necessary to facilitate the users interaction with the applet. We have also assembled a Client subclass that will serve as our means of communication.Our next step is to integrate the Client class within the applet class to produce a cohesiveapplication.
Nevertheless, it is important to keep in mind that we are not using this subclass merely as a means of keeping the information separate. The main reason for creating a separate class is that by making it a thread as well, we are able to perform two tasks at the exact same time.
NOTE |
---|
Although it may seem as if we are able to perform two tasks at the exact same time, this is not entirely true on a standard single-processor computer. What Java (and other time-sharing applications such as Windows) does is portion time-slices to each thread, allowing each to run for a brief moment before switching control over to the other. Because this process is automatically done by the Java virtual machine and because the time for which a given thread is not running is so small, we can think of the two threads as running at the same time. |
Although we almost forget about its existence, we must retain some control over the Client class. We do not want it checking for new commands before the socket connection has been opened or after it has been closed. Additionally, we want to be able to control such socket events as opening and closing in a manner that will isolate them from the main applet, but will also impact the status of our run() method. Conveniently for us, we developed the Client class in such a manner: keeping the connect() and disconnect() methods separate entities, both of which have control over the run() method (by means of the boolean variable running).
Consequently, within the applet class, the code necessary to control communications is quite simple:
public class Project extends Applet { public Client museum; . . . public void init() { museum = new Client(); . . museum.connect(); museum.start(); } . . public void leave() { museum.disconnect(); } }
First of all, note that this is the first time that we have dealt with the applet itself. Thus far its only important property is that it is named Project.
Also note the use of the constructor Client(). Like any other class, the Client class requires not only a declaration, but also a new statement to actually create and allocate memory for the class. We will deal with this constructor again in the next section.
Finally, note that in the previous example we established the connection by means of the museum.connect() statement located the init() method. Most likely (and in the case of the museum applet), we will want to establish the socket stream as soon as the applet starts up. Consequently, the most logical place for the museum.connect() statement is in the init() method. Nevertheless, you may place this code anywhere you may like in your applet. (You may, for example, have a Connect to Server button somewhere in your applet.)
WARNING |
---|
While giving the user the ability to initiate connection may be a good idea, be careful that you not do allow the same user to connect the server more than once without first disconnecting. This can lead to all sorts of headaches and improper results. |
We have created the framework of both the Project (applet) and Client classes and have begun the process of intertwining them by creating an instance of the Client class in the Project class. However, there is more to the interaction between the two classes than what we have thus far.
Remember that the purpose of the Client subclass is to provide information to the applet. Nevertheless, we are required to employ the run() method of the threaded Client class, which does not allow us to return information through a simple return() statement. Also, in most cases (as well as the museum application), we must return several pieces of information (the x and y coordinates of the tile being painted as well as its color).
However, with a little construction, we can easily solve this problem. First, we must enable the Client class to refer to the Project class. Thus, we must make a few alterations to the Client class. The changes will allow the Client class to accept the Project applet class in its constructor method and make use of it during its lifetime.
public class Client extends Thread { private Project quilt; public Client (Project proj) { quilt = proj; } ... }
NOTE |
---|
Note that in this setup, the parent class is required to pass itself as an argument to the constructor method. Therefore the appropriate syntax would resemble the following: |
museum = new Client(this);
What exactly does the previous code accomplish? First, it establishes a constructor method for the Client class. (Remember that as in C++, constructor methods must have the same name as the class itself.) While this constructor may serve other purposes, its most important function is to accept a reference to a Project class as one of its arguments.
Furthermore, we have created a public variable, quilt, of type Project. By doing so, each method of the Client class is now able to reference all public methods and variables in the Project class. For example, if we had the following definitions in the Project class
public int tiles_remaining; public void sow(int x, int y, int hue) { ... }
the Client subclass could reference quilt.tiles_remaining, and quilt.sow().
Okay, time for an assessment of where we stand. We are now able to
As of now, whenever the user clicks on a square, the request to paint is immediately sent to the server by the sendInfo() method. Nevertheless, we have not yet developed a true method of translating a command after it comes back across the server stream.
We have, however, laid the foundation for such a process. By enabling the Client subclass to refer to all public variables and methods of the Project applet class, we have given ourselves access to all of the necessary information. In fact, we have many options for updating the data in the applet once it has been parsed from the server stream. Nevertheless, some approaches are much better than others.
The most secure and flexible approach is to create a translating method in the applet class, such as the sow() method in the Project classmentioned several times in this chapter. This method will serve as the bridge between the client and applet classes. Why is this approach the best? Why cant we create some public variables in the Project class that can be changed by the Client class? There are several reasons.
The first such reason is that it makes life a great deal easier for the programmer. By employing a translating method, we are able to maintain the encapsulation enjoyed thus far in our application. The sow() method will be contained entirely within the Project class and thus will have all the necessary resources, such as private variables that would be hidden from the Client subclass. Furthermore, when we are actually coding the application, we will be able to focus on each class independently. When we code the Client class, we can effectively forget how the sow() method will work, and when we code the Project class, we can trust that the appropriate information will be sent to it.
The second reason for having the Client class rely on a foreign method is flexability. If we decided to revamp this application, thereby changing the manner in which we stored the data, we would have no need to drastically change the Client class. In fact, in the worst case, the only changes necessary would be to increase the amount of data being read from the stream and the number of parameters passed to the sow() method.
The third reason for employing a translating method is that by linking the two classes by a method rather than direct access, we are able to prevent the corruption and intermingling of data that can occur when two processes attempt to access the same data at the same time. For example, if we made tiles[ ][ ], the array that contained the design in the applet class to be public, we could change the colors with a statement such as this:
quilt.tiles[(stroke[0])][(stroke[1])] = stroke[2];
However, what would happen if at the exact moment that the paint() method was accessing tiles[1][2], the Client class were changing its value? What if more data had to be stored (the color, the design, the authors name, and so on)? Would the paint() method get the old data, the new data, a mixture, or none? As you can see, this could be a catastrophic problem. However, if we use a separate translating method in our applet class, this problem could easily be solved through use of the synchronized modifier. If our applet grew to the point that this problem presented itself, we could make the paint() method synchronized and place any necessary statements in a synchronized block. As a result, these portions of our code would never be able to run at the exact same time, thereby solving our problem.
Now that we have seen why the sow() method is necessary, let us discuss its actual code. As we discussed earlier, each command in the museum application will consist of three integers: the x coordinate, the y coordinate, and the new color. Also remember that in our discussion of the Client class, we noted that the sow() method must be able to handle corrupted data. Consequently, the sow method could look something like this:
public void sow(int x, int y, int hue) { if ( (x>=0) && (x <= size) && ( y >= 0) && (y <= size) && (hue >= 0) && (hue <= Â9) ) { // 9 colors tiles[x][y] = hue; repaint(); } }
While the first statement is self explanatory, the second statement deserves some comment, if not a complete explanation. The first statement performs the actual task of changing the color on the tile. However, the user will not see anything unless the repaint() method is called. Thus, by setting the array and calling repaint(), the sow() method updates the quilt in the mind of the computer as well as on the screen itself.
In a brief moment, we will also harness the sow() method to perform another vital functionfor us.
Now that we have developed the entire functionality of the Client subclass, we will tie ittogether once and for all and put it aside. First look our final product over, as shown in List-ing 35.2.
import java.net.Socket; import java.io.*; public class Client extends Thread { private Socket soc; private Boolean running; private Project quilt; public Client (Project proj) { quilt = proj; } public void connect () { String host; int port = 2600; host = www.museum.com; try { soc = new Socket(host, port); } catch (Exception e) return(false); } public boolean SendInfo(int x, int y, int hue) { OutputStream out; try { out = soc.getOutputStream(); out.write(x); out.write(y); out.write(hue); out.write(\n); out.flush(); } catch (Exception e) return(false); return(true); } public void run() { int spot; int stroke[]; stroke = new int[10]; // an array for storing the commands spot = 0; DataInputStream in; in = new DataInputStream(soc.getInputStream()); while (running) { do { // reads the information stroke[spot] = in.readByte(); } while ( (stroke[spot] != \n) && (++spot < 9) ); // until the newline flag or limit of array spot = 0; // resets counter quilt.sow(stroke[0],stroke[1],stroke[2]); } public boolean disconnect () { running = false; try { soc.close() } catch (Exception e) return(false); return(true); } }
While none of the methods are new, there is a very important piece of the class that has been completely installed for the first timenamely the boolean field running. As a field, it is accessible to all methods within the Client subclass. Nevertheless, by making it private, we have assured that if it is changed, its change will be associated with an appropriate change in the status of the socket connectiona connection or disconnection, for example.
While the Client class may require application-dependent changes, the previous version is rather sufficient to satisfy most requirements. However, as of yet we have dealt rather little with the applet itself. This is primarily because each multiuser applet will inherently be very different. Nevertheless, there are a few topics that should be discussed if you wish to develop a quality multiuser applet.
What we have developed thus far in the chapter is entirely sufficient to satisfy the demands of the museum. Nevertheless, there are several other topics regarding efficiency, architecture, and appearance that we have not dealt with yet. In the next few sections we will discuss such issues as well as those regarding the development of an effective server on the other end.
While a programmer may appreciate the intricacies of a multiuser environment, users are attracted to those applications that look nice and enable them to do interesting things. While a multiuser environment is certainly one of the latter, we must still keep in mind the first criteria of making our applet look nice and run smoothly.
Although this chapters purpose is not to deal with such issues as the graphical layout of applets, the communication structures developed in this chapter do have a direct impact on the smoothness of the changes that will occur on the users screen. In the case of the museum applet developed earlier in this chapter, every time that a user clicks on a tile, his or her request to paint must be sent to the server and then returned to the client. Once this information is finally returned to the client, the applet must then repaint each and every tile. While this process occurs in a matter of secondsfor the user who has to wait three seconds to see his change reflected, as well as the user who can actually observe each tile being repaintedthis process may be a few seconds too slow.
A SHOT OF JAVA JARGON |
---|
As we have learned, the method that actually handles the creation of graphics in a Java applet is named paint(). While various actions such as drawing lines, changing the background color, and displaying messages may be performed in this method, this functionality is nevertheless commonly referred to as the painting of the applet. |
This slow and choppy appearance, commonly referred to as flickering, can nevertheless be easily remedied. While this requires a few changes to our applet, the main idea behind it is noticing that we do not need to repaint those items that have not changed. While this may seem rather elementary, when developing an applet, it would seem a great deal easier to simply repaint the entire screen. (In fact, if you take a look at some of the Java applets on the Web, you will notice that many applets exhibit the problem of flickering.)
In the case of our museum applet, you will see that we need only to paint the tile that has been changed. Because we know the coordinates and size of each tile, this should not be much of a problem at all. It would seem as if all that we had to do was modify the paint() method to paint only one tile. Thus when the sow() method calls repaint, only the most recently changed tile is changed.
Nevertheless, there are two problems with such a simplistic approach. First of all, the paint() method is not only called by explicit calls to repaint(). It is also called whenever the applet appears, such as when it first starts up or after the browser has been hidden behind another application. Thus, if we change the paint() method to paint only one tile, we may be left with only one tile on our screen, rather than the full quilt.
Another problem is that we cannot pass information to the paint() method inasmuch as (like the run() method of threads) we are overriding an already created method.
Dont worry. These problems can be handled easily. Peruse the following additions to our code in Listing 35.3 and see if you can determine how the two problems were solved.
public class Project extends Applet { private boolean FullPaint; private java.awt.Point Changed; public void init() { ... FullPaint = true; Changed = new Point(); } public void paint(Graphics g) { if (FullPaint) { for (int i = 0; i < size; i++) for(int j = 0; j < size; j++) { g.setColor(hues[ (tiles[i][j]) ]); g.fillRect( (i*scale+xoff) , (j*scale+yoff), scale,scale); } } else g.fillRect( (Changed.x*scale+xoff) , (Changed.y*scale+yoff), scale,scale); FullPaint = true; } public void sow(int x, int y, int hue) { if ( (x>=0) && (x <= size) && ( y >= 0) && (y <= size) && (hue >= 0) && (hue Â<= 9) ) { // 9 colors tiles[x][y] = hue; Changed.x = x; Changed.y = y; FullPaint = false; repaint(); } } }
Not too bad, right? Lets see how the problems were solved.
Most notably, we have added two new variables, one for each problem. The first variable is a Boolean named FullPaint. As you can probably guess, its function is to inform the paint() method as to whether it should paint all the tiles over or just the newest one. Note that the key to this variable is that we want it to be true as much as possible so that we prevent the one tile quilts discussed earlier. In this setup, the FullPaint variable is set to false only one line before the call to repaint() and is reset to true at the end of the paint() method.
CAUTION |
---|
Remember to reset the FullPaint to true at the end of your paint() statement! If you dont youll defeat the purpose of the variableand ruin the appearance of your applet. |
How did we deal with the second problem of being unable to inform the paint() method of what tile was changed? You will notice the use of the private Point Changed. Inasmuch as the field is accessible to all methods, by setting the x and y values of this variable in the sow() method, we are able to indirectly pass this information along to the paint() method.
A LOOK INSIDE THE EVOLUTION OF THE LANGUAGE |
---|
In the sample code, we employed Changed, a variable of type Point, which can be found in the java.awt package. While this is a useful class, it has not always been part of Java. When the alpha releases came out, authors (especially those who were making graphical interfaces) were forced to develop their own Point-type classes. While this was not much of a problem, Sun chose to respond to this need by including the Point class in the beta and later API libraries. |
Although we have developed a sufficient disconnect() method, we must remember that things do not always work as well as they should. In fact, the disconnect() method in the socket class has had some problems in Java further complicated by the fact that we are running our applet through a browser. As we discussed earlier, not closing sockets can become a serious problem in a multiuser environment. Thus, it is a good practice to develop into your protocol a close command. Although it should resemble the other commands in your protocol, it need not be anything elaborate. In the museum example, if 123 were the command to paint tile (1,2) color 3, sending -123 could be the defined close command.
Another related issue is that some users may not tell the applet that they are leaving, and thus the disconnect() method will not have a chance to execute. For example, the user may go to another HTML page or his or her computer might be struck with a sudden power outage. In either case, it is thus advisable to have some method by which the server can ensure that all its clients are still active.
A simple way of doing this is to develop another command into your protocol, the semantics of which are irrelevant. Regardless of the syntax, the server should send out some kind of Are you there? command periodically. In terms of the applet itself, you should develop the translating method in such a manner that when such a command is received it will respond with an appropriate answer.
Consequently, if this functionality is built into the applet, the server will know that any client that does not respond within a reasonable amount of time (20 seconds, for example) is no longer active and can be closed.
In this chapter, we have continually referred to the information passed from each client to the server as a request. Furthermore, you will note that in the system developed in this chapter, although a user may click a square, his or her screen is not updated until the request has been echoed back from the server. These two facts may seem a bit abnormal, for it may seem more natural to have the applet update itself and then inform the server of what was done. Nevertheless, as you will soon see, these two procedures are very necessary for the exact same reason.
In the example of the museum applet, users are only able to paint blank (unpainted) tiles. Thus, imagine for a moment what would happen if user A decided to paint tile (1,2) black, and a moment later user B decided to paint tile (1,2) yellow. User As command would be received first, and thus Bs command would be received second. (It is impossible for the server to receive two commands at the same time on the same socket. Subsequent commands are normally placed in a queue.) If the applets were designed to paint themselves before the commands were echoed, user A and user B would have different images of the same quilt at the same time. Disaster!
Consequently, in a multiuser environment where the status would be really important, or even a game, it is essential that you develop your environment in such a manner that the status of whatever is being displayed on the screen, such as the position of the players, is updated only by what is returned from the server. As a result, the server should be developed in such a manner that any invalid requests (such as a request to paint an already painted tile) are simply sucked up by the server and are not echoed to any clients. Thus, if two users attempt to paint the same tile, only the request of the first user would be honored, and the second request would simply be swallowed up by the server. Even better, you could enable your server to send user- specific error messages that would cause some form of error message on the users screen.
Several situations exist (such as error messages) wherein the server would like to speak with just one user. Furthermore, your situation might require that only five users may be in the environment at a given moment. These are real-world problems, but ones that are easily solved.
At the heart of these solutions is the idea of having more than one server for your multiuser environment. This may involve one central server that has the power to spawn other servers (on other ports), or a central server acting as agatekeeper, accepting clients only if there are openings.
Either case requires the development of a richer protocol, but also provides you with opportunities for greater power over your environment. For example, you can now establish several two-player games that will be managed by a central server. By designing the applet to connect on a given port (1626, for example) and request entrance to a game, the server on that port would act as a manager by beginning a new game on another port (1627, for example) and informing the applet of the new port number. Thus, the applet would disconnect from port 1626, connect to port 1627, and wait for the central server to send another user to the same port. Once the central server has sent two players to port 1627, it would start a new server on the next available port (1628, for example) and could continue indefinitely.
Also, in the case of the central server/children server system as well as the gatekeeper system, the server may assign each client an identification number upon connection. This will not only allow the server to keep track of who is sending the information, but also enable it to send user- specific messages (for example, Player OneYou cant do that). Both of these tasks can be facilitated by appending a simple identification letter or number to each command in the protocol. (B112 could mean, for example, that player 1 just bought plot 12.)
Although we have dealt with many topics in this chapter, keep in mind that the power of Java is its abstract capability to facilitate a multiuser environment, not its specific lexical constructs. Nevertheless, there are a few issues relating to the Java language that one should keep in mind when developing a multiuser environment: