Objectives
In this chapter you’ll learn:
• To understand Java networking with URLs, sockets and datagrams.
• To implement Java networking applications by using sockets and datagrams.
• To understand how to implement Java clients and servers that communicate with one another.
• To understand how to implement network-based collaborative applications.
• To construct a multithreaded server.
If the presence of electricity can be made visible in any part of a circuit, I see no reason why intelligence may not be transmitted instantaneously by electricity.
—Samuel F. B. Morse
Protocol is everything.
—Francois Giuliani
What networks of railroads, highways and canals were in another age, the networks of telecommunications, information and computerization ... are today.
—Bruno Kreisky
The port is near, the bells I hear, the people all exulting.
—Walt Whitman
Outline
19.1 Introduction
19.2 Manipulating URLs
19.3 Reading a File on a Web Server
19.4 Establishing a Simple Server Using Stream Sockets
19.5 Establishing a Simple Client Using Stream Sockets
19.6 Client/Server Interaction with Stream Socket Connections
19.7 Connectionless Client/Server Interaction with Datagrams
19.8 Client/Server Tic-Tac-Toe Using a Multithreaded Server
19.9 Security and the Network
19.10 [Web Bonus] Case Study: DeitelMessenger Server and Client
19.11 Wrap-Up
There is much excitement about the Internet and the World Wide Web. The Internet ties the information world together. The World Wide Web makes the Internet easy to use and gives it the flair and sizzle of multimedia. Organizations see the Internet and the web as crucial to their information-systems strategies. Java provides a number of built-in networking capabilities that make it easy to develop Internet-based and web-based applications. Java can enable programs to search the world for information and to collaborate with programs running on other computers internationally, nationally or just within an organization. Java can enable applets and applications to communicate with one another (subject to security constraints).
Java’s fundamental networking capabilities are declared by classes and interfaces of package java.net
, through which Java offers stream-based communications that enable applications to view networking as streams of data. The classes and interfaces of package java.net
also offer packet-based communications for transmitting individual packets of information—commonly used to transmit audio and video over the Internet. In this chapter, we show how to create and manipulate sockets and how to communicate with packets and streams of data.
Our discussion of networking focuses on both sides of the client/server relationship. The client requests that some action be performed, and the server performs the action and responds to the client. A common implementation of the request-response model is between web browsers and web servers. When a user selects a website to browse through a browser (the client application), a request is sent to the appropriate web server (the server application). The server normally responds to the client by sending an appropriate HTML web page.
We introduce Java’s socket-based communications, which enable applications to view networking as if it were file I/O—a program can read from a socket or write to a socket as simply as reading from a file or writing to a file. The socket is simply a software construct that represents one endpoint of a connection. We show how to create and manipulate stream sockets and datagram sockets.
With stream sockets, a process establishes a connection to another process. While the connection is in place, data flows between the processes in continuous streams. Stream sockets are said to provide a connection-oriented service. The protocol used for transmission is the popular TCP (Transmission Control Protocol).
With datagram sockets, individual packets of information are transmitted. This is not appropriate for everyday programmers, because the protocol used—UDP, the User Datagram Protocol—is a connectionless service, and thus does not guarantee that packets arrive in any particular order. With UDP, packets can even be lost or duplicated. Significant extra programming is required on your part to deal with these problems (if you choose to do so). UDP is most appropriate for network applications that do not require the error checking and reliability of TCP. Stream sockets and the TCP protocol will be more desirable for the vast majority of Java programmers.
Performance Tip 19.1
Connectionless services generally offer greater performance but less reliability than connection-oriented services.
Portability Tip 19.1
TCP, UDP and related protocols enable a great variety of heterogeneous computer systems (i.e., computer systems with different processors and different operating systems) to intercommunicate.
We also introduce a case study in which we implement a client/server chat application similar to the instant-messaging services popular on the web today. This case study is provided as a web bonus at www.deitel.com/books/javafp/. The application incorporates many networking techniques introduced in this chapter. It also introduces multicasting, in which a server can publish information and clients can subscribe to that information. Each time the server publishes more information, all subscribers receive it. Throughout the examples of this chapter, we’ll see that many of the networking details are handled by the Java APIs.
The Internet offers many protocols. The Hypertext Transfer Protocol (HTTP), which forms the basis of the World Wide Web, uses URIs (Uniform Resource Identifiers) to identify data on the Internet. URIs that specify the locations of documents are called URLs (Uniform Resource Locators). Common URLs refer to files or directories and can reference objects that perform complex tasks, such as database lookups and Internet searches. If you know the HTTP URL of a publicly available HTML document anywhere on the web, you can access it through HTTP.
Java makes it easy to manipulate URLs. When you use a URL that refers to the exact location of a resource (e.g., a web page) as an argument to the showDocument
method of interface AppletContext
, the browser in which the applet is executing will display that resource. The applet in Figs. 19.1–19.2 demonstrates simple networking capabilities. It enables the user to select a web page from a JList
and causes the browser to display the corresponding page. In this example, the networking is performed by the browser.
Fig. 19.1. HTML document to load SiteSelector
applet.
Fig. 19.2. Loading a document from a URL into a browser.
This applet takes advantage of applet parameters specified in the HTML document that invokes the applet. When browsing the World Wide Web, you’ll often come across applets that are in the public domain—you can use them free of charge on your own web pages (normally in exchange for crediting the applet’s creator). Many applets can be customized via parameters supplied from the HTML file that invokes the applet. For example, Fig. 19.1 contains the HTML that invokes the applet SiteSelector
in Fig. 19.2.
The HTML document contains eight parameters specified with the param
tag—these lines must appear between the starting and ending applet
tags. The applet can read these values and use them to customize itself. Any number of param
tags can appear between the starting and ending applet
tags. Each parameter has a name
and a value
. Applet
method getParameter
retrieves the value
associated with a specific parameter name and returns it as a string. The argument passed to getParameter
is a string containing the name of the parameter in the param
element. In this example, parameters represent the title and location of each website the user can select. Parameters specified for this applet are named title
#, where the value of # starts at 0
and increments by 1 for each new title. Each title should have a corresponding location parameter of the form location
#, where the value of # starts at 0
and increments by 1 for each new location. The statement
String title = getParameter( "title0" );
gets the value associated with parameter "title0"
and assigns it to reference title
. If there is no param
tag containing the specified parameter, getParameter
returns null
.
The applet (Fig. 19.2) obtains from the HTML document (Fig. 19.1) the choices that will be displayed in the applet’s JList
. Class SiteSelector
uses a HashMap
(package java.util
) to store the website names and URLs. In this example, the key is the string in the JList
that represents the website name, and the value is a URL
object that stores the location of the website to display in the browser.
Class SiteSelector
also contains an ArrayList
(package java.util
) in which the site names are placed so that they can be used to initialize the JList
(one version of the JList
constructor receives an array of Object
s which is returned by ArrayList
’s toArray
method). An ArrayList
is a dynamically resizable array of references. Class ArrayList
provides method add
to add a new element to the end of the ArrayList
. (We provide discussions of classes ArrayList
and HashMap
in Chapter 16.)
Lines 25–26 in the applet’s init
method (lines 23–57) create a HashMap
object and an ArrayList
object. Line 29 calls our utility method getSitesFromHTMLParameters
(declared at lines 60–89) to obtain the HTML parameters from the HTML document that invoked the applet.
Method getSitesFromHTMLParameters
uses Applet
method getParameter
(line 67) to obtain a website title. If the title
is not null
, the loop in lines 70–88 begins executing. Line 73 uses Applet
method getParameter
to obtain the website location. Line 77 uses the location
as the value of a new URL
object. The URL
constructor determines whether its argument represents a valid URL. If not, the URL
constructor throws a MalformedURLException
. Note that the URL
constructor must be called in a try
block. If the URL
constructor generates a MalformedURLException
, the call to printStackTrace
(line 83) causes the program to output a stack trace to the Java console. On Windows machines, the Java console can be viewed by right clicking the Java icon in the notification area of the taskbar. Then the program attempts to obtain the next website title. The program does not add the site for the invalid URL to the HashMap
, so the title will not be displayed in the JList
.
For a proper URL
, line 78 places the title
and URL
into the HashMap
, and line 79 adds the title
to the ArrayList
. Line 87 gets the next title from the HTML document. When the call to getParameter
at line 87 returns null
, the loop terminates.
When method getSitesFromHTMLParameters
returns to init
, lines 32–56 construct the applet’s GUI. Line 32 adds the JLabel
“Choose a site to browse
” to the NORTH
of the JFrame
’s BorderLayout
. Line 34 creates JList siteChooser
to allow the user to select a web page to view. Lines 35–54 register a ListSelectionListener
to handle the siteChooser
’s events. Line 56 adds siteChooser
to the CENTER
of the BorderLayout
.
When the user selects one of the websites listed in siteChooser
, the program calls method valueChanged
(lines 39–52). Line 42 obtains the selected site name from the JList
. Line 45 passes the selected site name (the key) to HashMap
method get
, which locates and returns a reference to the corresponding URL
object (the value) that is assigned to reference newDocument
.
Line 48 uses Applet
method getAppletContext
to get a reference to an AppletContext
object that represents the applet container. Line 51 uses the AppletContext
reference browser
to invoke method showDocument
, which receives a URL
object as an argument and passes it to the AppletContext
(i.e., the browser). The browser displays in the current browser window the World Wide Web resource associated with that URL
. In this example, all the resources are HTML documents.
For programmers familiar with HTML frames, there is a second version of AppletContext
method showDocument
that enables an applet to specify the so-called target frame in which to display the web resource. This second version takes two arguments—a URL
object specifying the resource to display and a string representing the target frame. There are some special target frames that can be used as the second argument. The target frame _blank
results in a new web browser window to display the content from the specified URL. The target frame _self
specifies that the content from the specified URL should be displayed in the same frame as the applet (the applet’s HTML page is replaced in this case). The target frame _top
specifies that the browser should remove the current frames in the browser window, then display the content from the specified URL in the current window. [Note: If you are interested in learning more about HTML, please visit our Resource Centers on extensible hypertext markup language (XHTML; www.deitel.com/xhtml/) and the web page formatting capability called Cascading Style Sheets (CSS; www.deitel.com/css21/).]
Error-Prevention Tip 19.1
The applet in Fig. 19.2 must be run from a web browser, such as Mozilla or Microsoft Internet Explorer, to see the results of displaying another web page. The appletviewer
is capable only of executing applets—it ignores all other HTML tags. If the websites in the program contained Java applets, only those applets would appear in the appletviewer
when the user selected a website. Each applet would execute in a separate appletviewer
window.
Our next example once again hides the networking details from us. The application in Fig. 19.3 uses Swing GUI component JEditorPane
(from package javax.swing
) to display the contents of a file on a web server. The user enters a URL in the JTextField
at the top of the window, and the application displays the corresponding document (if it exists) in the JEditorPane
. Class JEditorPane
is able to render both plain text and HTML-formatted text, as illustrated in the two screen captures (Fig. 19.4), so this application acts as a simple web browser. The application also demonstrates how to process HyperlinkEvent
s when the user clicks a hyperlink in the HTML document. The techniques shown in this example can also be used in applets. However, an applet is allowed to read files only on the server from which it was downloaded.
Fig. 19.3. Reading a file by opening a connection through a URL.
Fig. 19.4. Test class for ReadServerFile
.
The application class ReadServerFile
contains JTextField enterField
, in which the user enters the URL of the file to read and JEditorPane contentsArea
to display the contents of the file. When the user presses the Enter key in enterField
, the application calls method actionPerformed
(lines 31–34). Line 33 uses ActionEvent
method getActionCommand
to get the string the user input in the JTextField
and passes the string to utility method getThePage
(lines 61–74).
Line 65 invokes JEditorPane
method setPage
to download the document specified by location
and display it in the JEditorPane
. If there is an error downloading the document, method setPage
throws an IOException
. Also, if an invalid URL is specified, a MalformedURLException
(a subclass of IOException
) occurs. If the document loads successfully, line 66 displays the current location in enterField
.
Typically, an HTML document contains hyperlinks—text, images or GUI components which, when clicked, provide quick access to another document on the web. If a JEditorPane
contains an HTML document and the user clicks a hyperlink, the JEditorPane
generates a HyperlinkEvent
(package javax.swing.event
) and notifies all registered HyperlinkListener
s (package javax.swing.event
) of that event. Lines 42–53 register a HyperlinkListener
to handle HyperlinkEvent
s. When a HyperlinkEvent
occurs, the program calls method hyperlinkUpdate
(lines 46–51). Lines 48–49 use HyperlinkEvent
method getEventType
to determine the type of the HyperlinkEvent
. Class HyperlinkEvent
contains a public
nested class called EventType
that declares three static EventType
objects, which represent the hyperlink event types. ACTIVATED
indicates that the user clicked a hyperlink to change web pages, ENTERED
indicates that the user moved the mouse over a hyperlink and EXITED
indicates that the user moved the mouse away from a hyperlink. If a hyperlink was ACTIVATED
, line 50 uses HyperlinkEvent
method getURL
to obtain the URL
represented by the hyperlink. Method toString
converts the returned URL
to a string that can be passed to utility method getThePage
.
Look-and-Feel Observation 19.1
A JEditorPane
generates HyperlinkEvent
s only if it is uneditable.
The two examples discussed so far use high-level Java networking capabilities to communicate between applications. In the examples, it was not your responsibility to establish the connection between a client and a server. The first program relied on the web browser to communicate with a web server. The second program relied on a JEditorPane
to perform the connection. This section begins our discussion of creating your own applications that can communicate with one another.
Establishing a simple server in Java requires five steps. Step 1 is to create a ServerSocket
object. A call to the ServerSocket
constructor, such as
ServerSocket server = new ServerSocket( portNumber, queueLength );
registers an available TCP port number and specifies the maximum number of clients that can wait to connect to the server (i.e., the queue length). The port number is used by clients to locate the server application on the server computer. This is often called the handshake point. If the queue is full, the server refuses client connections. The constructor establishes the port where the server waits for connections from clients—a process known as binding the server to the port. Each client will ask to connect to the server on this port. Only one application at a time can be bound to a specific port on the server.
Software Engineering Observation 19.1
Port numbers can be between 0 and 65,535. Most operating systems reserve port numbers below 1024 for system services (e.g., e-mail and World Wide Web servers). Generally, these ports should not be specified as connection ports in user programs. In fact, some operating systems require special access privileges to bind to port numbers below 1024.
Programs manage each client connection with a Socket
object. In Step 2, the server listens indefinitely (or blocks) for an attempt by a client to connect. To listen for a client connection, the program calls ServerSocket
method accept
, as in
Socket connection = server.accept();
which returns a Socket
when a connection with a client is established. The Socket
allows the server to interact with the client. The interactions with the client actually occur at a different server port from the handshake point. This allows the port specified in Step 1 to be used again in a multithreaded server to accept another client connection. We demonstrate this concept in Section 19.8.
Step 3 is to get the OutputStream
and InputStream
objects that enable the server to communicate with the client by sending and receiving bytes. The server sends information to the client via an OutputStream
and receives information from the client via an InputStream
. The server invokes method getOutputStream
on the Socket
to get a reference to the Socket
’s OutputStream
and invokes method getInputStream
on the Socket
to get a reference to the Socket
’s InputStream
.
The stream objects can be used to send or receive individual bytes or sequences of bytes with the OutputStream
’s method write
and the InputStream
’s method read
, respectively. Often it is useful to send or receive values of primitive types (e.g., int
and double
) or Serializable
objects (e.g., String
s or other serializable types) rather than sending bytes. In this case, we can use the techniques discussed in Chapter 14 to wrap other stream types (e.g., ObjectOutputStream
and ObjectInputStream
) around the OutputStream
and InputStream
associated with the Socket
. For example,
ObjectInputStream input =
new ObjectInputStream( connection.getInputStream() );
ObjectOutputStream output =
new ObjectOutputStream( connection.getOutputStream() );
The beauty of establishing these relationships is that whatever the server writes to the ObjectOutputStream
is sent via the OutputStream
and is available at the client’s InputStream
, and whatever the client writes to its OutputStream
(with a corresponding ObjectOutputStream
) is available via the server’s InputStream
. The transmission of the data over the network is seamless and is handled completely by Java.
Step 4 is the processing phase, in which the server and the client communicate via the OutputStream
and InputStream
objects. In Step 5, when the transmission is complete, the server closes the connection by invoking the close
method on the streams and on the Socket
.
Software Engineering Observation 19.2
With sockets, network I/O appears to Java programs to be similar to sequential file I/O. Sockets hide much of the complexity of network programming from the programmer.
Software Engineering Observation 19.3
With Java’s multithreading, we can create multithreaded servers that can manage many simultaneous connections with many clients. This multithreaded-server architecture is precisely what popular network servers use.
Software Engineering Observation 19.4
A multithreaded server can take the Socket
returned by each call to accept
and create a new thread that manages network I/O across that Socket
. Alternatively, a multithreaded server can maintain a pool of threads (a set of already existing threads) ready to manage network I/O across the new Socket
s as they are created. See Chapter 18 for more information on multithreading.
Performance Tip 19.2
In high-performance systems in which memory is abundant, a multithreaded server can be implemented to create a pool of threads that can be assigned quickly to handle network I/O across each new Socket
as it is created. Thus, when the server receives a connection, it need not incur the overhead of thread creation. When the connection is closed, the thread is returned to the pool for reuse.
Establishing a simple client in Java requires four steps. In Step 1, we create a Socket
to connect to the server. The Socket
constructor establishes the connection to the server. For example, the statement
Socket connection = new Socket( serverAddress, port );
uses the Socket
constructor with two arguments—the server’s address (serverAddress) and the port number. If the connection attempt is successful, this statement returns a Socket
. A connection attempt that fails throws an instance of a subclass of IOException
, so many programs simply catch IOException
. An UnknownHostException
occurs specifically when the system is unable to resolve the server address specified in the call to the Socket
constructor to a corresponding IP address.
In Step 2, the client uses Socket
methods getInputStream
and getOutputStream
to obtain references to the Socket
’s InputStream
and OutputStream
. As we mentioned in the preceding section, we can use the techniques of Chapter 14 to wrap other stream types around the InputStream
and OutputStream
associated with the Socket
. If the server is sending information in the form of actual types, the client should receive the information in the same format. Thus, if the server sends values with an ObjectOutputStream
, the client should read those values with an ObjectInputStream
.
Step 3 is the processing phase in which the client and the server communicate via the InputStream
and OutputStream
objects. In Step 4, the client closes the connection when the transmission is complete by invoking the close
method on the streams and on the Socket
. The client must determine when the server is finished sending information so that it can call close
to close the Socket
connection. For example, the InputStream
method read
returns the value –1 when it detects end-of-stream (also called EOF—end-of-file). If an ObjectInputStream
is used to read information from the server, an EOFException
occurs when the client attempts to read a value from a stream on which end-of-stream is detected.
Figures 19.5 and 19.7 use stream sockets to demonstrate a simple client/server chat application. The server waits for a client connection attempt. When a client connects to the server, the server application sends the client a String
object (recall that String
s are Serializable
objects) indicating that the connection was successful. Then the client displays the message. The client and server applications each provide textfields that allow the user to type a message and send it to the other application. When the client or the server sends the string "TERMINATE"
, the connection terminates. Then the server waits for the next client to connect. The declaration of class Server
appears in Fig. 19.5. The declaration of class Client
appears in Fig. 19.7. The screen captures showing the execution between the client and the server are shown as part of Fig. 19.7.
Fig. 19.5. Server portion of a client/server stream-socket connection.
Server
ClassServer
’s constructor (lines 30–55) creates the server’s GUI, which contains a JTextField
and a JTextArea
. Server
displays its output in the JTextArea
. When the main
method (lines 7–12 of Fig. 19.6) executes, it creates a Server
object, specifies the window’s default close operation and calls method runServer
(declared at lines 58–87).
Fig. 19.6. Test class for Server
.
Method runServer
sets up the server to receive a connection and processes one connection at a time. Line 62 creates a ServerSocket
called server
to wait for connections. The ServerSocket
listens for a connection from a client at port 12345
. The second argument to the constructor is the number of connections that can wait in a queue to connect to the server (100
in this example). If the queue is full when a client attempts to connect, the server refuses the connection.
Common Programming Error 19.1
Specifying a port that is already in use or specifying an invalid port number when creating a ServerSocket
results in a BindException
.
Line 68 calls method waitForConnection
(declared at lines 90–96) to wait for a client connection. After the connection is established, line 69 calls method getStreams
(declared at lines 99–109) to obtain references to the streams for the connection. Line 70 calls method processConnection
(declared at lines 112–133) to send the initial connection message to the client and to process all messages received from the client. The finally
block (lines 76–80) terminates the client connection by calling method closeConnection
(lines 136–151) even if an exception occurs. Method displayMessage
(lines 169–180) is called from these methods to use the event dispatch thread to display messages in the application’s JTextArea
.
Method waitForConnection
(lines 90–96) uses ServerSocket
method accept
(line 93) to wait for a connection from a client. When a connection occurs, the resulting Socket
is assigned to connection
. Method accept
blocks until a connection is received (i.e., the thread in which accept
is called stops executing until a client connects). Lines 94–95 output the host name of the computer that made the connection. Socket
method getInetAddress
returns an InetAddress
(package java.net
) containing information about the client computer. InetAddress
method getHostName
returns the host name of the client computer. For example, there is a special IP address (127.0.0.1
) and host name (localhost
) that is useful for testing networking applications on your local computer (this is also known as the loopback address). If getHostName
is called on an InetAddress
containing 127.0.0.1
, the corresponding host name returned by the method would be localhost
.
Method getStreams
(lines 99–109) obtains the Socket
’s streams and uses them to initialize an ObjectOutputStream
(line 102) and an ObjectInputStream
(line 106), respectively. Note the call to ObjectOutputStream
method flush
at line 103. This statement causes the ObjectOutputStream
on the server to send a stream header to the corresponding client’s ObjectInputStream
. The stream header contains such information as the version of object serialization being used to send objects. This information is required by the ObjectInputStream
so that it can prepare to receive those objects correctly.
Software Engineering Observation 19.5
When using an ObjectOutputStream
and ObjectInputStream
to send and receive data over a network connection, always create the ObjectOutputStream
first and flush
the stream so that the client’s ObjectInputStream
can prepare to receive the data. This is required only for networking applications that communicate using ObjectOutputStream
and ObjectInputStream
.
Performance Tip 19.3
A computer’s input and output components are typically much slower than its memory. Output buffers typically are used to increase the efficiency of an application by sending larger amounts of data fewer times, thus reducing the number of times an application accesses the computer’s input and output components.
Line 114 of method processConnection
(lines 112–133) calls method sendData
to send "SERVER>>> Connection successful"
as a string to the client. The loop at lines 120–132 executes until the server receives the message "CLIENT>>> TERMINATE"
. Line 124 uses ObjectInputStream
method readObject
to read a String
from the client. Line 125 invokes method displayMessage
to append the message to the JTextArea
.
When the transmission is complete, method processConnection
returns, and the program calls method closeConnection
(lines 136–151) to close the streams associated with the Socket
and close the Socket
. Then the server waits for the next connection attempt from a client by continuing with line 68 at the beginning of the while
loop.
When the user of the server application enters a string in the text field and presses the Enter key, the program calls method actionPerformed
(lines 40–44), which reads the string from the text field and calls utility method sendData
(lines 154–166) to send the string to the client. Method sendData
writes the object, flushes the output buffer and appends the same string to the textarea in the server window. It is not necessary to invoke displayMessage
to modify the textarea here, because method sendData
is called from an event handler—thus, sendData
executes as part of the event-dispatch thread.
Note that Server
receives a connection, processes it, closes it and waits for the next connection. A more likely scenario would be a Server
that receives a connection, sets it up to be processed as a separate thread of execution, then immediately waits for new connections. The separate threads that process existing connections can continue to execute while the Server
concentrates on new connection requests. This makes the server more efficient, because multiple client requests can be processed concurrently. We demonstrate a multithreaded server in Section 19.8.
Client
ClassLike class Server
, class Client
’s (Fig. 19.7) constructor (lines 29–56) creates the GUI of the application (a JTextField
and a JTextArea
). Client
displays its output in the text-area. When method main
(lines 7–19 of Fig. 19.8) executes, it creates an instance of class Client
, specifies the window’s default close operation and calls method runClient
(declared at lines 59–79). In this example, you can execute the client from any computer on the Internet and specify the IP address or host name of the server computer as a command-line argument to the program. For example, the command
java Client 192.168.1.15
attempts to connect to the Server
on the computer with IP address 192.168.1.15
.
Fig. 19.7. Client portion of a stream-socket connection between client and server.
Fig. 19.8. Class that tests the Client
.
Client
method runClient
(lines 59–79) sets up the connection to the server, processes messages received from the server and closes the connection when communication is complete. Line 63 calls method connectToServer
(declared at lines 82–92) to perform the connection. After connecting, line 64 calls method getStreams
(declared at lines 95–105) to obtain references to the Socket
’s stream objects. Then line 65 calls method processConnection
(declared at lines 108–126) to receive and display messages sent from the server. The finally
block (lines 75–78) calls closeConnection
(lines 129–144) to close the streams and the Socket
even if an exception has occurred. Method displayMessage
(lines 162–173) is called from these methods to use the event-dispatch thread to display messages in the application’s textarea.
Method connectToServer
(lines 82–92) creates a Socket
called client
(line 87) to establish a connection. The method passes two arguments to the Socket
constructor—the IP address of the server computer and the port number (12345) where the server application is awaiting client connections. In the first argument, InetAddress static
method getByName
returns an InetAddress
object containing the IP address specified as a command-line argument to the application (or 127.0.0.1
if no command-line arguments are specified). Method getByName
can receive a string containing either the actual IP address or the host name of the server. The first argument also could have been written other ways. For the localhost address 127.0.0.1, the first argument could be specified with either of the following expressions:
InetAddress.getByName( "localhost" )
InetAddress.getLocalHost()
Also, there are versions of the Socket
constructor that receive a string for the IP address or host name. The first argument could have been specified as "127.0.0.1"
or "localhost"
. We chose to demonstrate the client/server relationship by connecting between applications executing on the same computer (localhost
). Normally, this first argument would be the IP address of another computer. The InetAddress
object for another computer can be obtained by specifying the computer’s IP address or host name as the argument to InetAddress
method getByName
. The Socket
constructor’s second argument is the server port number. This must match the port number at which the server is waiting for connections (called the handshake point). Once the connection is made, lines 90–91 display a message in the text area indicating the name of the server computer to which the client has connected.
The Client
uses an ObjectOutputStream
to send data to the server and an ObjectInputStream
to receive data from the server. Method getStreams
(lines 95–105) creates the ObjectOutputStream
and ObjectInputStream
objects that use the streams associated with the client
socket.
Method processConnection
(lines 108–126) contains a loop that executes until the client receives the message "SERVER>>> TERMINATE"
. Line 117 reads a String
object from the server. Line 118 invokes displayMessage
to append the message to the textarea.
When the transmission is complete, method closeConnection
(lines 129–144) closes the streams and the Socket
.
When the user of the client application enters a string in the text field and presses the Enter key, the program calls method actionPerformed
(lines 41–45) to read the string and invoke utility method sendData
(147–159) to send the string to the server. Method sendData
writes the object, flushes the output buffer and appends the same string to the JTextArea
in the client window. Once again, it is not necessary to invoke utility method displayMessage
to modify the textarea here, because method sendData
is called from an event handler.
We have been discussing connection-oriented, streams-based transmission. Now we consider connectionless transmission with datagrams.
Connection-oriented transmission is like the telephone system in which you dial and are given a connection to the telephone of the person with whom you wish to communicate. The connection is maintained for the duration of the call, even when you are not talking.
Connectionless transmission with datagrams is more like the way the postal service carries mail. If a large message will not fit in one envelope, you break it into separate message pieces that you place in separate, sequentially numbered envelopes. Each letter is then mailed at the same time. The letters could arrive in order, out of order or not at all (though rare, it does happen). The person at the receiving end reassembles the message pieces into sequential order before attempting to make sense of the message. If your message is small enough to fit in one envelope, you need not worry about the “out-of-sequence” problem, but it is still possible that your message might not arrive. One difference between datagrams and postal mail is that duplicates of datagrams can arrive at the receiving computer.
Figures 19.9–19.12 use datagrams to send packets of information via the User Datagram Protocol (UDP) between a client application and a server application. In the Client
application (Fig. 19.11), the user types a message into a text field and presses Enter. The program converts the message into a byte
array and places it in a datagram packet that is sent to the server. The Server
(Fig. 19.9) receives the packet and displays the information in it, then echoes the packet back to the client. Upon receiving the packet, the client displays the information it contains.
Fig. 19.9. Server side of connectionless client/server computing with datagrams.
Fig. 19.10. Class that tests the Server
.
Server
window after packet of data is received from Client
Fig. 19.11. Client side of connectionless client/server computing with datagrams.
Fig. 19.12. Class that tests the Client
.
Client
window after sending packet to Server
and receiving packet back from Server
Server
ClassClass Server
(Fig. 19.9) declares two DatagramPacket
s that the server uses to send and receive information and one DatagramSocket
that sends and receives the packets. The Server
constructor (lines 19–37) creates the graphical user interface in which the packets of information will be displayed. Line 30 creates the DatagramSocket
in a try
block. Line 30 uses the DatagramSocket
constructor that takes an integer port number argument (5000
in this example) to bind the server to a port where it can receive packets from clients. Client
s sending packets to this Server
specify the same port number in the packets they send. A SocketException
is thrown if the DatagramSocket
constructor fails to bind the DatagramSocket
to the specified port.
Common Programming Error 19.2
Specifying a port that is already in use or specifying an invalid port number when creating a DatagramSocket
results in a SocketException
.
Server
method waitForPackets
(lines 40–68) uses an infinite loop to wait for packets to arrive at the Server
. Lines 47–48 create a DatagramPacket
in which a received packet of information can be stored. The DatagramPacket
constructor for this purpose receives two arguments—a byte
array in which the data will be stored and the length of the array. Line 50 uses DatagramSocket
method receive
to wait for a packet to arrive at the Server
. Method receive
blocks until a packet arrives, then stores the packet in its DatagramPacket
argument. The method throws an IOException
if an error occurs while receiving a packet.
When a packet arrives, lines 53–58 call method displayMessage
(declared at lines 86–97) to append the packet’s contents to the textarea. DatagramPacket
method getAddress
(line 54) returns an InetAddress
object containing the host name of the computer from which the packet was sent. Method getPort
(line 55) returns an integer specifying the port number through which the host computer sent the packet. Method getLength
(line 56) returns an integer representing the number of bytes of data sent. Method getData
(line 57) returns a byte
array containing the data. Lines 57–58 initialize a String
object using a three-argument constructor that takes a byte
array, the offset and the length. This String
is then appended to the text to display.
After displaying a packet, line 60 calls method sendPacketToClient
(declared at lines 71–83) to create a new packet and send it to the client. Lines 77–79 create a DatagramPacket
and pass four arguments to its constructor. The first argument specifies the byte
array to send. The second argument specifies the number of bytes to send. The third argument specifies the client computer’s Internet address, to which the packet will be sent. The fourth argument specifies the port where the client is waiting to receive packets. Line 81 sends the packet over the network. Method send
of DatagramSocket
throws an IOException
if an error occurs while sending a packet.
Client
ClassClass Client
(Fig. 19.11) works similarly to class Server
, except that the Client
sends packets only when the user types a message in a text field and presses the Enter key. When this occurs, the program calls method actionPerformed
(lines 32–57), which converts the string the user entered into a byte
array (line 41). Lines 44–45 create a DatagramPacket
and initialize it with the byte
array, the length of the string that was entered by the user, the IP address to which the packet is to be sent (InetAddress.getLocalHost()
in this example) and the port number at which the Server
is waiting for packets (5000 in this example). Line 47 sends the packet. Note that the client in this example must know that the server is receiving packets at port 5000—otherwise, the server will not receive the packets.
Note that the DatagramSocket
constructor call (line 71) in this application does not specify any arguments. This no-argument constructor allows the computer to select the next available port number for the DatagramSocket
. The client does not need a specific port number, because the server receives the client’s port number as part of each DatagramPacket
sent by the client. Thus, the server can send packets back to the same computer and port number from which it receives a packet of information.
Client
method waitForPackets
(lines 81–107) uses an infinite loop to wait for packets from the server. Line 91 blocks until a packet arrives. This does not prevent the user from sending a packet, because the GUI events are handled in the event-dispatch thread. It only prevents the while
loop from continuing until a packet arrives at the Client
. When a packet arrives, line 91 stores it in receivePacket
, and lines 94–99 call method displayMessage
(declared at lines 110–121) to display the packet’s contents in the textarea.
In this section, we present the popular game Tic-Tac-Toe implemented by using client/server techniques with stream sockets. The program consists of a TicTacToeServer
application (Figs. 19.13–19.14) that allows two TicTacToeClient
applications (Figs. 19.15–19.16) to connect to the server and play Tic-Tac-Toe. Sample outputs are shown in Fig. 19.17.
Fig. 19.13. Server side of the client/server Tic-Tac-Toe program.
Fig. 19.14. Class that tests the Tic-Tac-Toe server.
Fig. 19.15. Client side of the client/server Tic-Tac-Toe program.
Fig. 19.16. Test class for the Tic-Tac-Toe client.
Fig. 19.17. Sample outputs from the client/server Tic-Tac-Toe program.
As the TicTacToeServer
receives each client connection, it creates an instance of inner-class Player
(lines 182–301 of Fig. 19.13) to process the client in a separate thread. These threads enable the clients to play the game independently. The first client to connect to the server is player X and the second is player O. Player X moves first. The server maintains the board information so it can determine whether a player’s move is valid or invalid.
We begin with a discussion of the server side of the Tic-Tac-Toe game. When the TicTacToeServer
application executes, the main
method (lines 7–12 of Fig. 19.14) creates a TicTacToeServer
object called application
. The constructor (lines 34–69 of Fig. 19.13) attempts to set up a ServerSocket
. If successful, the program displays the server window, then main
invokes the TicTacToeServer
method execute
(lines 72–100). Method execute
loops twice, blocking at line 79 each time while waiting for a client connection. When a client connects, line 79 creates a new Player
object to manage the connection as a separate thread, and line 80 executes the Player
in the runGame
thread pool.
When the TicTacToeServer
creates a Player
, the Player
constructor (lines 192–208) receives the Socket
object representing the connection to the client and gets the associated input and output streams. Line 201 creates a Formatter
(see Chapter 24) by wrapping it around the output stream of the socket. The Player
’s run
method (lines 219–297) controls the information that is sent to and received from the client. First, it passes to the client the character that the client will place on the board when a move is made (line 225). Line 226 calls Formatter
method flush
to force this output to the client. Line 241 suspends player X’s thread as it starts executing, because player X can move only after player O connects.
After player O connects, the game can be played, and the run
method begins executing its while
statement (lines 264–283). Each iteration of this loop reads an integer (line 269) representing the location where the client wants to place a mark, and line 272 invokes the TicTacToeServer
method validateAndMove
(declared at lines 118–163) to check the move. If the move is valid, line 275 sends a message to the client to this effect. If not, line 280 sends a message indicating that the move was invalid. The program maintains board locations as numbers from 0 to 8 (0 through 2 for the first row, 3 through 5 for the second row and 6 through 8 for the third row).
Method validateAndMove
(lines 118–163 in class TicTacToeServer
) allows only one player at a time to move, thereby preventing them from modifying the state information of the game simultaneously. If the Player
attempting to validate a move is not the current player (i.e., the one allowed to make a move), it is placed in a wait state until its turn to move. If the position for the move being validated is already occupied on the board, validMove
returns false
. Otherwise, the server places a mark for the player in its local representation of the board (line 142), notifies the other Player
object (line 146) that a move has been made (so that the client can be sent a message), invokes method signal
(line 152) so that the waiting Player
(if there is one) can validate a move and returns true
(line 159) to indicate that the move is valid.
TicTacToeClient
ClassEach TicTacToeClient
application (Fig. 19.15) maintains its own GUI version of the Tic-Tac-Toe board on which it displays the state of the game. The clients can place a mark only in an empty square on the board. Inner class Square
(lines 205–262 of Fig. 19.15) implements each of the nine squares on the board. When a TicTacToeClient
begins execution, it creates a JTextArea
in which messages from the server and a representation of the board using nine Square
objects are displayed. The startClient
method (lines 80–100) opens a connection to the server and gets the associated input and output streams from the Socket
object. Lines 85–86 make a connection to the server. Class TicTacToeClient
implements interface Runnable
so that a separate thread can read messages from the server. This approach enables the user to interact with the board (in the event dispatch thread) while waiting for messages from the server. After establishing the connection to the server, line 99 executes the client with the worker ExecutorService
. The run
method (lines 103–124) controls the separate thread of execution. The method first reads the mark character (X or O) from the server (line 105), then loops continuously (lines 121–125) and reads messages from the server (line 124). Each message is passed to the processMessage
method (lines 129–156) for processing.
If the message received is "Valid move."
, lines 134–135 display the message "Valid move, please wait."
and call method setMark
(lines 173–184) to set the client’s mark in the current square (the one in which the user clicked), using SwingUtilities
method invokeLater
to ensure that the GUI updates occur in the event dispatch thread. If the message received is "Invalid move, try again."
, line 139 displays the message so that the user can click a different square. If the message received is "Opponent moved."
, line 145 reads an integer from the server indicating where the opponent moved, and lines 149–150 place a mark in that square of the board (again using SwingUtilities
method invokeLater
to ensure that the GUI updates occur in the event dispatch thread). If any other message is received, line 155 simply displays the message. Figure 19.17 shows sample screen captures of two applications interacting via the TicTacToeServer
.
As much as we look forward to writing a great variety of powerful network-based applications, our efforts may be limited because of security concerns. Many web browsers, such as Mozilla and Microsoft Internet Explorer, by default prohibit Java applets from doing file processing on the machines on which they execute. Think about it. A Java applet is designed to be sent to your browser via an HTML document that could be downloaded from any web server in the world. Often you’ll know very little about the sources of Java applets that will execute on your system. To allow these applets free rein with your files could be disastrous.
A more subtle situation occurs with limiting the machines to which executing applets can make network connections. To build truly collaborative applications, we would ideally like to have our applets communicate with machines almost anywhere. The Java security manager in a web browser often restricts an applet so that it can communicate only with the machine from which it was originally downloaded.
These restrictions may seem too strict. However, the Java Security API now provides capabilities for digitally signed applets that will enable browsers to determine whether an applet is downloaded from a trusted source. A trusted applet can be given additional access to the computer on which it is executing.
[Note: This case study is available at www.deitel.com/books/javafp/.] Chat rooms have become common on the Internet. They provide a central location where users can chat with each other via short text messages. Each participant can see all messages that the other users post, and each user can post messages. This case study integrates many of the Java networking, multithreading and Swing GUI features you have learned thus far to build an online chat system. We also introduce multicasting, which enables an application to send DatagramPacket
s to groups of clients.
The DeitelMessenger case study is a significant application that uses many intermediate Java features, such as networking with Socket
s, DatagramPacket
s and MulticastSocket
s, multithreading and Swing GUI. The case study also demonstrates good software engineering practices by separating interface from implementation, enabling developers to support different network protocols and provide different user interfaces. After reading this case study, you’ll be able to build more significant networking applications.
In this chapter, you have learned the basics of network programming in Java. You learned two different methods of sending data over a network: streams-based networking using TCP/IP and datagrams-based networking using UDP. In the next chapter, you’ll learn basic database concepts, how to interact with data in a database using SQL and how to use JDBC to allow Java applications to manipulate database data.