Java Networking

Java Networking

Java’s networking capabilities are primarily provided through the java.net package, which offers classes for implementing network communications, managing network resources, and handling various networking protocols.

Socket Programming in Java

A network socket is a software component within a computer network that acts as a communication point for transmitting and receiving data between nodes (devices).

Socket programming is the foundation of network communication between nodes.

Sockets support full-duplex communication, which means they allow bidirectional data transfer. This means that data can be sent and received simultaneously over the same connection.

In the context of computer networks, we need to define a few key terms:

  • Node: Any device or endpoint that is connected to the network and can send and receive data. Examples include computers, smartphones, routers, switches, and IoT devices.
  • Host: A host is any node connected to a network. It’s identified by an IP address or domain name. It is like a phone number that identifies a specific node on the network (like how a phone number identifies a specific building or office).
  • Port A port is a communication endpoint that allows applications to communicate over a network. It is like an extension number. It identifies a specific service or application running on that computer (like how an extension number reaches a specific office or department).
  • Server: A software program that runs on a host machine and listens for incoming requests on a specific port. It handles these requests and sends back the responses to clients.
  • localhost: It refers to the local computer or node that a program is running on. It is a hostname that resolves to the loopback IP address, which is 127.0.0.1 for IPv4 and ::1 for IPv6.
  • Private IP Addresses: Private IP addresses that are not routable on the public internet and are used for internal communication within the network. The t following IPv4 address ranges for private networks: 10.0.0.010.255.255.255, 172.16.0.0172.31.255.255, and 192.168.0.0192.168.255.255.
  • Public IP addresses: Public IP addresses are globally unique and routable on the public internet. They are assigned by the Internet Service Providers (ISPs) and are used to identify nodes on the internet.

Types of Sockets in Java:

In Java, the package java.net provides the classes for implementing networking applications. There are two types of sockets:

  • Server Socket: Waits for client requests to come in over the network. In Java, the class ServerSocket
  • Client Socket: Initiates connection to server

We use a host name and a port number together to uniquely define the destination of network communication on a node.

Java Socket Communication

Example 1: Echo Server-Client

Echo Server

We will create a TCP socket server that echoes back any message received from clients. We will use standard Java I/O classes (BufferedReader and PrintWriter) for character/text based network communication.

EchoServer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.*;
import java.net.*;

public class EchoServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(9000)) {
            System.out.println("Server is listening on port 9000");

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New client connected");

                try (
                        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)
                    ) {

                    String line;
                    while ((line = reader.readLine()) != null) {
                        System.out.println("Received: " + line);
                        writer.println("Server: " + line);
                    }
                }
            }
        } catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
        }
    }
}

Echo Client

The corresponding client that sends messages to the server:

EchoClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.*;
import java.net.*;

public class EchoClient {
    public static void main(String[] args) {
        try (
                Socket socket = new Socket("localhost", 9000);
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                PrintWriter writer = new PrintWriter(
                        socket.getOutputStream(), true);
                BufferedReader userInput = new BufferedReader(
                        new InputStreamReader(System.in))
            ) {
                String message;
                while (true) {
                    System.out.print("Enter message: ");
                    message = userInput.readLine();

                    if ("exit".equalsIgnoreCase(message)) {
                        break;
                    }

                    writer.println(message);
                    System.out.println(reader.readLine());
                }
            } catch (IOException ex) {
            System.out.println("Client exception: " + ex.getMessage());
        }
    }
}

Example 2: Multi-User Chat Server using Multithreading

We will build a more complex example of a chat server that can handle multiple clients simultaneously using multithreading. Each client connection is handled in a separate thread to allow the server to accept multiple concurrent connections. When a client connects, a new ClientHandler thread is created to handle the client’s communication.

Chat Server

ChatServer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {
    private static Set<ClientHandler> clients = Collections.synchronizedSet(new HashSet<>());
    
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(9000)) {
            System.out.println("Chat Server is running on port 9000");
            
            while (true) {
                Socket socket = serverSocket.accept();
                ClientHandler client = new ClientHandler(socket);
                clients.add(client);
                client.start();
            }
        } catch (IOException ex) {
            System.out.println("Server exception: " + ex.getMessage());
        }
    }
    static class ClientHandler extends Thread {
        private Socket socket;
        private PrintWriter writer;
        
        public ClientHandler(Socket socket) {
            this.socket = socket;
        }
        
        public void run() {
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()))) {
                
                writer = new PrintWriter(socket.getOutputStream(), true);
                String message;
                
                while ((message = reader.readLine()) != null) {
                    broadcast(message);
                }
            } catch (IOException ex) {
                System.out.println("Error handling client: " + ex.getMessage());
            } finally {
                clients.remove(this);
                try {
                    if(writer != null) {
                        writer.close();
                    }
                    if(socket != null){
                        socket.close();
                    }
                } catch (IOException ex) {}
            }
        }
        
        private void broadcast(String message) {
            for (ClientHandler client : clients) {
                client.writer.println(message);
            }
        }
    }
}

Chat Client

graph LR
    subgraph Client
        RT[Reader Thread]
        MT[Main Thread]
    end
    subgraph Server
        S[Server]
    end
    RT <--> S
    MT <--> S

The ChatClient uses threads for full-duplex communication. A reader thread will continuously read incoming messages from server and print them to the console. The main thread will handle user input and send messages to the server.

ChatClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.io.*;
import java.net.*;

public class ChatClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 9000)) {
            // Reader thread for receiving messages
            new Thread(() -> {
                try (BufferedReader reader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()))) {
                    String message;
                    while ((message = reader.readLine()) != null) {
                        System.out.println(message);
                    }
                } catch (IOException ex) {
                    System.out.println("Error reading: " + ex.getMessage());
                }
            }).start();
            
            // Main thread for sending messages
            try (PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                 BufferedReader userInput = new BufferedReader(
                         new InputStreamReader(System.in))) {
                
                System.out.println("Enter your name:");
                String name = userInput.readLine();
                
                String message;
                while (true) {
                    message = userInput.readLine();
                    if (message.equalsIgnoreCase("exit")) break;
                    writer.println(name + ": " + message);
                }
            }
            
        } catch (IOException ex) {
            System.out.println("Client error: " + ex.getMessage());
        }
    }
}

Run the server and at least two clients

Open at least three tabs/windows of your terminal (Powershell or Terminal) and compile and run the server and clients in a new tab or window.

1
2
3
javac ChatServer.java && java ChatServer
javac ChatClient.java && java ChatClient
javac ChatClient.java && java ChatClient

TCP Socket Demo

Accessing Chat Service via Telnet

The chat server operates at the Transport Layer (Layer 4) of the OSI model, using raw TCP sockets rather than an application-level protocol. This means you can connect to it using any TCP client, including Telnet. It sends plain text directly over TCP and doesn’t implement any specific application protocol.

Using Telnet to Connect

Telnet is a network protocol that provides a bidirectional text-based communication channel between two terminal-oriented processes like TCP sockets. It works with our chat server because both telnet and our server use raw TCP connections and exchange plain text messages with line endings.

  1. Download and install telnet
  • On Windows:
    1. Open Control Panel
    2. Go to Programs and Features
    3. Click “Turn Windows features on or off”
    4. Check the box next to “Telnet Client”
    5. Click OK and wait for installation
  • On macOS:
    1. Install hombrew using Homebrew
    2. Install telnet using hombrew:
      1
      
      brew install telnet
      
  1. Start the chat server
  2. Open PowerShell, or the Terminal and connect using telnet:
1
telnet localhost 9000
  1. Type messages and press Enter to send
  2. Messages appear as plain text on the connected clients.

Testing Multiple Connections

Open multiple Powershell/Terminal windows to simulate different users:

1
2
3
4
5
6
7
8
# User 1
telnet localhost 9000

# User 2
telnet localhost 9000

# User 3
telnet localhost 9000

Each terminal session represents a different chat partusericipant.

Making Your Chat Server Public with ngrok

ngrok creates secure tunnels to localhost, allowing others to access your chat server over the internet.

ngrock establishes a secure tunnel that connects your computer to the internet. When you run a socket server on your computer (localhost), only you can access it from the public Internet. ngrok creates a secure/encrypted connection between the public endpoint and your local machine. It generates a special link that lets anyone on the internet connect to your socket server without assigning a public IP address for your actual computer. ngrock will then forward the requests to your socket server.

For example:

  • Your chat server runs on: localhost:9000 (only you can access it on your computer)
  • ngrok creates: tcp://2.tcp.ngrok.io:19842 (anyone can access on the Internet)
  1. Install ngrok
  • Sign up for a free account on ngrok with GitHub.
  • Download and install ngrok
    • Windows: Download a standalone executable.
    • macOS: Download a standalone executable or install it using Homebrew: brew install ngrok.
  • Follow the installation instructions to configure it:
1
ngrok config add-authtoken YOUR_TOKEN_HERE
  1. Start your chat server on port 9000
  2. Create tunnel with ngrok:
1
ngrok tcp 9000
  1. ngrok displays a forwarding address like:
Forwarding tcp://2.tcp.ngrok.io:19842 -> localhost:9000

Copy the address. Now anyone can connect to your chat server using teh generated domain and port number:

1
telnet 2.tcp.ngrok.io 19842

ngrok demo

Connect using a different computer or smart phone

To connect from mobile devices, you need a telnet client. Most Secure Shell (SSH) clients support telnet.

Android

  1. You may install ConnectBot, JuiceSSH, or Termius from the Google Play Store.
  2. Create a new connection:
    • Host: Your ngrok address (e.g., 2.tcp.ngrok.io)
    • Port: Your ngrok port (e.g., 19842)
    • Type: Telnet
  3. Connect and start sending messages to the multi-user Java Socket chat system

iPhone/iPad

  1. Install Termius or iTerminal from App Store.
  2. Add new host:
    • Host: Your ngrok address
    • Port: Your ngrok port
    • Connect using: Telnet
  3. Tap connect to join the chat TCP socket

Note: The ngrok URL changes each time you restart ngrok, so you’ll need to update the connection details on your mobile devices accordingly.

iTerminal

Telnet via iTerminal app

Termius

Telnet via Termius app


Socket Timeouts

Socket timeouts are crucial for preventing indefinite blocking operations. Java provides two types of timeouts:

  1. Connection Timeout: Maximum time to wait for connection establishment
  2. Read Timeout: Maximum time to wait for data to be received

Example: Socket Timeouts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Socket socket = new Socket();
// Set connection timeout to 5 seconds
socket.connect(new InetSocketAddress(host, port), 9000);

// Set read timeout to 10 seconds
socket.setSoTimeout(10000);

try {
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(socket.getInputStream()));
    String response = reader.readLine(); 
    // Should throw SocketTimeoutException after 10 seconds
} catch (SocketTimeoutException e) {
    System.out.println("Read timed out");
}

URLs and Exceptions

Java provides the URL class for working with URLs and several exceptions for handling network-related errors:

Common Networking Exceptions:

  • UnknownHostException: Host name cannot be resolved
  • ConnectException: Connection refused
  • SocketTimeoutException: Operation timed out
  • SocketException: General socket error
  • MalformedURLException: Invalid URL format

URL Example:

1
2
3
4
5
6
7
8
9
try {
    URL url = new URL("https://api.example.com/data");
    System.out.println("Protocol: " + url.getProtocol());
    System.out.println("Host: " + url.getHost());
    System.out.println("Path: " + url.getPath());
    System.out.println("Query: " + url.getQuery());
} catch (MalformedURLException e) {
    System.out.println("Invalid URL: " + e.getMessage());
}

URLConnection vs HttpClient

URLConnection (Legacy API)

  • Part of original Java networking API
  • More verbose and less features
  • Synchronous only
  • Still supported but not recommended for new code

URLConnection Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
URL url = new URL("https://api.example.com/data");
URLConnection conn = url.openConnection();
conn.setRequestProperty("Accept", "application/json");

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(conn.getInputStream()))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

HttpClient (Modern API)

  • Introduced in Java 11
  • Support for HTTP/2
  • Synchronous and asynchronous operations
  • Better API design
  • Support for websockets
  • Recommended for new code

HttpClient Example

Below is an example on how to fetch data from the Wikipedia API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;


public class Wikipedia {
    public static String getWikipediaInformation(String diseaseName) throws IOException {
        final String API_URL = "https://en.wikipedia.org/api/rest_v1/page/summary/";
        // Create a new HttpClient
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        // Create a new HttpRequest
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(API_URL + diseaseName))
                .header("Accept", "application/json")
                .GET()
                .build();
        // Send request and get response
        try {
            HttpResponse<String> response = client.send(request,
                    HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() != 200) {
                throw new IOException("Failed to fetch data from Wikipedia: " + response.statusCode());
            }
            // Parse the JSON response using the third-party library org.json
            return new org.json.JSONObject(response.body()).getString("extract");
        } catch (IOException | InterruptedException e) {
            System.out.println("Error: " + e.getMessage());
        }
        return null;
    }
}

Which One to Use HttpClient or URLConnection?

  • Use HttpClient if:
    • You’re starting a new project
    • You need HTTP/2 support
    • You need async operations
    • You want a modern, well-designed API
  • Use URLConnection if:
    • You must maintain legacy code
    • You need to support very old Java versions
    • You have specific requirements tied to URLConnection

The HttpClient API is the modern, recommended way to make HTTP requests in Java. It provides a more robust, feature-rich, and developer-friendly interface compared to the older URLConnection API.