Socket programming in Java
Socket programming in Java
TCP is relatively simple and reliable protocol that enables a client to make a connection to a
server and the two systems to communicate. In TCP, each entity knows that its communication
payloads have been received.
UDP is a connectionless protocol and is good for scenarios where you do not necessarily need
every packet to arrive at its destination, such as media streaming.
To appreciate the difference between TCP and UDP, consider what would happen if you were
streaming video from your favorite website and it dropped frames. Would you prefer that the
client slow down your movie to receive the missing frames or would you prefer that the video
continue playing? Video streaming protocols typically leverage UDP. Because TCP guarantees
delivery, it is the protocol of choice for HTTP, FTP, SMTP, POP3, and so forth.
In this tutorial, I introduce you to socket programming in Java. I present a series of client-server
examples that demonstrate features from the original Java I/O framework, then gradually
advance to using features introduced in NIO.2.
Once our socket instance is connected to the server we can start obtaining input and output
streams to the sever. Input streams are used to read data from the server while output streams are
used to write data to the server. We can execute the following methods to obtain input and output
streams:
InputStream in = socket.getInputStream();
Because these are ordinary streams, the same streams that we would use to read from and write
to a file, we can convert them to the form that best serves our use case. For example, we could
wrap the OutputStream with a PrintStream so that we can easily write text with methods
like println(). For another example, we could wrap the InputStream with
a BufferedReader, via an InputStreamReader, in order to easily read text with
methods like readLine().
Listing 1. SimpleSocketClientExample.java
package com.geekcap.javaworld.simplesocketclient;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
{
public static void main( String[] args )
System.out.println( "Usage:
SimpleSocketClientExample <server> <path>" );
System.exit( 0 );
try
out.println();
System.out.println( line );
line = in.readLine();
in.close();
out.close();
socket.close();
catch( Exception e )
e.printStackTrace();
}
Listing 1 accepts two command-line arguments: the server to connect to (assuming that we're
connecting to the server on port 80) and the resource to retrieve. It creates a Socket that points
to the server and explicitly specifies port 80. It then executes the command:
For example:
GET / HTTP/1.0
When you retrieve a web page from a web server, such as www.google.com, the HTTP client
uses DNS servers to find the server's address: it starts by asking the top-level domain server for
the com domain where the authoritative domain-name server is for the www.google.com.
Then it asks that domain-name server for the IP address (or addresses) for www.google.com.
Next, it opens a socket to that server on port 80. (Or, if you want to define a different port, you
can do so by adding a colon followed by the port number, for example: :8080.) Finally, the
HTTP client executes the specified HTTP method, such as GET, POST, PUT, DELETE, HEAD,
or OPTI/ONS. Each method has its own syntax. As shown in the above code snips,
the GET method requires a path followed by HTTP/version number and an empty line. If
we wanted to add HTTP headers we could have done so before entering the new line.
java
com.geekcap.javaworld.simplesocketclient.SimpleSocketClientExamp
le www.javaworld.com /
HTTP/1.1 200 OK
Date: Sun, 21 Sep 2014 22:20:13 GMT
Server: Apache
X-Gas_TTL: 10
Cache-Control: max-age=10
X-GasHost: gas2.usw
X-Cooking-With: Gasoline-Local
X-Gasoline-Age: 8
Content-Length: 168
Etag: "60001b-a8-4b73af4bf3340"
Content-Type: text/html
Vary: Accept-Encoding
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<br><br>
<center>Success</center>
</body>
</html>
This output shows a test page on JavaWorld's website. It replied back that it speaks HTTP
version 1.1 and the response is 200 OK.
As you'll soon see, NIO's handling of this scenario would be a bit different. For now, though, we
can directly create a ServerSocket by passing it a port to listen on (more
about ServerSocketFactorys in the next section):
And now we can accept incoming connections via the accept() method:
Listing 2, below, puts all of the server code so far together into a slightly more robust example
that uses threads to handle multiple requests. The server shown is an echo server, meaning that it
echoes back any message it receives.
While the example in Listing 2 isn't complicated it does anticipate some of what's coming up in
the next section on NIO. Pay special attention to the amount of threading code we have to write
in order to build a server that can handle multiple simultaneous requests.
Listing 2. SimpleSocketServer.java
package com.geekcap.javaworld.simplesocketclient;
import java.io.BufferedReader;
import java.io.I/OException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
this.port = port;
try
this.start();
}
catch (I/OException e)
e.printStackTrace();
running = false;
this.interrupt();
@Override
running = true;
while( running )
try
requestHandler.start();
catch (I/OException e)
e.printStackTrace();
if( args.length == 0 )
System.exit( 0 );
server.startServer();
try
Thread.sleep( 60000 );
catch( Exception e )
e.printStackTrace();
server.stopServer();
this.socket = socket;
}
@Override
try
out.flush();
out.flush();
line = in.readLine();
}
in.close();
out.close();
socket.close();
catch( Exception e )
e.printStackTrace();
In Listing 2 we create a new SimpleSocketServer instance and start the server. This is
required because the SimpleSocketServer extends Thread to create a new thread to
handle the blocking accept() call that you see in the read() method. The run() method
sits in a loop accepting client requests and creating RequestHandler threads to process the
request. Again, this is relatively simple code, but also involves a fair amount of threaded
programming.
Note too that the RequestHandler handles the client communication much like the code in
Listing 1 did: it wraps the OutputStream with a PrintStream to facilitate easy writes and,
similarly, wraps the InputStream with a BufferedReader for easy reads. As far as a
server goes, it reads lines from the client and echoes them back to the client. If the client sends
an empty line then the conversation is over and the RequestHandler closes the socket.
Channels are designed to support bulk transfers from one NIO buffer to another.
Buffers represent a contiguous block of memory interfaced by a simple set of operations.
Non-Blocking Input/Output is a set of classes that expose channels to common I/O sources like files
and sockets.
When programming with NIO, you open a channel to your destination and then read data into
a buffer from the destination, write the data to a buffer, and send that to your destination. We'll
dive into setting up a socket and obtaining a channel to it shortly, but first let's review the process
of using a buffer:
When data is written into the buffer, the buffer knows the amount of data written into it. It
maintains three properties, whose meanings differ if the buffer is in read mode or write mode:
Position: In write mode, the initial position is 0 and it holds the current position being written to in the
buffer; after you flip a buffer to put it in read mode, it resets the position to 0 and holds the current
position in the buffer being read from,
Capacity: The fixed size of the buffer
Limit: In write mode, the limit defines how much data can be written into the buffer; in read mode, the
limit defines how much data can be read from the buffer.
Listing 3 shows the source code for our new asynchronous Echo Server.
Listing 3. SimpleSocketServer.java
package com.geekcap.javaworld.nio2;
import java.io.I/OException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public NioSocketServer()
try
@Override
try
byteBuffer.flip();
byteBuffer.get( lineBytes, 0,
bytesRead );
// Debug
byteBuffer.clear();
else
running = false;
catch (InterruptedException e)
e.printStackTrace();
catch (ExecutionException e)
e.printStackTrace();
}
catch (TimeoutException e)
try
if( ch.isOpen() )
ch.close();
e1.printStackTrace();
@Override
public void failed(Throwable exc, Void att) {
///...
});
catch (I/OException e)
e.printStackTrace();
try
Thread.sleep( 60000 );
catch( Exception e )
e.printStackTrace();
}
In Listing 3 we first create a new AsynchronousServerSocketChannel and then bind it
to port 5000:
AsynchronousServerSocketChannel.open().bind(new
InetSocketAddress(5000));
The only thing to be aware of is that the main() method, which creates the server, also sets up
a 60 second timer to keep the application running. Because
the AsynchronousSocketChannel's accept() method returns immediately, if we don't
have the Thread.sleep() then our application will stop immediately.
To test this out, launch the server and connect to it using a telnet client:
Send a few strings to the server, observe that they are echoed back to you, and then send an
empty line to terminate the conversation.
In conclusion
In this article I've presented two approaches to socket programming with Java: the traditional
approach introduced with Java 1.0 and the newer, non-blocking NIO and NIO.2 approaches
introduced in Java 1.4 and Java 7, respectively. You've seen several iterations of a Java socket
client and a Java socket server example, demonstrating both the utility of basic Java I/O and
some scenarios where non-blocking I/O improves the Java socket programming model. Using
non-blocking I/O, you can program Java networked applications to handle multiple simultaneous
connections without having to manage multiple thread collections. You can also take advantage
of the new server scalability that is built in to NIO and NIO.2.