ch27 (9)


developer.com - Reference Click here to support our advertisers SHOPPING JOB BANK CLASSIFIEDS DIRECTORIES REFERENCE Online Library LEARNING CENTER JOURNAL NEWS CENTRAL DOWNLOADS COMMUNITY CALENDAR ABOUT US Journal: Get the weekly email highlights from the most popular journal for developers! Current issue developer.com developerdirect.com htmlgoodies.com javagoodies.com jars.com intranetjournal.com javascripts.com All Categories : Java Chapter 27 Multiuser Network Programming by Michael Afergan CONTENTS Our Mission-Should We Choose to Accept It The Requirements of the Server Integrating a Communication Class in Your Applet How to Connect to a Server How to Communicate with the Server How to Disconnect from the Server The Graphical Interface How to Thread the Client Class How to Share Information The Translating Method Advanced Topics of Applet Development Summary 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 Java's popularity has increased, Java has recently been used for more practical purposes. Applets have been used to provide live-time, ticker-like feeds and to create interactive environments for such purposes as retrieving financial information. In essence, by employing the power of Java, users can now do much of what they think they should be able to do on the Internet-but have not been able to do before. 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 such as the one in Figure 27.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 world-without any requirement that the users know each other beforehand. Figure 27.1 : A multiuser environment. This ability to link computers in live time enables Java programmers to accomplish feats never before possible in a Web page. A business can now set up a system so that people who desire information can talk with a customer service representative directly on the Web page rather than sending e-mail to a service department. Moreover, because of the graphical nature of the Web and Java, the customer service representative can demonstrate information in a visual manner (for example, by pointing out items on a diagram). Additionally, an enterprising company can 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. Why Java? The concept of servers, sockets, and communication is nothing new, nor is the idea of linking many users together in a live-time environment. In fact, the Web, without Java, can be considered a multiuser environment inasmuch as multiple users can access the same information in the form of a simple HTML page. Furthermore, methods exist by which a user can directly affect what another user sees on a given page (such as the hit counters found at the bottom of many HTML pages). What, then, does Java offer that is so revolutionary? It offers the capacity to perform actions while the user is viewing the same HTML page. The architecture of the Web enables users to view Web pages from around the world. However, once this page is displayed on your screen, it cannot change. In the example of the hit counters-although more users may access the given page-you are never informed of this fact unless you reload that page. Java, however, brings life into Web pages by means of applets, enabling the pages to perform actions such as communicating with a server. Java enables programmers to create applets as potent and effective as the applications on your own computer. It is this capacity that enables us to create multiuser environments. This chapter discusses the elements required to create 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. Although each of these topics is explained 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 depends heavily on the environment itself, and therefore is significantly different in each. Consequently, although 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! Sockets and Netscape With any new technology come new concerns and problems. Java is definitely not an exception to this rule. The chief concern of many has been the fact that Java applets can connect to servers from around the world and transmit information from the host that its owner might not want to make public. Although there are several solutions to this problem, Netscape has chosen to place stringent restrictions on Java socket connections for now. Currently, Java sockets can connect only to servers running on the host that served the applet. For example, an applet residing in http://www.xyz.com/page.html can connect back only to a server running on www.xyz.com. Therefore, when developing a multiuser environment as discussed in this chapter, make sure that the server to which the applet connects is running on the same host as the HTML page in which the applet resides. (See Chapter 35, "Java Security," for more details about applet security.) Our Mission-Should We Choose to Accept It 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 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.) Although this is a rather simplistic example of the power of multiuser environments, it is an excellent model for the explanation of the concepts involved. The Requirements of the Server Although it is not of our direct concern, the server in this environment plays an extremely large role. Because the server can be written in virtually any language, we won't 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 Figure 27.1, the server acts as the intermediate agent between the various users. Thus, the server must be able to do the following: Accept multiple connections from clients on a given port Receive information from the clients Send information to the clients 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, the server must keep track of the color of all the tiles. Furthermore, if a new client begins work on an in-progress quilt, it's 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 with Java. Therefore, we now move on to more exciting matters, such as the socket communication required to provide the interaction. Integrating a Communication Class in Your Applet For an effective implementation, it's 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 can 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 9, "Threads and Multithreading," for details on creating threads.) This threaded class runs continually 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. How to Connect to a Server Assuming that we have a suitable server running, the actual communication is not a very complicated process. In essence, the applet needs 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. In theory, the connect() method in the preceding class is sufficient to connect to a server on a known port. However, 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 10, "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 preceding 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 that corresponds to whether or not it was successful in connecting. How to Communicate with the Server Inasmuch as different situations require different constructs, Java supplies several options for the syntax of communication with a server. Although 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. Keep in mind that there are some nice wrapper methods in the java.io.InputStream subclasses that you may want to use to simplify the processing of the parsing involved with the communication. Sending Information Once we have established our connection, the output is then passed through the java.net.Socket.outputStream, which is a java.io.outputStream. Because OutputStream is a protected variable, however, we cannot reference it as simply as you may imagine. To access this stream, we must employ the java.net.Socket.getOutputStream() method. A simple method to send data resembles 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, communication is not 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 all parties can speak. Although this may be as simple as a specific order to a series of integers, it is nevertheless vital to the success of the environment. Protocols Although we will adopt a rather simple protocol for this applet, there are a few issues you should keep in mind when developing more complex protocols: Preface each command with a title, such as a letter or short word. For example, B123 could be the command to buy property lot 123, and S123 could be the command to sell lot 123. Make all titles of the same format. Doing so makes parsing the input stream much easier. For example, B and S are good titles. B and Sell would cause some headaches. Terminate each command with a common character. Doing so signifies the end of information and also gives you a starting point in case some of the information becomes garbled. For the sake of simplicity, the newline character (\n) is usually best. In the case of our quilt-making applet for the museum, each update packet consists of three things: the x-coordinate of the recently painted tile, the y-coordinate of the recently painted tile, and the color to be applied (which can also be represented by an integer). Additionally, we will use the newline character (\n) as our terminating character. All 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'); /* Now flush the output stream to make sure that everything is sent. */ out.flush(); } catch (Exception e) return(false); return(true); } Reading Information Another factor that complicates the reading of information from the socket is 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 runs 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. A refresher on threads Before we proceed in our development of this threaded subclass, it is important to review a key concept. As presented in Chapter 9, "Threads and Multithreading," a class that extends the definition of the java.lang.Thread class can continually execute code while other portions of the applet are running. Nevertheless, this is not true of all the methods of the threaded class. Only the run() method has the power attributed to threads (namely the capacity to run independently of other operations). This method is automatically declared in every thread, but has no functionality unless you override the original declaration in your code. Although other methods can exist within the threaded subclass, remember that only the code contained in the run() method can execute concurrently with the rest of the processes in the application. Furthermore, remember that the run() method of class foo is begun by invoking foo.start() from the master class. Also note that because we are overriding a method first declared in the java.lang.Runnable interface, we are forced to employ the original declaration: public void run(). Your code won't compile if you choose to use another declaration, such as public boolean run(). (We deal with ways of returning data in the section on sharing information, later in this chapter.) What results from all this planning is the method shown in Listing 27.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 deals with garbled input; and how the method returns information to the main applet. (You will be quizzed later!) Listing 27.1. Our method to read information from the socket. 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 spot = 0; // array // resets the counter quilt.sew(stroke[0],stroke[1],stroke[2]); } } Okay. Time is up. Here are the answers. First of all, 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 because 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 exits the while loop and runs through to its completion. Second, 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 exactly what the error will be, there is no ideal way of handling garbled information. The run() method in Listing 27.1 uses the newline character as its guide, and continues to read until it reaches one. If, however, there are nine characters before a newline character, it exits the do loop regardless and calls the sew() method. This means that the sew() 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 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.) Although we will discuss this process in more detail later, keep in mind that the last line of code simply passes the appropriate values to a method in the applet that will in turn process them: quilt.sew(stroke[0],stroke[1],stroke[2]); A note for advanced readers You will notice that in our example, all the information coming across the stream is read in the form of integers. Obviously, in the real world, other data types such as characters-or even mixed data types such as three numbers and a letter-could be passed. Although accommodating these data types would require slight alterations to the preceding code, doing so would not be a dramatic hack. Although you could deal with such a situation by having separate read() statements for each data type, remember the ASCII relationship between characters and numbers. Because the ASCII value of a number is 48 more than that number (for example, the ASCII value for '1' is 1 + 48), you can choose to read all the data in as integers and then translate certain values to their character equivalents-or vice versa, depending on how you sent the information to the server. Finally, keep in mind that serverInput is a basic java.lang.Inputstream that can be manipulated in several ways using the powers of the java.lang and java.io classes and methods. (See Chapter 12, "The Language Package," and Chapter 14, "The I/O Package," for more details on java.lang and java.io, respectively.) How to Disconnect from the Server 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. There is only one essential functionality we haven't included in our Client class thus far: the capacity to disconnect from the server. Although 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 because of hardware constraints, there is an inherent limit to the number of clients a server can have connected at the same time. Consequently, if the socket connections remain open even when the clients leave, the server eventually becomes saturated with lingering sockets. In general, it's good practice to close sockets; doing so benefits all aspects of the environment, ranging from the Internet-access software 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); } Although the code itself is nothing extraordinary, note the use of the boolean variable running (also used in the run() method). Remember that the function of the running variable is to serve as a flag that allows the client to listen to the socket stream. Inasmuch as we do not want the client to listen to the socket stream after the user has decided to disconnect, we set the variable to false in this method. Also note that the running = false statement comes before the soc.close() statement. This arrangement is made because regardless of the success of the disconnect statement, we no longer want to listen to the stream. The Graphical Interface Now that we have developed the framework of our Client subclass, let's switch gears to develop the applet class that will act as the heart of our application. Although it is 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 you create inherits various methods that enable it to respond to virtually anything 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. Responding to Input The most important aspect of a graphical interface is its capacity to monitor the user's responses to the system. As you saw in Chapter 16, "The Windowing (AWT) 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 many methods that we can use to catch and process events. These methods seize control of the applet under appropriate conditions and enable 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 will change colors and quit with the keyboard, we 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 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 can be as simple as this: public boolean keyDown(Event evt, int key) { if ( (key >= '0') && (key <= '9') ) { // if a valid key current_color = key - 48; // converts ASCII key to return(true); // numeric equivalent } else if ( key = 'Q') { leave(); // a method that will handle return(true); // cleanup } return(false); } In this method, 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 (which we have almost completed), the mouseDown() method can 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. Displaying Information Chapter 19, "Java Graphics Fundamentals," gave an overview of how to create a background and fill it in with whatever you want. In a dynamic graphical interface, however, various properties 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 we are concerned with is the colors (if any) that have 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, such as 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) paints squares with length and width equal to a final field, scale. These squares are located on the grid with respect to the initial x and y offsets. How to Thread the Client Class So 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 user's 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 cohesive application. 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 can perform two tasks at the exact same time. Note Although it may seem as if we can perform two tasks at the exact same time, this is not entirely true on a standard single-processor computer. What Java does (and other time-sharing applications such as Windows as well) is to allocate "time-slices" to each thread, allowing each thread to run for a brief moment before switching control to another thread. 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 to check for new commands before the socket connection has been opened or after it has been closed. Additionally, we want to be able to control socket events such as opening and closing in a manner that isolates them from the main applet, but also impacts the status of the run() method. Conveniently for us, we developed the Client class in such a manner: We kept 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 we have dealt with the applet itself. So 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 deal with this constructor again in the next section. Finally, note that we established the connection by means of the museum.connect() statement located in the init() method. Most likely (and in the case of the museum applet), you will want to establish the socket stream as soon as the applet starts up. The most logical place for the museum.connect() statement is in the init() method. Nevertheless, you can place this code anywhere you like in your applet. (You can, for example, have a Connect to Server button somewhere in your applet.) Caution Although giving the user the ability to initiate connection may be a good idea, be careful that you do not allow the same user to connect to the server more than once without first disconnecting. Neglecting to screen for multiple connections for the same user can lead to all sorts of headaches and improper results. How to Share Information 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 seen 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 in 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). With a little construction, we can easily solve this problem. First, we must enable the Client class to refer to the Project class; this involves 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 In this setup, the parent class is required to pass itself as an argument to the constructor method. Therefore, when we add the code to do so to our program, the syntax resembles the following: museum = new Client(this); What exactly does the preceding 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.) Although this constructor may serve other purposes, its most important function is to accept a reference to a Project class as one of its arguments. Further, the code creates a public variable, quilt, of type Project. By doing so, each method of the Client class can now reference all public methods and variables in the Project class. For example, if we had the following definitions in the Project class, the Client subclass could reference quilt.tiles_remaining and quilt.sew(): public int tiles_remaining; public void sew(int x, int y, int hue) { ... } The Translating Method Okay, time for an assessment of where we stand. The museum applet is now able to do the following: Connect to the server Monitor the user's actions Display the current design Receive and send information Create a client "within" the main applet Enable the client to reference the main applet At this point, whenever the user clicks a square, the "request to paint" is immediately sent to the server by the sendInfo() method. But 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 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 sew() method in the Project class-the sew() method has been mentioned several times in this chapter. This method serves as the bridge between the client and applet classes. Why is this approach the best? Why can't we create some public variables in the Project class that can be changed by the Client class? There are several reasons. Why Use a Translating Method? The first reason we use a translating method is that doing so makes life a great deal easier for the programmer. By employing a translating method, we can maintain the encapsulation enjoyed thus far in our application. The sew() method is contained entirely within the Project class and thus has all the necessary resources (such as private variables that would be hidden from the Client subclass). Furthermore, when we actually code the application, we can focus on each class independently. When we code the Client class, we can effectively forget how the sew() method works; 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 flexibility. If we decided to revamp this application and changed the manner in which we store 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 sew() method. The third reason for employing a translating method is that by linking the two classes with a method rather than direct access, we can 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 contains the design in the applet class) to be public, we could change the colors with a statement such as this one: 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 was changing its value? What if more data had to be stored (the color, the design, the author's 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 can 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. How to Create a Translating Method Now that we have seen why the sew() method is necessary, let us discuss its actual code. As mentioned earlier, each command in the museum application consists of three integers: the x coordinate, the y coordinate, and the new color. Also remember from the discussion of the Client class that the sew() method must be able to handle corrupted data. Consequently, the sew() method could look something like this: public void sew(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(); } } The first statement is self-explanatory, but 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-even though the user does not see anything unless the repaint() method is called. Thus, by setting the array and calling repaint(), the sew() method updates the quilt in the mind of the computer as well as on the screen itself. In a moment, we will also harness the sew() method to perform another vital function for us. Now that we have developed the entire functionality of the Client subclass, let's tie it together once and for all and put it aside. First look the final product over, as shown in Listing 27.2. Listing 27.2. The final version of the Client class. 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.sew(stroke[0],stroke[1],stroke[2]); } public boolean disconnect () { running = false; try { soc.close() } catch (Exception e) return(false); return(true); } } Although none of the methods used in Listing 27.2 are new, there is a very important piece of the class that has been completely installed for the first time-the boolean field running. As a field, it is accessible to all methods within the Client subclass. Nevertheless, by making it private, we have ensured that, if it is changed, its change will be associated with an appropriate change in the status of the socket connection-a connection or disconnection, for example. Although the Client class may require application-dependent changes, the version shown in Listing 27.2 is sufficient to satisfy most requirements. However, as of yet, we have dealt very little with the applet itself. This is primarily because each multiuser applet is inherently very different. Nevertheless, there are a few topics that should be discussed if you want to develop a quality multiuser applet. Advanced Topics of Applet Development 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. The next few sections discuss these issues as well as those regarding the development of an effective server on the other end. Animation and Dynamic Changes to the Screen Although a programmer may appreciate the intricacies of a multiuser environment, users are attracted to those applications that look nice and help them to do interesting things. Although a multiuser environment is certainly one of the latter variety, we must still keep in mind the first criteria: making the applet look nice and run smoothly. Although this chapter's 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 user's screen. In the case of the museum applet developed earlier in this chapter, every time 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. Although this process occurs in a matter of seconds, for users who have to wait three seconds to see their changes reflected, as well as users who can actually observe each tile being repainted, this 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 paint(). Although 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 graphical process can produce a very choppy effect, commonly referred to as "flickering," and can nevertheless be easily remedied. Although this requires a few changes to our applet, the main idea behind it is noticing that we do not have to repaint those items that have not changed. Even though this may seem a rather elementary observation, when developing an applet, it may 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 have to paint only 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 we have to do is modify the paint() method to paint only one tile; when the sew() method calls repaint(), only the most recently changed tile must change. There are two problems with such a simplistic approach. First of all, the paint() method is called not only 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). If we change the paint() method to paint only one tile, we may be left with only one tile on the screen, rather than the full quilt. Another problem is that we cannot pass information to the paint() method because (as with the run() method for threads) we are overriding an already created method. Don't worry. These problems can be handled easily. Peruse the additions to our code in Listing 27.3 and see whether you can determine how the two problems were solved. Listing 27.3. Revised code to provide smoother animation. 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 sew(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(); } } } Okay, let's see how the problems were solved. Most notably, we 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 tell the paint() method whether it should repaint all the tiles 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" scenario 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 don't, you'll defeat the purpose of the variable-and ruin the appearance of your applet. How did we deal with the second problem of being unable to inform the paint() method which tile has changed? Notice the use of the private variable Point Changed. Because the field is accessible by all methods, by setting the x and y values of this variable in the sew() method, we can indirectly pass this information along to the paint() method. A look inside the evolution of the language The code in Listing 27.3 employed Changed, a variable of type Point, which can be found in the java.awt package. This is a useful class, but 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. Even though 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. Ensuring that the Sockets Close Properly Although we have developed a sufficient disconnect() method, 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 discussed earlier, not closing sockets can become a serious problem in a multiuser environment. It is good practice to develop into your protocol a "close" command. Although it should resemble the other commands in your protocol, the close command need not be anything elaborate. In the museum example, if 123 were the command to paint tile (1,2) in color 3, sending -123 could be the defined close command because it would be an aberration from the rest of the commands-something easily distinguishable. Are You Still There? Another related issue is that some users may not tell the applet that they are leaving-so the disconnect() method does 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. To handle either case, it is 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 responds 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. Requests versus Commands This chapter has repeatedly referred to the information passed from each client to the server as a request that describes what the user would like as opposed to a command that defines a particular action to be taken. Furthermore, 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: It may seem more natural to have the applet update itself and then inform the server of what was done. As you will soon see, these two procedures are very necessary for the exact same reason. In the example of the museum applet, users can paint only blank (unpainted) tiles. 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 A's command is received first and user B's command is 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 in which the status is really important (or even in a game application), 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. That means that the server should be developed so 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. If two users attempt to paint the same tile, only the request of the first user should be honored, and the second request should simply be swallowed up by the server. Even better, you could enable your server to send user-specific error messages that cause some form of error message on the user's screen. Limiting and Keeping Track of Users In several situations-such as error messages-the server wants to speak with just one user. Additionally, your situation may require that only five users 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. Such a configuration may involve one central server that has the power to spawn other servers (on other ports), or a central server that acts as a "gatekeeper," accepting clients only if there are openings (see Figure 27.2). Figure 27.2 : An example of a gatekeeper system. 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 establish several two-player games managed by a central server. By designing the applet to connect on a given port (1626, for example) and to request entrance to a game, the server on that port can act as a manager by beginning a new game on another port (1627, for example) and informing the applet of the new port number. The applet can then 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 starts a new server on the next available port (1628, for example) and can continue in this manner indefinitely (see Figure 27.3). Figure 27.3 : An example of a central server/children server system. In the cases of the central server/children server system and the gatekeeper system, the server can assign each client an identification number at connection time. This arrangement not only allows the server to keep track of who is sending the information, it also enables the server to send user-specific messages (for example, Player One: You can't 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.) Summary Even though this chapter did not deal with many topics, keep in mind that the power of Java is its abstract capability to facilitate a multiuser environment, not its specific lexical constructs. Here are a few issues relating to the Java language that you should keep in mind when developing a multiuser environment: Develop a user-friendly graphical interface. By using the java.awt.Event class, you can respond to the user's interactions in an effective manner. Create a client class that extends the java.lang.Thread class. Although you can create as many methods as you want within this class, the best approach is to harness the run() method's capacity to run concurrently to develop an effective means of reading data from the stream. Also, remember to use some form of flag that provides you with a way of controlling the lifetime of the run() method. Develop the framework by which the applet and client classes will be able to communicate. This is best done by making an instance of the client class a field in the applet class. By making the appropriate methods of the client class public, your applet class can perform such tasks as connecting, disconnecting, and sending data. Create a "translating" method in your applet class. Doing so enables the client class to send the parsed data to the applet class to elicit the proper response. Contact reference@developer.com with questions or comments. Copyright 1998 EarthWeb Inc., All rights reserved. PLEASE READ THE ACCEPTABLE USAGE STATEMENT. Copyright 1998 Macmillan Computer Publishing. All rights reserved.

Wyszukiwarka

Podobne podstrony:
ch27
ch27 (13)
CH27 (11)
ch27
ch27 (4)
ch27
DK2192 CH27
ch27
ch27 (3)
ch27 (2)
CH27

więcej podobnych podstron