CPSC 225, Spring 2019
Lab 12: NetDraw

In this lab, you will use the Socket class to implement a network connection, and you will use a thread to read incoming messages from the Socket. The program that you will work on is "NetDraw." Your starting point is a program that lets the user draw shapes and stamp small images onto a Canvas. It is similar to the drawing program from Lab 6. You will add the networking feature. Once that is done, the program will be able to connect to a NetDrawServer. Anything drawn in one program that is connected to the server will also appear in the Canvases of all of the other programs that are connected to the same program.

You should add the contents of the folder /classes/cs225/lab12-files to your project. This includes two Java files, NetDraw.java and SimpleDialogs.java. There is also a folder, stamps, which contains the small images that the user can stamp onto the canvas. You should add the entire stamps folder to the source folder of your project. (Note that you can download a .zip archive, lab12-files.zip, that contains copies of the files that you need.)

Your work for the eCardDesigner project from Lab 10 and Lab 11 should be turned in by 1:30 tomorrow (Wednesday, April 24). It should be turned into a folder named lab11 in your homework folder.

Your work for today's lab is due next week. You should turn it into a folder named lab12 in your homework folder. It should be in that folder no later than 10:00 AM next Thursday, May 2.

There will not be a new lab assignment next week. The lab period next week is meant for working on your final project. Remember that your final project is officially due on the last day of class, Monday, May 6. However, I will not actually look for it for printing until the afternoon of Tuesday, May 7. All files that are needed for compiling and running your program should be turned into a folder named final in your homework folder.

Running NetDraw

The original version of NetDraw is a stand-alone program that lets the user create images. You will add networking to the program, but for that to work, NetDraw must be able to connect to a server. The server is defined by the program NetDrawServer.java.

During the lab today, a slightly modified version of the server is running on the computer at IP address 172.21.7.12. This version automatically sends a random draw command to each connected computer every five seconds (to make sure that commands are always being broadcast by the server as you work on your program).

If you would like to try a completed version of the NetDraw program, you can run the executable jar file, NetDrawComplete.jar. There is a copy available in /classes/cs225. If you are working on a lab computer, you can run that copy with the following command in a Terminal:

java  -jar  /classes/cs225/NetDrawComplete.jar

You will find a "Connect" command in the "Control" menu. When you select that command, a dialog box will pop up asking you for the host name or IP address of the server. Enter the IP address 172.21.7.12 and click "OK". (If you are working on your own computer, it must be connected to the HWS Private Network for this to work.) Every five seconds, a random shape or stamp sent by the server will appear in the program window. If other users are connected to the same server, anything that they draw in their programs will also be drawn in yours.


You can also run a server program on your computer. One way to do that is to add the source code file, NetDrawServer.java, to your Eclipse project and run it there. Another is to run the executable jar file NetDrawServer.jar, which you can do in a Terminal on a lab computer with the command

java  -jar  /classes/cs225/NetDrawServer.jar

You can connect a NetDraw program to a server running on the same machine by using localhost as the host name, or 127.0.0.1 as the IP address, of the computer that you want to connect to. Both localhost and 127.0.0.1 are ways of referring to the computer that you are using. You can run two or more copies of NetDraw and connect them both to a server on the same computer. As you work on completing the program, you will probably need to do that to test it.

It is also possible for other people to connect to a server running on your computer, as long as they know the IP address of your computer. To find your computer's IP address, you can use the ifconfig command on Mac OS or Linux, or the ipconfig command on Windows. You need to find the IP address in the output from the command; ask for help if you need it. Note that most computers on campus get their IP addresses from a server when they start up, and that IP address can change. Also, when on the HWS network, both your computer and the one that connects to your computer must be on the HWS Private Network.

The NetDraw Protocol

There is a very strict protocol for communication between a NetDraw program and a NetDraw server. For your program to work successfully, it must follow the protocol exactly.

Each message sent from the client (that is, your NetDraw program) to the server, or from the server to the client, consists of one line of text, terminated by an end-of-line. That is, it is something that can be easily written by the println() method of a PrintWriter and read by the nextLine() method of a Scanner.

The server listens on port number 35053. As soon as the connection is made, the client (that is, your program) must send a line of text containing the string NetDraw. If the client does not send this string, the server will close the connection. The client should then read a line of text from the server, which will also be the string NetDraw. This initial exchange of strings acts as a "handshake" to allow each side of the connection to check that program on the other end of the connection is the right kind of program.

The connected client can then send messages to the server. If the message is valid, a copy of the message is sent to every other client that is connected to the server. (Note that a copy is not sent back to the client that sent the message to the server.) If a client sends an invalid message, the server responds to that client with a message that starts with "illegal message: ", followed by the invalid message. The server then closes the connection.

After the initial handshake, every message from a client must begin with one of the following words: "stamp", "text", "line", "rect", "oval", "roundrect", "filledrect", "filledoval", "filledroundrect" (not including the quotation marks!). Strings are case-sensitive. The word is followed by any numeric data for the message. All numbers are of type double. The items in the message are separated by white space (spaces and/or tabs). For "stamp" and "text" messages, the numbers in the message are followed by a string. Data for the messages is as follows:

Note that for numbers, the server checks that the number is there, but does not check that the value makes sense. For example, RGB color components should be in the range 0.0 to 1.0, but the server does not verify that.

Opening a Connection

You can begin work on NetDraw by adding a "Connect" command to the "Control" menu. When the user selects this command, your program should ask the user for the host name or IP address of the server. You can use SimpleDialogs.prompt() to ask the question. Note that this method returns null if the user cancels the dialog; in that case, you should not open a connection. Also, you should probably count an empty string as canceling the request. If the user does want to make the connection, you should open a connection to the server that they specified.

However, you should not open the connection while running on the GUI thread. Instead, you should make a new thread. That thread can open the connection, and it can also be responsible for reading incoming messages from the server. To define the thread, you can use the following nested class. The host parameter for the constructor will be the host name or IP address specified by the user. You will have to write the run() method:

private class NetReader extends Thread {
    String host;
    NetReader(String host) {
        this.host = host;
        setDaemon(true);
    }
    public void run() {
    }
}

The thread should open the connection, create a PrintWriter and Scanner to be used for communication with the server, and send and receive the handshake string. It can then go into an infinite loop in which it reads incoming messages from the server and applies them to imageGraphics. Note that the Scanner can be a local variable, but the PrintWriter should be a global variable in the NetDraw class, because it will be used elsewhere in the program for sending messages to the server.

Of course, errors can occur when working with the network, and you will have to catch those errors. It is advisable to use a try statement with a finally clause and to use the finally clause to close the Socket that represents the connection, if the socket was successfully created.

As discussed in class yesterday, you should not directly manipulate the JavaFX GUI in any way from any thread other than the GUI thread. Instead, code executed on the NetReader thread must use Platform.runLater() to make any changes to the GUI. This includes drawing to imageGraphics, changing the text on the message label, showing a message dialog box, or anything else! The parameter to Platform.runLater() is a Runnable, often given as a lambda expression. For example,

Platform.runLater( () -> message.setText("Connected to server.") );

Receiving Messages

After opening the connection and exchanging handshakes, the NetReader thread runs in an infinite loop in which it reads lines of text from the server and interprets them as drawing commands. The commands can be assumed to follow the NetDraw protocol that is described above.

You will need to parse the incoming message. The easiest way to do that is probably with a Scanner. Recall that if you construct a Scanner from a String, then the scanner will actually read data from that string. You will be able to use the scanner's next(), nextDouble(), and nextLine() methods to get the data that you need from the message. Using that data, you should draw the items specified by the message into the imageGraphics graphics context.

I suggest that you start by implementing the "stamp" message, which is the most simple. Once you get that working, you can work on the other types of message. Or you might want to work on sending some messages before you complete work on all the incoming messages.

Sending Messages

You can send a message to the server simply by calling the println() method in the PrintWriter created from the Socket's output stream. Or, you might prefer to use printf(), but if you do use printf, make sure to add an end-of-line at the end of the message. And don't forget to call the PrintWriter's flush() method immediately after the println or printf, to make sure that the message is actually sent on its way over the network!

You should send a message to the server at any point in the program where something is drawn to the imageGraphics graphics context. This is done in mousePressed() for "text" and "stamp" commands and in mouseReleased() for all the other commands. You should not send a message to the server when a shape is simply being drawn temporarily to the overlayGraphics graphics context.

Note that messages do not need to be sent by a separate thread. You can send the messages in the mouse event handler methods, even though they run on the GUI thread. This is because sending small amounts of data over the network should not ordinarily block.

Remember that the messages that you send to the server must follow the protocol. If you send an invalid message to the server, your connection will be shut off. That might seem drastic, but it should help you debug the message-sending aspect of your program. Before closing the connection, the server sends one final message that starts with "illegal message" and that includes the text of the invalid message that your program sent to the server.