Java TCP Sockets and Swing Tutorialby Ashish Myles
Introduction
This tutorial is aimed for programmers with at least a little experience with
Java. For introductory material on Java, check out Sun's Java Tutorial and Java Documentation, which can be
accessed from Sun's Java home page.
Information on how to compile and run programs is also available there. This
page takes you step-by-step through compiling and running your first Java
program. For the following tutorial, make sure to download and install JDK1.2 or
JDK1.3 - also known as Java 2. The latest Java Development Kit can be downloaded
here. The following tutorial
introduces Java sockets and Swing separately before combining them into a TCP
chat program. You can download this tutorial in zip format by clicking here.
TCP Socket Programming
Introduction
Let's cover Java TCP sockets first since they are much simpler than Swing.
The beauty of Java sockets is that no knowledge whatsoever of the details of TCP
is required. TCP stands for Transmission Control Protocol and is a standard
protocol data transmission with confirmation of data reception. That's as far as
I know on the subject, and yet I managed to make a chat program.
Concept
In order to initiate a TCP session, a server and a client are required.
Firstly, a server is set up to listen at a given port. The server waits and does
nothing until a client attempts to connect that port. If everything goes fine,
the connection is successful and both the server and client have an instance of
the Socket class. From each instance of this class, an input stream
and an output stream can be obtained, and all communication is done via these
streams.
Example
The Socket class is in the java.net package, so be
sure to say import java.net.*; at the beginning of your file. The
following is a simple example that illustrates the different portions of a
server/client pair. This example works using localhost, which
corresponds to the default local computer IP address of 127.0.0.1.
This way, both the server and the client will be running on the same computer.
Server.java and Client.java contain the server and client source
code for this simple example. Here is the server code
(Server.java): import java.lang.*;
import java.io.*;
import java.net.*;
class Server {
public static void main(String args[]) {
String data = "Toobie ornaught toobie";
try {
ServerSocket srvr = new ServerSocket(1234);
Socket skt = srvr.accept();
System.out.print("Server has connected!\n");
PrintWriter out = new PrintWriter(skt.getOutputStream(), true);
System.out.print("Sending string: '" + data + "'\n");
out.print(data);
out.close();
skt.close();
srvr.close();
}
catch(Exception e) {
System.out.print("Whoops! It didn't work!\n");
}
}
}
The key portions of this program are in the try{} block. The
ServerSocket instantiation is what sets up the server to listen at
the given port. The server is automatically set up at the computer on which it
is run. The Socket instantiation on the next line uses the
accept() method of ServerSocket. This method waits
until a client attempts to connect to the server, and it returns an instance of
the Socket class. This Socket instance
(skt) is now the "warp tunnel" through which we can communicate
with the client. skt.getOutputStream() returns the output stream
through which the server can talk to the client, and
skt.getInputStream() returns the input stream through with the
server can hear the client. This example creates a PrintWriter
instance using the output stream for easier output and sends the data (stored in
data) to the client (out.print(data);). Bingo! Easy as
that! After everything is done, all the streams and sockets shuold be closed
before the program is exited. Now, let's see the client code. Here is the
client code (Client.java): import java.lang.*;
import java.io.*;
import java.net.*;
class Client {
public static void main(String args[]) {
try {
Socket skt = new Socket("localhost", 1234);
BufferedReader in = new BufferedReader(new
InputStreamReader(skt.getInputStream()));
System.out.print("Received string: '");
while (!in.ready()) {}
System.out.println(in.readLine()); // Read one line and output it
System.out.print("'\n");
in.close();
}
catch(Exception e) {
System.out.print("Whoops! It didn't work!\n");
}
}
}
Once again, the meat of the program is in the try{} block. A
connection to the server is attempted through the instantiation of the
Socket class. It attempts to contact the server at
localhost through port 1234 - the same port where the server is
listening. Once the socket is at hand, it works exactly the same as the one
obtained through the ServerSocket class in
Server.java. This time, the input stream is obtained and a
BufferedReader is instantiated using it. The data is read from this
stream and displayed to the screen. Simple yet again!
Java/Swing Programming
Introduction
Swing is a rapid GUI development tool that is part of the standard Java
development kit. It was primarily developed due to the shortcomings of the
Abstract Windows Toolkit (AWT). For example, Swing's JButton class
enhances the AWT Button class to allow not only text, but images on
the button. In addition, all Swing components support assistive technologies.
Swing Components
I am only going to go over a few basic Swing components:
If you know how to use these basic
components, using the others is simple. Usually, while making a GUI-based
application, you instantiate a JFrame and choose its layout. Then
you put one or more JPanels in the JFrame if you want
to. JPanels also have different layout options like JFrames do.
After that, you add other components. The following is the code corresponding to
what I just said. It can also be found in SimpleGui1.java.
import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class SimpleGui1 {
public static void main(String args[]) {
// Let's make a button first
JButton btn = new JButton("Click Me!");
btn.setMnemonic(KeyEvent.VK_C); // Now you can hit the button with Alt-C
// Let's make the panel with a flow layout.
// Flow layout allows the components to be
// their preferred size.
JPanel pane = new JPanel(new FlowLayout());
pane.add(btn); // Add the button to the pane
// Now for the frame
JFrame fr = new JFrame();
fr.setContentPane(pane); // Use our pane as the default pane
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Exit program when frame is closed
fr.setLocation(200, 200); // located at (200, 200)
fr.pack(); // Frame is ready. Pack it up for display.
fr.setVisible(true); // Make it visible
}
}
And here is the result: 
The JButton, JPanel, and JFrame
actually have a ton of more options you can set. Check out the Java
documentation for the whole deal. In this simple example, clicking the button
does nothing, but clicking the "X" at the top right automatically exits the
program completely.
Event Handlers
Now to put something behind this simple little GUI that we have built.
Functionality can be added to clickable GUI components through event handlers.
This is done by adding a listener object to a component. Whenever any action is
done on the component, the appropriate listeners are triggered and their
appropriate methods (event handlers) are invoked. To see how this works, let's
modify the example above: import java.lang.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class SimpleGui2 {
private static JFrame fr = null;
private static JButton btn = null;
public static void main(String args[]) {
// Let's make a button first
btn = new JButton("Click Me!");
btn.setMnemonic(KeyEvent.VK_C); // Now you can hit the button with Alt-C
btn.addActionListener(new ButtonListener()); // Allow the button to disable itself
// Let's make the panel with a flow layout.
// Flow layout allows the components to be
// their preferred size.
JPanel pane = new JPanel(new FlowLayout());
pane.add(btn); // Add the button to the pane
// Now for the frame
fr = new JFrame();
fr.setContentPane(pane); // Use our pane as the default pane
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Exit program when frame is closed
fr.setLocation(200, 200); // located at (200, 200)
fr.pack(); // Frame is ready. Pack it up for display.
fr.setVisible(true); // Make it visible
}
// Button event handler
static class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
btn.setEnabled(false); // Disable the button
}
}
}
Now this brings up the same window that the first one did. However, if you
click the button, it disables itself:
To see something a lot more complex, check out my preliminary design for the
TCP chat program: Gui.java. The GUI looks like this:
Try clicking the connect and disconnect buttons and see what happens. This
program is a lot simpler than the actual TCP chat program which you will see
later.
Thread Issues
Going from AWT to Swing results in a little issue with threads. All AWT
classes are thread safe; Swing's classes are not. A little knowledge about
threads is necessary to completely understand the reason for the complexity of
using threads. Sun's Java Tutorial covers this topic very well, so you might
want to take a peek there. Consider an object that has been instantiated in one
thread. Now this object needs to be modified by two or more threads. Data can
easily be corrupted if two threads try to modify the same object at the same
time. Of course, you know there is no such thing as "the same time" when it
comes to threads since the operating system switches between the various threads
to make them seem like they are running concurrently. However, if one thread is
interrupted while it is modifying an object and a second thread is initiated so
that it begins to modify that object, data can get corrupted.
There are a couple of ways to get around this problem in Java. Java allows a
way to enforce that no thread modifies the same object that your thread is
modifying through the use of the synchronize keyword. You can read
up about it in Sun's Java tutorial since it is out of the scope of this
tutorial. Synchronizing an object before modifying it blocks any other threads
attempting to access the synchronized object until your
synchronize block is done. AWT's methods synchronize
on their corresponding object instantiation, which prevents the objects (eg.
buttons, panels, etc.) on the screen from being corrupted while they are being
drawn. However, this can block your thread if it is trying to access the AWT
object. This just slows things down. For more information on threads and
synchronization, click here.
Swing and Threads
Swing, as I mentioned above, is not thread-safe. This means that most of its
methods do not synchronize with the internal state of the Swing object. Only a
few functions that are marked as thread-safe in the Java documentation are safe
to execute from anywhere. The programmer is now given the responsibility of
making sure that there are no thread conflicts. This can be done by making sure
that all the modifications to the Swing objects happen in the same thread. All
the events - such as button clicks and others - are handled on the thread known
as the event-handling thread. This thread is also where all the component
painting onto the screen is done. Hence, any modifications to the visible Swing
components can be made in any event handling routine. For example, in the
SimpleGui2 class above, the properties of the button are modified
in the ButtonListener class, whose method is executed on the
event-handling thread every time the button is clicked.
Now, what if you have to modify an object without receiving any events? Swing
allows you to run a class that implements the Runnable interface on
the event-handling thread. This is done using the
SwingUtilities.invokeLater(runnableObj); method. For example,
runnableObj is an instantiation of a class that implements the
Runnable interface.
SwingUtilities.invokeLater(runnableObj); will queue the
runnableObj.run() method on the event-handling queue, and the
method will eventually execute and be able to modify the Swing components. If
you see the TCPChat code, you will see that that is exactly what I
have done to update the GUI with the state of the program. For more information
and examples with Swing and threads, click here.
TCP Chat Program
The chat program is in TCPChat.java. It implements both the client and
the server side, which is not too hard, since both use the Socket
class. It would take much too long to detail every portion of the program, so I
will just tell you how to use it, and you can check out the code to see how its
done.
Firstly, this is what the final product looks like:
When two people want to chat using this program, each has to have an instance
of it running on his/her computer. This program will also work with two
instances on the same computer through localhost, as mentioned
above in the TCP Sockets section. To use the chatting program, do the following:
- Select the host option on one and the guest option on the other. Make sure
that the port numbers are the same.
- For the guest, type in the IP address/name of the computer running the
host in the Host IP text field.
- Now, click the "Connect" button on the host followed by the "Connect"
button on the guest. If everything goes fine, both should be connected.
- Type the outgoing messages in the text field right below the text area on
the right. Any incoming/outgoing messages will displayed in the text area.
- To disconnect, click the "Disconnect" button.
- To exit, click the "X" on the top-right of the window.
Links and More Information
All of my information is directly from Sun's Java 2 Documentation, and Java
Tutorial, both of which are accessible at Sun's Java home page. Here is a list
of useful links:
|