Learn Java Programming
9.0 Network programming in Java
The following are the objectives of this module.
- To completely understand the Java net package
- The basics of communication – the TCP protocol and the UDP protocol
- What is a socket – and what is a port
- Create a server listening for TCP connections
- How to create a client to connect to a server listening to TCP connections
- How a 3 way handshake is implemented for a tcp communication.
- Create a server listening for UDP connections
- How to create a client to connect to a server listening to UDP connections
On a network, communication happens between two process when they agree upon a given protocol for exchange of data. The two most popular network protocols in use today are:
- Transmission Control Protocol (TCP)
- User Datagram Protocol (UDP)
TCP is a connection-oriented protocol. A virtual channel is established between the client and server, for the duration of the connection. A TCP connection guarantees safe delivery of information between the server and the clients.
UDP protocol is a stateless protocol. A datagram packet has server information to reach the host for which it was destined. The datagram packet has a header which contains the destination address and the payload
In order to communicate to a server, a client will need the following two information:
- The host name
- Port number on the host.
The host name is a DNS name or IP address that is used to uniquely identify the server on a network.
Port
In a given server specified by the host name, there may be several processes. To identify our server process we need a unique port number on the server. Port number is the location in which the server process is waiting for the client to connect to it.
To enable communication between two hosts connected by a network layer, Java gives us a beautiful abstraction called the socket.
A socket can be visualized as a pipe with two ends terminating on the client and the server – data can flow between the two ends.
There are two types of server sockets one for TCP connections and other for UDP connections:
- ServerSocket
- DatagramSocket
TCP Connections
In order to accept TCP Connections, we would need to instantiate a ServerSocket object Here is how we define a ServerSocket to accept TCP Connections:
int PORT = 5253;
ServerSocket serverSocket = new ServerSocket(PORT);
Socket socket = serverSocket.accept();
The accept() method on the serverSocket object is a blocking call – that is, it blocks for input till available. When a client connects to the server, the accept method returns a socket object.
If a client needs to connect to this server, using TCP protocol (let us assume that the server runs on your local machine, then the host name can be specified as localhost)
Socket socket = new Socket("localhost", 5252);
Thus, it is important to understand, both the server and the client each have a socket object when the connection is established.
It would help to visualize the socket as a “hole” – one end on the server and one end on the client – through which the data will flow from the client to the server.
Reading data from the TCP network layer
Let us assume a client connected to the server at port 5252 on local host. Reading data sent from the client is very easy.
Simply get the input stream from the socket. Similarly it is easy to send data to the client from the server, by writing to the output stream obtained from the socket.
int PORT = 5253;
ServerSocket serverSocket = new ServerSocket(PORT);
Socket socket = serverSocket.accept();
socket.getInputStream();
socket.getOutputStream();
Once we have the input and output stream, it is easy to wrap it into the appropriate stream
. . .
int PORT = 5253;
ServerSocket serverSocket = new ServerSocket(PORT);
Socket socket = serverSocket.accept();
. . .
DataInputStream in = null;
DataOutputStream out = null;
try {
in = new DataInputStream(socket.getInputStream());
out = new DataOutputStream(socket.getOutputStream());
} catch (Exception e) {
. . .
} finally {
. . .
}
Writing a TCP Server
Now let us look at a simple TCP Server implementation – we will make this server a Threaded Server, so that it can serve simultaneous concurrent connections.
public class TCPServer extends Thread {
public static void main(String[] args) throws Exception {
TCPServer tcpServer = new TCPServer();
tcpServer.startServer();
}
private void startServer() {
this.start();
}
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(5000);
while (true) {
Socket socket = null;
DataInputStream dis = null;
DataOutputStream dos = null;
socket = serverSocket.accept();
dis = new DataInputStream(socket.getInputStream());
int value = dis.readInt();
System.out.println(
"Received data from TCPClient ["
+ value + "]");
dos = new DataOutputStream(socket.getOutputStream());
dos.write(value * value);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null) socket.close();
if (dis != null) dis.close();
if (dos != null) dos.close();
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
}
Since the TCPServer is a Threaded implementation – each request can now be served by one thread.
The run() method creates a ServerSocket on port 5000 and listens to TCP connections – Note we have a while true loop inside the run() method – the TCPServer is alive to serve as many requests as it can.
The serverSocket.accept() method is the blocking call that will wait for connections.
Once a client connects to the Server, from the socket, we get the input stream and the output stream associated with the socket. We expect the client to write an integer value, so we read the integer value using the readInt() method:
int value = dis.readInt();
We then square this value, and the output is written to the output stream using
dos.write(value * value);
Writing the TCPClient class
The TCPClient implementation is straight-forward. We just create a socket connection using the hostname and port number. Once connected, we then get the input stream, and the output stream from the socket. The client code writes an int value and expects an int value from the server.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws Exception {
TCPClient tcpClient = new TCPClient();
for(int i=1; i< 10; i++){
tcpClient.testTCPConnection(i);
Thread.sleep(1000);
}
}
private void testTCPConnection(int value) throws IOException {
Socket socket = new Socket("localhost", 5000);
DataOutputStream dos
= new DataOutputStream(socket.getOutputStream());
DataInputStream dis
= new DataInputStream(socket.getInputStream());
dos.write(value);
int returnedValue = dis.read();
System.out.println("Received the value from server as ["
+ returnedValue + "]");
}
}
Client-Server handshake
In a typical client-server TCP Connection, the client sends some data to the server, which the server will respond with the service. Once the client receives the server response, it will send an acknowledgment back to the server.
Remember that both the client and the server have a reference to the InputStream and the OutputStream on the socket. Using this stream, it is possible for the client and the server to establish the three way handshake.
- Client sends a request
- Server sends response
- Client acknowledges receipt of response
UDP Connections
In order to create a UDP Server, that will accept UDP packets we make use of the Datagram socket. Just like the TCP Server, The UDP Server needs to publish itself on a specific port
int PORT = 5253;
int BUFFER_SIZE = 1024;
DatagramSocket datagramSocket = new DatagramSocket(PORT);
byte[] buffer = new byte[BUFFER_SIZE];
DatagramPacket packet = new DatagramPacket(buffer, BUFFER_SIZE);
this.datagramSocket.receive(packet);
It is possible for us to have a UDP Server and TCP Server on the same port (in our example here, it is 5253). The DatagramSocket receives a DatagramPacket
Writing a UDP Server
Now let us look at a simple UDPServer implementation – this server is also a Threaded server, so that it can process multiple simultaneous concurrent requests.
public class UDPServer extends Thread {
public static void main(String[] args) throws Exception {
UDPServer udpServer = new UDPServer();
udpServer.startServer();
}
private void startServer() {
this.start();
}
public void run() {
DatagramSocket datagramSocket = null;
try {
datagramSocket = new DatagramSocket(3000);
} catch (IOException e) {
e.printStackTrace();
}
while(true) {
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, 1024);
try {
datagramSocket.receive(dp);
byte[] byteBuf = dp.getData();
ByteArrayInputStream bain
= new ByteArrayInputStream(byteBuf);
DataInputStream dis
= new DataInputStream (bain);
int data = dis.readInt();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bain != null) bain.close();
if (dis != null) dis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Since we have made the UDPServer as a Threaded server – it can now handle concurrent requests for multiple clients. Also if you would notice that the while(true) loop inside the run() method makes sure that the server is able to process requests continuously in its life-time
Reading data from the UDP layer
To read data from the UDP layer, we first need to get the byte[] from the Datagram packet. This byte array can then be converted to an appropriate stream using the Java IO classes as shown below
byte[] byteBuf = datagramPacket.getData();
ByteArrayInputStream bain = new ByteArrayInputStream(byteBuf);
DataInputStream dis = new DataInputStream (bain);
Writing a UDP Client
Here is how we write a UDPClient to connect to a given UDP Server. First and foremost we need to know the server host name and the PORT number. Let us assume that the server address is given as serverIP and the serverPort is 5253
Then we construct a DatagramPacket using the byte[] – we also need to specify the size of the byte array, the server hostname and the port
Let us assume that we have ByteArrayOutputStream object called baos. Here is how we create the DatagramPacket:
InetAddress address = InetAddress.getLocalHost();
String clientIP = address.getHostAddress();
DatagramPacket dp =
new DatagramPacket(baos.toByteArray(), baos.size(),
InetAddress.getByName(serverIP), 5253 );
Once we have created the DatagramPacket, it is as simple as sending it on its way
DatagramSocket ds = new DatagramSocket();
ds.send(dp);
Here is the complete UDPClient code:
public class UDPClient {
public static void main(String[] args) throws Exception {
UDPClient udpClient = new UDPClient();
for (int i = 1; i < 10; i++) {
udpClient.testUDPClient(i);
Thread.sleep(1000);
}
}
private void testUDPClient(int value) {
try {
InetAddress address = InetAddress.getLocalHost();
String serverIP = address.getHostAddress();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(value);
System.out.println(
"Client : Send UDP data to the server as [" + value + "]");
DatagramPacket dp =
new DatagramPacket(baos.toByteArray(), baos.size(),
InetAddress.getByName(serverIP), 5253);
DatagramSocket ds = new DatagramSocket();
ds.send(dp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Now that we have a solid understanding of how Java supports the common network TCP and UDP protocols, and with our knowledge of Java Thread programming, it is possible to build real robust scalable client server systems.
Summary:
A good understanding of simple network communication using TCP and UDP is imperative for the Java programmer. Using the short examples in this chapter we understood the following
- The abstraction called the Socket – and the visualization of the socket
- What is meant by a PORT
- How to write a multi-threaded TCP Server implementation
- Writing a simple TCP client
- How three-way handshakes are implemented in network applications.
- Writing a multi-threaded UDP Server implementation
- Writing a simple UDP client