6.4 Socket Class
The Socket class represents client sockets, and is a communication channel between two TCP communications ports belonging to one or two machines. A socket may connect to a port on the local system, avoiding the need for a second machine, but most network software will usually involve two machines. TCP sockets can't communicate with more than two machines, however. If this functionality is required, a client application should establish multiple socket connections, one for each machine.
Constructors
There are several constructors for the java.net.Socket class. Two constructors, which allowed a boolean parameter to specify whether UDP or TCP sockets were to be used, have been deprecated. These constructors should not be used and are not listed hereif UDP functionality is required, use a DatagramSocket (covered in Chapter 5).
The easiest way to create a socket is to specify the hostname of the machine and the port of the service. For example, to connect to a Web server on port 80, the following code might be used:
try { // Connect to the specified host and port Socket mySocket = new Socket ( "www.awl.com", 80); // ...... } catch (Exception e) { System.err.println ("Err " + e); }
However, a wide range of constructors is available, for different situations. Unless otherwise specified, all constructors are public.
protected Socket ()creates an unconnected socket using the default implementation provided by the current socket factory. Developers should not normally use this method, as it does not allow a hostname or port to be specified.
Socket (InetAddress address, int port) throws java.io.IOException,
java.lang.SecurityExceptioncreates a socket connected to the specified IP address and port. If a connection cannot be established, or if connecting to that host violates a security restriction (such as when an applet tries to connect to a machine other than the machine from which it was loaded), an exception is thrown.
Socket (InetAddress address, int port, InetAddress localAddress, int localPort) throws java.io.IOException, java.lang.SecurityExceptioncreates a socket connected to the specified address and port, and is bound to the specified local address and local port. By default, a free port is used, but this method allows you to specify a specific port number, as well as a specific address, in the case of multi-homed hosts (i.e., a machine where the localhost is known by two or more IP addresses).
protected Socket (SocketImpl implementation)creates an unconnected socket using the specified socket implementation. Developers should not normally use this method, as it does not allow a hostname or port to be specified.
Socket (String host, int port) throws java.net.UnknownHostException, java.io.IOException, java.lang.SecurityExceptioncreates a socket connected to the specified host and port. This method allows a string to be specified, rather than an InetAddress. If the hostname could not be resolved, a connection could not be established, or a security restriction is violated, an exception is thrown.
Socket (String host, int port, InetAddress localAddress, int localPort) throws java.net.UnknownHostException, java.io.IOException, java.lang.SecurityExceptioncreates a socket connected to the specified host and port, and bound to the specified local port and address. This allows a hostname to be specified as a string, and not an InetAddress instance, as well as allowing a specific local address and port to be bound to. These local parameters are useful for multihomed hosts (i.e., a machine where the localhost is known by two or more IP addresses). If the hostname can't be resolved, a connection cannot be established, or a security restriction is violated, an exception is thrown.
6.4.1 Creating a Socket
Under normal circumstances, a socket is connected to a machine and port when it is created. Although there is a blank constructor that does not require a hostname or port, it is protected and can't be called from normal applications. Furthermore, there isn't a connect() method that allows you to specify these details at a later point in time, so under normal circumstances the socket will be connected when created. If the network is fine, the call to a socket constructor will return as soon as a connection is established, but if the remote machine is not responding, the constructor method may block for an indefinite amount of time. This varies from system to system, depending on a variety of factors such as the operating system being used and the default network timeout (some machines on a local intranet, for example, seem to respond faster than some Internet machines, depending on network settings). You can't ever guarantee how long a socket may block for, but this is abnormal behavior and won't happen frequently. Nonetheless, in mission-critical systems it may be appropriate to place such calls in a second thread, to prevent an application from stalling.
NOTE
At a lower level, sockets are produced by a socket factory, which is a special class responsible for creating the appropriate socket implementation. Under normal circumstances, a standard java.net.Socket will be produced, but in special situations, such as special networking environments in which custom sockets are used (for example, to break through a firewall by using a special proxy server), socket factories may actually return a socket subclass. The details of socket factories are best left to experienced developers who are familiar with the intricacies of Java networking and have a definite purpose for creating custom sockets and socket factories. For more information on this topic, consult the Java API documentation for the java.net.SocketFactory and java.net.SocketImplFactory class.
6.4.2 Using a Socket
Sockets can perform a variety of tasks, such as reading information, sending data, closing a connection, and setting socket options. In addition, the following methods are provided to obtain information about a socket, such as address and port locations:
Methods
void close() throws java.io.IOExceptioncloses the socket connection. Closing a connect may or may not allow remaining data to be sent, depending on the value of the SO_LINGER socket option. Developers are advised to flush any output streams before closing a socket connection.
InetAddress getInetAddress()returns the address of the remote machine that is connected to the socket.
InputStream getInputStream() throws java.io.IOExceptionreturns an input stream, which reads from the application this socket is connected to.
OutputStream getOutputStream() throws java.io.IOExceptionreturns an output stream, which writes to the application that this socket is connected to.
boolean getKeepAlive() throws java.net.SocketExceptionreturns the state of the SO_KEEPALIVE socket option.
InetAddress getLocalAddress()returns the local address associated with the socket (useful in the case of multihomed machines).
int getLocalPort()returns the port number that the socket is bound to on the local machine.
int getPort()returns the port number of the remote service to which the socket is connected.
int getReceiveBufferSize() throws java.net.SocketExceptionreturns the receive buffer size used by the socket, determined by the value of the SO_RCVBUF socket option.
int getSendBufferSize() throws java.net.SocketExceptionreturns the send buffer size used by the socket, determined by the value of the SO_SNDBUF socket option.
int getSoLinger() throws java.net.SocketExceptionreturns the value of the SO_LINGER socket option, which controls how long unsent data will be queued when a connection is terminated.
int getSoTimeout() throws java.net.SocketExceptionreturns the value of the SO_TIMEOUT socket option, which controls how many milliseconds a read operation will block for. If a value of 0 is returned, the timer is disabled and a thread will block indefinitely (until data is available or the stream is terminated).
boolean getTcpNoDelay() throws java.net.SocketExceptionreturns "true" if the TCP_NODELAY socket option is set, which controls whether Nagle's algorithm (discussed in Section 6.4.4.5) is enabled.
void setKeepAlive(boolean onFlag) throws java.net.SocketExceptionenables or disables the SO_KEEPALIVE socket option.
void setReceiveBufferSize(int size) throws java.net.SocketExceptionmodifies the value of the SO_RCVBUF socket option, which recommends a buffer size for the operating system's network code to use for receiving incoming data. Not every system will support this functionality or allows absolute control over this feature. If you want to buffer incoming data, you're advised to instead use a BufferedInputStream or a BufferedReader.
void setSendBufferSize(int size) throws java.net.SocketExceptionmodifies the value of the SO_SNDBUF socket option, which recommends a buffer size for the operating system's network code to use for sending incoming data. Not every system will support this functionality or allows absolute control over this feature. If you want to buffer incoming data, you're advised to instead use a BufferedOutputStream or a BufferedWriter.
static void setSocketImplFactory (SocketImplFactory factory) throws java.net.SocketException, java.io.IOException, java. lang.SecurityException assigns a socket implementation factory for the JVM, which may already exist, or may violate security restrictions, either of which causes an exception to be thrown. Only one factory can be specified, and this factory will be used whenever a socket is created.
void setSoLinger(boolean onFlag, int duration) throws java.net. SocketException, java.lang.IllegalArgumentExceptionenables or disables the SO_LINGER socket option (according to the value of the onFlag boolean parameter), and specifies a duration in seconds. If a negative value is specified, an exception is thrown.
void setSoTimeout(int duration) throws java.net.SocketExceptionmodifies the value of the SO_TIMEOUT socket option, which controls how long (in milliseconds) a read operation will block. A value of zero disables timeouts, and blocks indefinitely. If a timeout does occur, a java.io.IOInterruptedException is thrown whenever a read operation occurs on the socket's input stream. This is distinct from the internal TCP timer, which triggers a resend of unacknowledged datagram packets (see Section 6.1.1.1 on error control).
void setTcpNoDelay(boolean onFlag) throws java.net.SocketExceptionenables or disables the TCP_NODELAY socket option, which determines whether Nagle's algorithm is used.
void shutdownInput() throws java.io.IOExceptioncloses the input stream associated with this socket and discards any further information that is sent. Further reads to the input stream will encounter the end of the stream marker.
void shutdownOutput() throws java.io.IOExceptioncloses the output stream associated with this socket. Any data previously written, but not yet sent, will be flushed, followed by a TCP connection-termination sequence, which notifies the application that no more data will be available (and in the case of a Java application, that the end of the stream has been reached). Further writes to the socket will cause an IOException to be thrown.
6.4.3 Reading from and Writing to TCP Sockets
Creating client software that uses TCP for communication is extremely easy in Java, no matter what operating system is being used. The Java Networking API provides a consistent, platform-neutral interface that allows client applications to connect to remote services. Once a socket is created, it is connected and ready to read/write by using the socket's input and output streams. These streams don't need to be created; they are provided by the Socket. getInputStream() and Socket.getOutputStream() methods. As was shown in Chapter 4 on I/O streams, filtered streams provide easy I/O access.
A filter can easily be connected to a socket stream, to make for simpler programming. The following code snippet demonstrates a simple TCP client that connects a BufferedReader to the socket input stream, and a PrintStream to the socket output stream.
try { // Connect a socket to some host machine and port Socket socket = new Socket ( somehost, someport ); // Connect a buffered reader BufferedReader reader = new BufferedReader ( new InputStreamReader ( socket.getInputStream() ) ); // Connect a print stream PrintStream pstream = new PrintStream( socket.getOutputStream() ); } catch (Exception e) { System.err.println ("Error " + e); }
6.4.4 Socket Options
Socket options are settings that modify how sockets work, and they can affect (both positively and negatively) the performance of applications. Support for socket options was introduced in Java 1.1, and some refinements have been made in later versions (such as support for the SO_KEEPALIVE option in Java 2 v 1.3). Generally, socket options should not be changed unless there is a good reason for doing so, as changes may negatively affect application and network performance (for example, enabling Nagle's algorithm may increase performance of telnet type applications but lower the available bandwidth). The one exception to this caveat is the SO_TIMEOUT optionvirtually every TCP application should handle timeouts gracefully rather than stalling if the application the socket is connected to fails to transmit data when required.
6.4.4.1 SO_KEEPALIVE Socket Option
The keepalive socket option is controversial; its use is a topic that some developers feel very strongly about. By default, no data is sent between two connected sockets unless an application has data to send. This means that an idle socket may not have data submitted for minutes, hours, or even days in the case of long-lived processes. Suppose, however, that a client crashes, and the end-of-connection sequence is not sent to a TCP server. Valuable resources (CPU time and memory) might be wasted on a client that will never respond. When the keepalive socket option is enabled, the other end of the socket is probed to verify it is still active. However, the application doesn't have any control over how often keepalive probes are sent. To enable keepalive, the Socket.setSoKeepAlive(boolean) method is called with a value of "true" (a value of "false" will disable it). For example, to enable keepalive on a socket, the following code would be used.
// Enable SO_KEEPALIVE someSocket.setSoKeepAlive(true);
Although keepalive does have some advantages, many developers advocate controlling timeouts and dead sockets at a higher level, in application code. It should also be kept in mind that keepalive doesn't allow you to specify a value for probing socket endpoints. A better solution than keepalive, and one that developers are advised to use, is to instead modify the timeout socket option.
6.4.4.2 SO_RCVBUF Socket Option
The receive buffer socket option controls the buffer used for receiving data. Changes can be made to the size by calling the Socket.setReceiveBufferSize(int) method. For example, to increase the receive buffer size to 4,096 bytes, the following code would be used.
// Modify receive buffer size someSocket.setReceiveBufferSize(4096);
Note that a request to modify the size of the receive buffer does not guarantee that it will change. For example, some operating systems may not allow this socket option to be modified, and will ignore any changes to the value. The current buffer size can be determined by invoking the Socket. getReceiveBufferSize() method. A better choice for buffering is to use a BufferedInputStream/BufferedReader.
6.4.4.3 SO_SNDBUF Socket Option
The send buffer socket option controls the size of the buffer used for sending data. By calling the Socket.setSendBufferSize(int) method, you can attempt to change the buffer size, but requests to change the size may be rejected by the operating system.
// Set the send buffer size to 4096 bytes someSocket.setSendBufferSize(4096);
To determine the size of the current send buffer, you can call the Socket.getSendBufferSize() method, which returns an int value.
// Get the default size int size = someSocket.getSendBufferSize();
Changing buffer size will be more effective with the DatagramSocket class. When buffering writes, the preferable choice is to use a BufferedOutputStream or a BufferedWriter.
6.4.4.4 SO_LINGER Socket Option
When a TCP socket connection is closed, it is possible that data may be queued for delivery and not yet sent (particularly if an IP datagram becomes lost in transit and must be resent). The linger socket option controls the amount of time during which unsent data may be sent, after which it is discarded completely. It is possible to enable/disable the linger option entirely, or to modify the duration of a linger, by using the Socket.setSoLinger(boolean onFlag, int duration) method:
// Enable linger, for fifty seconds someSocket.setSoLinger( true, 50 );
6.4.4.5 TCP_NODELAY Socket Option
This socket option is a flag, the state of which controls whether Nagle's algorithm (RFC 896) is enabled or not. Because TCP data is sent over the network using IP datagrams, a fair bit of overhead exists for each packet, such as IP and TCP header information. If only a few bytes at a time are sent in each packet, the size of the header information will far exceed that of the data. On a local area network, the extra amount of data sent probably won't amount to much, but on the Internet, where hundreds, thousands, or even millions of clients may be sending such packets through individual routers, this adds up to a significant amount of bandwidth consumption.
The solution is Nagle's algorithm, which states that TCP may send only one datagram at a time. When an acknowledgment comes back for each IP datagram, a new packet is sent containing any data that has been queued up. This limits the amount of bandwidth being consumed by packet header information, but at a not insignificant costnetwork latency. Since data is being queued, it isn't dispatched immediately, so systems that require quick response times such as X-Windows or telnet are slowed. Disabling Nagle's algorithm may improve performance, but if used by too many clients, network performance is reduced.
Nagle's algorithm is enabled or disabled by invoking the Socket.setTcpNoDelay(boolean state) method. For example, to deactivate the algorithm, the following code would be used:
// Disable Nagle's algorithm for faster response times someSocket.setTcpNoDelay(false);
To determine the state of Nagle's algorithm and the TCP_NODELAY flag, the Socket.getTcpNoDelay() method is used:
// Get the state of the TCP_NODELAY flag boolean state = someSocket.getTcpNoDelay();
6.4.4.6 SO_TIMEOUT Socket Option
This timeout option is the most useful socket option. By default, I/O operations (be they file- or network-based) are blocking. An attempt to read data from an InputStream will wait indefinitely until input arrives. If the input never arrives, the application stalls and in most cases becomes unusable (unless multithreading is used). Users are not fond of unresponsive applications, and find such application behavior annoying, to say the least. A more robust application will anticipate such problems and take corrective action.
NOTE
In a local intranet environment during testing, network problems are rare, but on the Internet stalled applications are probable. Server applications are not immunea server connection to a client uses the Socket class as well, and can just as easily stall. For this reason, all applications (be they client or server) should handle network timeouts gracefully.
When the SO_TIMEOUT option is enabled, any read request to the InputStream of a socket starts a timer. When no data arrives in time and the timer expires, a java.io.InterruptedIOException is thrown, which can be caught to check for a timeout. What happens then is up to the application developera retry attempt might be made, the user might be notified, or the connection aborted. The duration of the timer is controlled by calling the Socket. setSoTimeout(int) method, which accepts as a parameter the number of milliseconds to wait for data. For example, to set a five-second timeout, the following code would be used:
// Set a five second timeout someSocket.setSoTimeout ( 5 * 1000 );
Once enabled, any attempt to read could potentially throw an InterruptedIOException, which is extended from the java.io.IOException class. Since read attempts can already throw an IOException, no further code is required to handle the exceptionhowever, some applications may want to specifically trap timeout-related exceptions, in which case an additional exception handler may be added.
try { Socket s = new Socket (...); s.setSoTimeout ( 2000 ); // do some read operation .... } catch (InterruptedIOException iioe) { timeoutFlag = true; // do something special like set a flag } catch (IOException ioe) { System.err.println ("IO error " + ioe); System.exit(0); }
To determine the length of the TCP timer, the Socket.getSoTimeout() method, which returns an int, can be used. A value of zero indicates that timeouts are disabled, and read operations will block indefinitely.
// Check to see if timeout is not zero if ( someSocket.getSoTimeout() == 0) someSocket.setSoTimeout (500);