Java Network Programming: TCP/IP Communication and Socket Implementation
Computer networks enable connectivity across diverse hardware and operating systems. To facilitate reliable data exchange, standardized communication rules—known as network protocols—govern transmission formats, rates, and procedures. Key protocols include IP (Internet Protocol) at the network layer, TCP (Transmission Control Protocol) and UDP (User Datagram Protocol) at the transport layer, and application-layer protocols like HTTP, FTP, and SMTP.
The TCP/IP protocol suite organizes network architecture into four hierarchical layers:
- Link Layer: Handles physical network interfaces and hardware-level data transmission through device drivers and network interface cards.
- Network Layer: Manages logical addressing and packet routing across networks. IP operates at this layer, identifying hardware resources uniquely.
- Transport Layer: Controls end-to-end data transmission, quality assurance, and destination targeting. TCP and UDP function here, utilizing Socket endpoints defined by IP addresses and port numbers.
- Application Layer: Supports software-level communication through protocols like HTTP, FTP, SMTP, and Telnet.
TCP provides connection-orientde communication, establishing logical links before data transfer through a three-way handshake process: (1) client requests connection, (2) server acknowledges, (3) client confirms. This ensures reliable, ordered delivery suitable for file transfers and critical data.
UDP offers connectionless transmission without establishing prior links. Senders transmit without confirming receiver availability, and receivers process packets without acknowledgment. While UDP sacrifices reliability for speed and efficiency, it suits real-time applications like video streaming and VoIP where occasionla packet loss is acceptable.
IPv4 addresses uniquely identify network devices using 32-bit binary values typically expressed as four decimal octets (0-255), such as 192.168.1.1. Addresses divide into network and host portions, categorized into five classes (A-E) based on address ranges. The 127.0.0.1 loopback address facilitates local testing.
Port numbers distinguish services on a single host, functioning like room numbers in a building. Represented as 16-bit integers (0-65535), ports 0-1023 remain reserved for system services (e.g., HTTP/80, SSH/22). Applications should utilize ports above 1023.
Java's InetAddress class encapsulates IP address manipulation:
import java.net.InetAddress;
public class AddressDemo {
public static void main(String[] args) throws Exception {
InetAddress local = InetAddress.getLocalHost();
InetAddress remote = InetAddress.getByName("www.example.com");
System.out.println("Local IP: " + local.getHostAddress());
System.out.println("Remote IP: " + remote.getHostAddress());
System.out.println("Reachable: " + remote.isReachable(3000));
}
}
The URL class represents resource locators with the structure: protocol://host:port/path#fragment. Java enables URL instantiation and stream reading:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
public class UrlReader {
public static void main(String[] args) throws Exception {
URL endpoint = new URL("http", "www.example.com", 80, "/index.html");
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(endpoint.openStream()))) {
reader.lines().forEach(System.out::println);
}
}
}
Java implements TCP through ServerSocket (server-side) and Socket (client-side) classes. Servers instantiate ServerSocket with a port, awaiting connections via accept(). Clients create Socket instances targeting host-port combinations.
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
try (ServerSocket listener = new ServerSocket(9090)) {
System.out.println("Server active on port 9090");
Socket connection = listener.accept();
OutputStream out = connection.getOutputStream();
out.write("Connection established".getBytes());
}
}
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws Exception {
try (Socket conn = new Socket("localhost", 9090);
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
System.out.println("Server response: " + in.readLine());
}
}
}
Production servers handle multiple clients simultaneously through multithreading:
import java.io.*;
import java.net.*;
public class MultiThreadedServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket client = server.accept();
new Thread(new ClientHandler(client)).start();
}
}
}
class ClientHandler implements Runnable {
private Socket client;
public ClientHandler(Socket s) { this.client = s; }
public void run() {
try (PrintWriter out = new PrintWriter(client.getOutputStream(), true)) {
out.println("Thread ID: " + Thread.currentThread().getId());
} catch (IOException e) { e.printStackTrace(); }
}
}
UDP utilizes DatagramPacket (data containers) and DatagramSocket (communication endpoints). Unlike TCP, UDP transmits packets without connection establishment.
import java.net.*;
public class UdpReceiver {
public static void main(String[] args) throws Exception {
byte[] buffer = new byte[1024];
DatagramSocket socket = new DatagramSocket(5555);
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("Awaiting transmission...");
socket.receive(packet);
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.printf("Received from %s:%d - %s%n",
packet.getAddress().getHostAddress(), packet.getPort(), msg);
socket.close();
}
}
import java.net.*;
public class UdpSender {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
String payload = "UDP transmission test";
byte[] data = payload.getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,
InetAddress.getByName("localhost"), 5555);
socket.send(packet);
socket.close();
}
}
Chat applications require simultaneous send/receive capabilities:
import java.io.*;
import java.net.*;
import java.util.Scanner;
public class UdpChat {
public static void main(String[] args) {
new Thread(new ReceiverTask(7777)).start();
new Thread(new SenderTask(7777)).start();
}
}
class ReceiverTask implements Runnable {
private int port;
public ReceiverTask(int p) { this.port = p; }
public void run() {
try (DatagramSocket sock = new DatagramSocket(port)) {
byte[] buf = new byte[512];
while (true) {
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
sock.receive(pkt);
String txt = new String(pkt.getData(), 0, pkt.getLength());
System.out.println("Peer: " + txt);
}
} catch (IOException e) { e.printStackTrace(); }
}
}
class SenderTask implements Runnable {
private int targetPort;
public SenderTask(int p) { this.targetPort = p; }
public void run() {
try (DatagramSocket sock = new DatagramSocket();
Scanner scanner = new Scanner(System.in)) {
while (true) {
String line = scanner.nextLine();
if ("exit".equalsIgnoreCase(line)) break;
byte[] bytes = line.getBytes();
DatagramPacket pkt = new DatagramPacket(bytes, bytes.length,
InetAddress.getByName("localhost"), targetPort);
sock.send(pkt);
}
} catch (IOException e) { e.printStackTrace(); }
}
}