The network
is the soul of Java. Most of what is new and exciting about Java
centers around the potential for new kinds of dynamic, networked
applications. In this chapter, we’ll start our discussion of
the java.net
package, which contains the fundamental
classes for communications and working with networked resources.
We’ll also talk about the java.rmi
package,
which provides Java’s powerful, high-level, Remote Method
Invocation facilities.
The classes of java.net
fall into two categories:
the sockets API and tools for working with Uniform Resource Locators
(URLs). Figure 11.1 shows the
java.net
package.
Java’s sockets API provides access to the standard network protocols used for communications between hosts on the Internet. Sockets are the mechanism underlying all other kinds of portable networked communications. Sockets are a low-level tool—you can use sockets for any kind of communications between client and server server or peer applications on the Net, but you have to implement your own application-level protocols for handling and interpreting the data. Higher-level networking tools, like remote method invocation and other distributed object systems, are implemented on top of sockets.
Java remote method invocation (RMI) is a powerful tool that leverages Java object serialization, allowing you to transparently work with objects on remote machines as if they were local. With RMI it is easy to write distributed applications in which clients and servers work with each other’s data as full-fledged Java objects, rather than streams or packets of data.
In this chapter, we’ll provide some simple and practical
examples of Java network programming at both levels, using sockets
and RMI. In the next chapter, we’ll look at the other half of
the java.net
package, which works with URLs,
content handlers, and protocol handlers; we’ll also cover
servlets, which allow you to write application components for web
servers.
Sockets are a low-level programming interface for networked communications. They send streams of data between applications that may or may not be on the same host. Sockets originated in BSD Unix and are, in other languages, hairy and complicated things with lots of small parts that can break off and choke little children. The reason for this is that most socket APIs can be used with almost any kind of underlying network protocol. Since the protocols that transport data across the network can have radically different features, the socket interface can be quite complex.[37]
Java supports a simplified object-oriented interface to sockets that makes network communications considerably easier. If you have done network programming using sockets in C or another structured language, you should be pleasantly surprised at how simple things can be when objects encapsulate the gory details. If this is the first time you’ve come across sockets, you’ll find that talking to another application over the network can be as simple as reading a file or getting user input from a terminal. Most forms of I/O in Java, including most network I/O, use the stream classes described in Chapter 10. Streams provide a unified I/O interface; reading or writing across the Internet is similar to reading or writing a file on the local system.
Java provides different kinds of sockets to support three different
distinct classes of underlying protocols. In this first section,
we’ll look at Java’s basic Socket
class, which uses a connection-oriented
protocol. A connection-oriented protocol gives you the equivalent of
a telephone conversation; after establishing a connection, two
applications can send data back and forth—the connection stays
in place even when no one is talking. The protocol ensures that no
data is lost and that whatever you send always arrives in order that
you sent it. In the next section, we’ll look at the
DatagramSocket
class, which uses a
connectionless
protocol. A
connectionless protocol is more like the postal service. Applications
can send short messages to each other, but no end-to-end connection
is set up in advance and no attempt is made to keep the messages in
order. It is not even guaranteed that the messages will arrive at
all. A
MulticastSocket
is a variation of a
DatagramSocket
that can be used to send data to
multiple recipients (multicasting). Working with multicast sockets is
very much like working with datagram sockets. However, multicasting
is not widely supported across the Internet at this time, so we will
not cover it here.
Again, in theory, just about any protocol family can be used
underneath the socket layer: Novell’s IPX, Apple’s
AppleTalk, even the old ChaosNet protocols. But in practice,
there’s only one protocol family people care about on the
Internet, and only one protocol family Java supports: the
Internet Protocol, IP. The
Socket
class speaks TCP, and the
DatagramSocket
class speaks UDP, both standard
Internet protocols. These protocols are generally available on any
system that is connected to the Internet.
When writing network applications, it’s common to talk about clients and servers. The distinction is increasingly vague, but the side that initiates the conversation is usually considered the client . The side that accepts the request to talk is usually the server . In the case where there are two peer applications using sockets to talk, the distinction is less important, but for simplicity we’ll use this definition.
For
our purposes, the most important difference between a client and a
server is that a client can create a socket to initiate a
conversation with a server application at any time, while a server
must prepare to listen for incoming conversations in advance. The
java.net.Socket
class represents one side of an
individual socket connection on both the client and server. In
addition, the server uses the
java.net.ServerSocket
class to listen for connections from
clients. An application (or thread) acting as a server creates a
ServerSocket
object and waits, blocked in a call
to its accept( )
method, until a connection
arrives. When it does, the accept( )
method
creates a Socket
object the server uses to
communicate with the client. A server may carry on conversations with
multiple clients at once; in this case there will still be only a
single ServerSocket
but the server will have
multiple Socket
objects—one associated with
each client, as shown in Figure 11.2.
A client needs two pieces of information to locate and connect to another server on the Internet: a hostname (used to find the host’s network address) and a port number. The port number is an identifier that differentiates between multiple clients or servers on the same host. A server application listens on a prearranged port while waiting for connections. Clients select the port number assigned to the service they want to access. If you think of the host computers as hotels and the applications as guests, then the ports are like the guests’ room numbers. For one person to call another, he or she must know the other party’s hotel name and room number.
A client application opens a connection to a server by constructing a
Socket
that specifies the hostname and port
number of the desired server:
try { Socket sock = new Socket("wupost.wustl.edu", 25); } catch ( UnknownHostException e ) { System.out.println("Can't find host."); } catch ( IOException e ) { System.out.println("Error connecting to host."); }
This
code fragment attempts to connect a Socket
to port
25 (the SMTP mail service) of the host
wupost.wustl.edu. The client handles the
possibility that the hostname can’t be resolved
(UnknownHostException
) and that it might not be
able to connect to it (IOException
). As an
alternative to using a
hostname, you can provide
a string version of the host’s IP address:
Socket sock = new Socket("22.66.89.167", 25);
Once a connection is made, input and
output streams can be retrieved with the Socket
getInputStream( )
and getOutputStream( )
methods. The following (rather arbitrary) code sends and receives
some data with the streams:
try { Socket server = new Socket("foo.bar.com", 1234); InputStream in = server.getInputStream( ); OutputStream out = server.getOutputStream( ); // write a byte out.write(42); // write a newline or carriage return delimited string PrintWriter pout = new PrintWriter( out, true ); pout.println("Hello!"); // read a byte byte back = (byte)in.read( ); // read a newline or carriage return delimited string BufferedReader bin = new BufferedReader( new InputStreamReader( in ) ); String response = bin.readLine( ); // send a serialized Java object ObjectOutputStream oout = new ObjectOutputStream( out ); oout.writeObject( new java.util.Date( ) ); oout.flush( ); server.close( ); } catch (IOException e ) { ... }
In this exchange, the client first creates a
Socket
for communicating with the server. The
Socket
constructor specifies the server’s
hostname ( foo.bar.com) and a prearranged port
number (1234). Once the connection is established, the client writes
a single byte to the server using the
OutputStream
’s
write( )
method. It then wraps a
PrintWriter
around the
OutputStream
in order to send a string of text
more easily. Next, it performs the complementary operations: reading
a byte from the server using
InputStream
’s
read( )
and then creating a
BufferedReader
from which to get a full string of
text. Finally, we do something really funky and send a
serialized
Java object to the server, using an
ObjectOutputStream
. (We’ll talk in depth about
sending serialized objects later in this chapter.) The client then
terminates the connection with the close( )
method. All these operations have the potential to generate
IOException
s; the catch
clause
is where our application would deal with
these.
After a connection
is established, a server application uses the same kind of
Socket
object for its side of the communications.
However, to accept a connection from a client, it must first create a
ServerSocket
, bound to the correct port.
Let’s re-create the previous conversation from the
server’s point of
view:
// meanwhile, on foo.bar.com... try { ServerSocket listener = new ServerSocket( 1234 ); while ( !finished ) { Socket client = listener.accept( ); // wait for connection InputStream in = client.getInputStream( ); OutputStream out = client.getOutputStream( ); // read a byte byte someByte = (byte)in.read( ); // read a newline or carriage-return-delimited string BufferedReader bin = new BufferedReader( new InputStreamReader( in ) ); String someString = bin.readLine( ); // write a byte out.write(43); // say goodbye PrintWriter pout = new PrintWriter( out, true ); pout.println("Goodbye!"); // read a serialized Java object ObjectInputStream oin = new ObjectInputStream( in ); Date date = (Date)oin.readObject( ); client.close( ); } listener.close( ); } catch (IOException e ) { ... } catch (ClassNotFoundException e2 ) { ... }
First, our server creates a ServerSocket
attached
to port 1234. On some systems, there are rules about what
ports an application can use. Port
numbers below 1024 are usually reserved for system processes and
standard, well-known services, so we pick a port number outside of
this range. The ServerSocket
need be created only
once; thereafter we can accept as many connections as arrive.
Next we enter a loop, waiting for the accept( )
method of the
ServerSocket
to return an active
Socket
connection from a client. When a connection
has been established, we perform the server side of our dialog, then
close the connection and return to the top of the loop to wait for
another connection. Finally, when the server application wants to
stop listening for connections altogether, it calls the
close( )
method of the
ServerSocket
.
This server is single-threaded; it handles one connection at a time,
not calling accept( )
to listen for a new
connection until it’s finished with the current connection. A
more realistic server would have a loop that accepts connections
concurrently and passes them off to their own threads for processing.
(Our tiny web server example later in this chapter will do just
this.)
The previous examples presuppose that the
client has permission to connect to the server, and that the server
is allowed to listen on the specified socket. This is not always the
case. Specifically,
applets and other untrusted applications
run under the auspices of a SecurityManager
that
can impose arbitrary restrictions on what hosts they may or may not
talk to, and whether or not they can listen for connections.
The security policy imposed on
applets by the
SDK
appletviewer
and the current version of Netscape
allows untrusted applets to open socket connections only to the host
that served them. That is, they can talk back only to the server from
which their class files were retrieved. Untrusted applets are not
allowed to open server sockets themselves. Now, this doesn’t
mean that an untrusted applet can’t cooperate with its server
to communicate with anyone, anywhere. The applet’s server could
run a proxy
that lets the applet communicate indirectly with anyone it likes.
What this security policy prevents is malicious applets roaming
around inside corporate firewalls, making connections to trusted
services. It places the burden of security on the originating server,
and not the client machine. Restricting access to the originating
server limits the usefulness of “trojan” applications
that do annoying things from the client side. (You probably
won’t let your proxy mail-bomb people, because you’ll be
blamed.)
While fully trusted code and applications that are run without any
security manager can perform any kind of activities, the default
security policy that comes with SDK 1.2 and later dissallows most
network access. So if you are going to run your application under the
default
security manager (using the
-Djava.security.manager
option on the command line
or by manually installing the security manager within your
application) you will have to modify the policy file to grant the
appropriate permissions to your code. (See Section 3.2 in Chapter 3.)
The following policy file fragment sets the socket permissions to allow connections to or from any host, on any nonprivileged port:
grant { permission java.net.SocketPermission "*:1024-", "listen,accept,connect"; };
When starting the Java interpreter, you can install the security
manager and use this file (call it
mysecurity.policy
):
java -Djava.security.manager -Djava.security.policy=mysecurity.policy MyApplication
Many networked workstations run a time
service that dispenses their local clock time on a well-known port.
This was a precursor of NTP, the more general
Network Time Protocol. In the
next example, DateAtHost
, we’ll make a
specialized subclass of java.util.Date
that
fetches the time from a remote host instead of initializing itself
from the local clock. (See Chapter 9, for a
complete discussion of the Date
class.)
DateAtHost
connects to the time service (port 37)
and reads four bytes representing the time on the remote host. These
four bytes are interpreted as an integer representing the number of
seconds since the beginning of the 20th century.
DateAtHost
converts this to Java’s variant
of the absolute time (milliseconds since January 1, 1970, a date that
should be familiar to Unix users). The conversion first creates a
long
value, which is the unsigned equivalent of
the integer time
. It subtracts an offset to make
the time relative to the epoch ( January 1, 1970) rather than the
century, and multiplies by 1000 to convert to milliseconds. It then
uses the converted time to initialize itself:
//file: DateAtHost.java import java.net.Socket; import java.io.*; public class DateAtHost extends java.util.Date { static int timePort = 37; // seconds from start of 20th century to Jan 1, 1970 00:00 GMT static final long offset = 2208988800L; public DateAtHost( String host ) throws IOException { this( host, timePort ); } public DateAtHost( String host, int port ) throws IOException { Socket server = new Socket( host, port ); DataInputStream din = new DataInputStream( server.getInputStream( ) ); int time = din.readInt( ); server.close( ); setTime( (((1L << 32) + time) - offset) * 1000 ); } }
That’s all there is to it. It’s not very long, even with
a few frills. We have supplied two possible constructors for
DateAtHost
. Normally we’d expect to use the
first, which simply takes the name of the remote host as an argument.
The second constructor specifies the hostname and the port number of
the remote time service. (If the time service were running on a
nonstandard port, we would use the second constructor to specify the
alternate port number.) This second constructor does the work of
making the connection and setting the time. The first constructor
simply invokes the second (using the this( )
construct) with the default port as an argument. Supplying simplified
constructors that invoke their siblings with default arguments is a
common and useful technique; that is the only reason we’ve
shown it here.
The second constructor opens a socket to the specified port on the
remote host. It creates a DataInputStream
to wrap
the input stream and then reads a four-byte integer using the
readInt( )
method. It’s no coincidence that
the bytes are in the right
order. Java’s
DataInputStream
and
DataOutputStream
classes work with the bytes of
integer types in network byte order (most
significant to least significant). The time protocol (and other
standard network protocols that deal with binary data) also uses the
network byte order, so we don’t need to call any conversion
routines. Explicit data conversions would probably be necessary if we
were using a nonstandard protocol, especially when talking to a
non-Java client or server. In that case we’d have to read byte
by byte and do some rearranging to get our four-byte value. After
reading the data, we’re finished with the socket, so we close
it, terminating the connection to the server. Finally, the
constructor initializes the rest of the object by calling
Date
’s setTime( )
method
with the calculated time value.
The DateAtHost
class can work with a time
retrieved from a remote host almost as easily as
Date
is used with the time on the local host. The
only additional overhead is that we have to deal with the possible
IOException
that can be thrown by the
DateAtHost
constructor:
try { Date d = new DateAtHost( "sura.net" ); System.out.println( "The time over there is: " + d ); } catch ( IOException e ) { ... }
This example fetches the time at the host sura.net and prints its value.
Have you ever wanted your very own
web server? Well, you’re in luck.
In this section, we’re going to build
TinyHttpd
, a minimal but functional HTTP daemon.
TinyHttpd
listens on a specified port and services
simple HTTP “get file” requests. They look something like
this:
GET /path
/filename
[optional stuff
]
Your web browser sends one or more of these requests for each
document it retrieves from a web server. Upon reading a request, our
server will attempt to open the specified file and send its contents.
If that document contains references to images or other items to be
displayed inline, the browser continues with additional
GET
requests. For best performance
TinyHttpd
services each request in its own thread.
Therefore, TinyHttpd
can service several requests
concurrently.
Over and above the limitations imposed by its simplicity,
TinyHttpd
suffers from the limitations imposed by
the fickleness of filesystem access in Java. It’s important to
remember that file pathnames are still somewhat
architecture-dependent—as is the concept of a filesystem to
begin with. This example should work, as is, on Unix and DOS-like
systems, but may require some customizations to account for
differences on other platforms. It’s possible to write slightly
more elaborate code that uses the environmental information provided
by Java to tailor itself to the local system. (Chapter 10 gives some hints about how.)
Warning
Unless run with the security manager, the next example will serve files from your host without protection. Don’t try this at work.
Now, without further ado, here’s TinyHttpd
:
//file: TinyHttpd.java import java.net.*; import java.io.*; import java.util.*; public class TinyHttpd { public static void main( String argv[] ) throws IOException { ServerSocket ss = new ServerSocket( Integer.parseInt(argv[0]) ); while ( true ) new TinyHttpdConnection( ss.accept() ).start( ); } } // end of class TinyHttpd class TinyHttpdConnection extends Thread { Socket client; TinyHttpdConnection ( Socket client ) throws SocketException { this.client = client; setPriority( NORM_PRIORITY - 1 ); } public void run( ) { try { BufferedReader in = new BufferedReader( new InputStreamReader(client.getInputStream( ), "8859_1" )); OutputStream out = client.getOutputStream( ); PrintWriter pout = new PrintWriter( new OutputStreamWriter(out, "8859_1"), true ); String request = in.readLine( ); System.out.println( "Request: "+request ); StringTokenizer st = new StringTokenizer( request ); if ( (st.countTokens( ) >= 2) && st.nextToken( ).equals("GET") ) { if ( (request = st.nextToken( )).startsWith("/") ) request = request.substring( 1 ); if ( request.endsWith("/") || request.equals("") ) request = request + "index.html"; try { FileInputStream fis = new FileInputStream ( request ); byte [] data = new byte [ fis.available( ) ]; fis.read( data ); out.write( data ); out.flush( ); } catch ( FileNotFoundException e ) { pout.println( "404 Object Not Found" ); } } else pout.println( "400 Bad Request" ); client.close( ); } catch ( IOException e ) { System.out.println( "I/O error " + e ); } } }
Compile TinyHttpd
and place it in your class path,
as described in Chapter 3. Go to a directory with
some interesting documents and start the daemon, specifying an unused
port number as an argument. For example:
% java TinyHttpd 1234
You should now be able to use your web browser to retrieve files from your host. You’ll have to specify the port number you chose in the URL. For example, if your hostname is foo.bar.com, and you started the server as shown, you could reference a file as in:
http://foo.bar.com:1234/welcome.html
TinyHttpd
looks for files relative to its current
directory, so the pathnames you provide should be relative to that
location. Retrieved some files? (Did you notice that when you
retrieved an HTML file your web browser automatically generated more
requests for items like images that were contained within it?)
Let’s take a closer look.
The TinyHttpd
application has two classes. The
public TinyHttpd
class contains the main( )
method of our standalone application. It begins by
creating a ServerSocket
, attached to the specified
port. It then loops, waiting for client connections and creating
instances of the second class, a
TinyHttpdConnection
thread, to service each
request. The while
loop waits for the
ServerSocket
accept( )
method
to return a new Socket
for each client connection.
The Socket
is passed as an argument to construct
the TinyHttpdConnection
thread that handles it.
TinyHttpdConnection
is a subclass of
Thread
. It lives long enough to process one client
connection and then dies.
TinyHttpdConnection
’s constructor does two
things. After saving the Socket
argument for its
caller, it adjusts its priority. By lowering its priority to
NORM_PRIORITY-1
( just below the default
priority), we ensure that the threads servicing established
connections won’t block TinyHttpd
’s
main thread from accepting new requests. (On a time-slicing system,
this is less important.) After our object is constructed, its
start( )
method is invoked to bring the
run( )
method to life.
The body of TinyHttpdConnection
’s
run( )
method is where all the magic happens.
First, we fetch an OutputStream
for talking back
to our client. The second line reads the GET
request from the InputStream
into the variable
req
. This request is a single newline-terminated
String
that looks like the GET
request we described earlier. For this we use a
BufferedInputStream
wrapped around an
InputStreamReader
. (We’ll say more about the
InputStreamReader
in a moment.)
We then parse the contents of req
to extract a
filename. The next few lines are a brief exercise in string
manipulation. We create a StringTokenizer
and make
sure there are at least two tokens. Using nextToken( )
, we take the first token and make sure it’s the
word GET
. (If both conditions aren’t met, we
have an error.) Then we take the next token (which should be a
filename), assign it to req
, and check whether it
begins with a forward slash. If so, we use substring( )
to strip the first character, giving us a filename
relative to the current directory. If it doesn’t begin with a
forward slash, the filename is already relative to the current
directory. Finally, we check to see if the requested filename looks
like a directory name (i.e., ends in a slash) or is empty. In these
cases, we append the familiar default filename
index.html as a convenience.
Once we have the filename, we try to open the specified file and load
its contents into a large byte array. If all goes well, we write the
data out to the client on the OutputStream
. If we
can’t parse the request or the file doesn’t exist, we
wrap our OutputStream
with a
PrintStream
to make it easier to send a textual
message. Then we return an appropriate HTTP error message. Finally,
we close the socket and return from run( )
,
removing our Thread
.
In TinyHttpd
, we explicitly created the
InputStreamReader
for our
BufferedRead
and the
OutputStreamWriter
for our
PrintWriter
. We do this so that we can specify the
character encoding to use when
converting to and from the byte representation of the HTTP protocol
messages. (Note that we’re not talking about the body of the
file we will be sending—that is simply a stream of raw bytes to
us; rather we’re talking here about the GET
and response messages.) If we didn’t specify, we’d get
the default character encoding for the local system. For many
purposes that may be correct, but in this case we are speaking of a
well-defined international protocol, and we should be specific. The
RFC for HTTP specifies that web clients and servers should use the
ISO8859-1 character encoding. We specify this encoding explicitly
when we construct the InputStreamReader
and
OutputStreamWriter
. Now as it turns out, ISO8859-1
is just plain ASCII and conversion to and from Unicode should always
leave ASCII values unchanged, so again we would probably not be in
any trouble if we we did not specify an encoding. But it’s
important to think about these things at least once—and now you
have.
An important problem with
TinyHttpd
is that there are no restrictions on the
files it will serve. With a little trickery, the daemon will happily
send any file in your filesystem to the client. It would be nice if
we could enforce the restriction that TinyHttpd
serve only files that are in the current working directory or a
subdirectory, as it normally does. An easy way to do this is to
activate the Java Security Manager. Normally, a security manager is
used to prevent Java code downloaded over the Net from doing anything
suspicious. However, the security manager will serve nicely to
restrict file access in our application as well.
You can use a policy like the simple one that we provided in the section “Sockets and security” earlier in this chapter; it allows the server to accept connections on a specified range of sockets. As a happy bonus, the default file access security policy does just what we want: allows an application access to files in its current working directory and subdirectories. So simply installing the security manager will provide exactly the kind of file protection that we wanted in this case. (It would be easy to add additional permissions if you wish to extend the server’s range to other well-defined areas.)
With the security manager in place, the daemon will not be able to
access anything that isn’t within the current directory or a
subdirectory. If it tries to, the security manager throws an
exception and prevents access to the file. In that case, we should
have TinyHttpd
catch the
SecurityException
and return a proper message to
the web browser. Add the following catch
clause
after the FileNotFoundException
’s
catch
clause:
... } catch ( Security Exception e ) { pout.println("403 Forbidden"); }
TinyHttpd
still has quite a bit of room for
improvement. First, it consumes a lot of memory by allocating a huge
array to read the entire contents of the file all at once. A more
realistic implementation would use a buffer and send large amounts of
data in several passes. Reading and sending the data iteratively
would also allow us to handle the contingency where the first read
does not return all of the data. In practice, this will not happen
when reading from files, but the possibility is left open by the API
and a responsible application should handle it. Finally,
TinyHttpd
is of course not fully compliant with
the HTTP 1.0 protocol, but only implements a rudimentary portion of
the GET
command. A modern web server would expect
and send additional “meta” information about the
requested file in HTTP header text. As an additional convenience, it
wouldn’t be hard to add a few lines of code to read directories
and generate linked HTML listings as most web servers do. Have fun
with this example and you
can learn quite a
bit!
The Java sockets API is a simplified interface to the general socket mechanisms. In a C environment, where all of the gory details of the network are visible to you, a lot of complex and sometimes esoteric options can be set on sockets to govern the behavior of the underlying protocols. Java gives us access to a few of the important ones. We’ll refer to them by their C language names so that you can recognize them in other networking books.
The SO_TIMEOUT
option sets a
timer on all I/O methods of
a socket that block so that you don’t have to wait forever if
they don’t return. This works for operations such as
accept( )
on server sockets and read( )
or write( )
on all sockets. If the
timer expires before the operation would complete, an
InterruptedIOException
is thrown. You can catch
the exception and continue to use the socket normally if it is
appropriate, or you can take the opportunity to bail out of the
operation. Servers should use this sort of technique for their
“shutdown” logic:
serverSocket.setSoTimeout( 2000 ); // 2 seconds while ( !shutdown ) { try { Socket client = serverSocket.accept( ); handleClient( client ); } catch ( InterruptedIOException e ) { // ignore the exception } // exit }
You set the timer by calling the setSoTimeout( )
method of the
Socket
class with the timeout period, in
milliseconds, as an int
argument. This works for
regular Socket
s and
ServerSocket
s (TCP) and
DatagramSockets
(UDP), which we’ll discuss
in the next section.
To find out the current timeout value, call getSoTimeout( )
.
This option turns off a feature of
TCP called Nagle’s algorithm,
which tries to prevent certain interactive applications from flooding
the network with very tiny packets. Turn this off if you have a fast
network and you want all packets sent as soon as possible. The
Socket
setTcpNoDelay( )
method takes a boolean argument specifying whether the delay is on or
off.
To find out whether the TCP_NODELAY
option is
enabled, call getTcpNoDelay( )
, which returns a
boolean
.
This option controls what happens to any
unsent data when you perform a close( )
on an
active socket connection. Normally the system tries to deliver any
network buffered data and close the connection gracefully. The
setSoLinger( )
method of the
Socket
class takes two arguments: a boolean that
enables or disables the option, and an int
that
sets the “linger” value, in seconds. If you set the
linger value to 0, any unsent data is discarded, and the TCP
connection is aborted (terminated with a reset).
To find out the current linger value, call getSoLinger( )
.
This option can be enabled with the setKeepAlive( )
method. It triggers a feature
of TCP that polls the other side every two
hours if there is no other activity. Normally, when there is no data
flowing on a TCP connection, no packets are sent at all. This can
make it difficult to tell the difference between the other side
simply being quiet and having disappeared. If one side of the
connection closes it properly, this will be detected. But if the
other side simply disappears, we will not know unless and until we
try to talk to them. For this reason, servers often use this feature
to detect lost client connections (where they might otherwise only
respond to requests, rather than initiate them).
Keepalive
is not part of the TCP specification;
it’s an add-on that’s not guaranteed to be implemented
everywhere. If you have the option, the best way to be sure of
detecting lost clients is to implement the polling as part of your
own protocol.
In
TCP, it is technically
possible to close one direction of a stream but not the other. In
other words, you can shut down sending but not receiving, or vice
versa. A few protocols use this to indicate the end of a client
request by closing the client side of the stream, allowing the end of
stream to be detected by the server. You can shut down either half of
a socket connection with shutdownOutput( )
or
shutdownInput( )
.
Many networks are behind firewalls, which prevent applications from opening direct socket connections to the outside network. Instead, they provide a service called SOCKS (named for sockets) that serves as a proxy server for socket connections, giving the administrators more control over what connections are allowed.
Java has built-in support for SOCKS. All you have to do is set some system properties in your application (in an applet, this should be already taken care of for you, since you wouldn’t have authority to set those properties). Here’s a list of the properties that configure Java to use a proxy server:
-
http.proxySet
A boolean (
true
orfalse
) indicating whether to use the proxy-
http.proxyHost
The proxy server name
-
http.proxyPort
The proxy port number
You can set these properties on the command line using the
Java interpreter’s
-D
option or by calling the
System.setProperty( )
method. The following command runs
MyProgram
using the proxy server at
foo.bar.com on port 1234:
% java -Dhttp.proxySet=true -Dhttp.proxyServer=foo.bar.com -Dhttp.proxyPort=1234 MyProgram
In SDK 1.0.2, the names didn’t have the
http.
prefix. SDK 1.1 and later checks for the new
names and then the old names. If the firewall does not allow any
outside socket connections, your applet or application may still be
able to communicate with the outside world by using HTTP to send and
receive data. See Chapter 12, for an example of how
to perform
an HTTP POST.
[37] For a discussion of sockets in general, see Unix Network Programming, by Richard Stevens (Prentice- Hall). For a complete discussion of network programming in Java, see Java Network Programming, by Elliotte Rusty Harold (O’Reilly & Associates).
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.