Socket programming forms the backbone of network communication in modern applications. Whether you're building a chat application, a multiplayer game, or a distributed system, understanding how to work with sockets is essential. This blog will introduce you to the fundamentals of socket programming in Java, with a special focus on UDP communication.
Understanding Sockets: IP Address + Port
A socket represents an endpoint for network communication. Think of it as a door through which your application sends and receives data over a network. Every socket is uniquely identified by two components:
IP Address: This identifies the machine on the network. It's like a street address that tells you which house (computer) to deliver the message to. IPv4 addresses look like 192.168.1.100, while IPv6 addresses are longer and use hexadecimal notation.
Port Number: This identifies the specific application or service on that machine. If the IP address is the street address, the port number is like the apartment number. Ports range from 0 to 65535, with ports 0-1023 reserved for well-known services (like HTTP on port 80).
Together, an IP address and port number form a socket address. For example, 192.168.1.100:8080 represents a socket at IP address 192.168.1.100 on port 8080.
TCP vs UDP: Choosing Your Protocol
When working with sockets, you'll primarily use two transport protocols: TCP (Transmission Control Protocol) and UDP (User Datagram Protocol). Understanding their differences is crucial for choosing the right one for your application.
TCP (Transmission Control Protocol):
Connection-oriented: Establishes a connection before sending data
Reliable: Guarantees delivery of all packets in the correct order
Error checking: Automatically detects and retransmits lost packets
Flow control: Adjusts transmission speed based on network conditions
Higher overhead: More network traffic due to acknowledgments and connection management
Use cases: Web browsing, email, file transfers, any application where data integrity is critical
UDP (User Datagram Protocol):
Connectionless: No connection establishment, just send data
Unreliable: No guarantee of delivery or order
No error recovery: Lost packets are not retransmitted
No flow control: Sends at whatever rate the application specifies
Lower overhead: Minimal protocol overhead
Use cases: Live video streaming, online gaming, DNS queries, IoT sensors, any application where speed matters more than reliability
The choice between TCP and UDP depends on your application's requirements. If you need every byte to arrive correctly and in order, use TCP. If you need low latency and can tolerate some packet loss, UDP is your friend.
Deep Dive into UDP Socket Programming
Now let's focus on UDP programming in Java. UDP's simplicity makes it an excellent starting point for understanding socket programming concepts. We'll build several programs that demonstrate different aspects of UDP communication.
Basic UDP Server
Let's start with a simple UDP server that listens for messages and echoes them back to the sender:
import java.net.*;
import java.io.*;
public class UDPServer {
private DatagramSocket socket;
private boolean running;
private byte[] buffer = new byte[256];
public UDPServer(int port) throws SocketException, UnknownHostException {
InetAddress localhost = InetAddress.getByName("127.0.0.1");
socket = new DatagramSocket(port, localhost);
System.out.println("UDP Server started on " + localhost.getHostAddress() + ":" + port);
System.out.println("NOTE: Server is bound to localhost only. Connections from other machines will be refused.");
}
public void run() {
running = true;
while (running) {
try {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
InetAddress clientAddress = packet.getAddress();
int clientPort = packet.getPort();
String received = new String(packet.getData(), 0, packet.getLength());
System.out.println("Received from " + clientAddress + ":" + clientPort + " - " + received);
String response = "Echo: " + received;
byte[] responseData = response.getBytes();
DatagramPacket responsePacket = new DatagramPacket(
responseData,
responseData.length,
clientAddress,
clientPort
);
socket.send(responsePacket);
buffer = new byte[256];
} catch (IOException e) {
System.err.println("Error in server: " + e.getMessage());
}
}
}
public void stop() {
running = false;
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
public static void main(String[] args) {
int port = 5000;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Invalid port number. Using default port 5000.");
}
}
try {
UDPServer server = new UDPServer(port);
System.out.println("\n=== UDP Echo Server ===");
System.out.println("Server is running on localhost only for security.");
System.out.println("Clients must connect to 127.0.0.1:" + port);
System.out.println("Press Ctrl+C to stop the server.\n");
server.run();
} catch (SocketException e) {
System.err.println("Could not start server: " + e.getMessage());
} catch (UnknownHostException e) {
System.err.println("Could not bind to localhost: " + e.getMessage());
}
}
}
This server demonstrates several key concepts:
DatagramSocket: The core class for UDP communication. It's bound to a specific port where it listens for incoming packets.
DatagramPacket: Represents a UDP packet. For receiving, we create a packet with a buffer to hold incoming data. For sending, we create a packet with data, destination address, and port.
Blocking receive(): The receive() method blocks until a packet arrives. This is why we run it in a loop.
Stateless communication: Each packet is independent. The server doesn't maintain any connection state between packets.
Basic UDP Client
Now let's create a client that can send messages to our server:
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class UDPClient {
private DatagramSocket socket;
private InetAddress serverAddress;
private int serverPort;
private byte[] buffer;
public UDPClient(String serverHost, int serverPort) throws SocketException, UnknownHostException {
socket = new DatagramSocket();
this.serverAddress = InetAddress.getByName(serverHost);
this.serverPort = serverPort;
System.out.println("UDP Client ready to send to " + serverHost +
" (" + this.serverAddress.getHostAddress() + "):" + serverPort);
if (this.serverAddress.isLoopbackAddress()) {
System.out.println("NOTE: Connecting to localhost. This is perfect for learning!");
} else {
System.out.println("WARNING: Connecting to external host. Make sure firewall allows UDP on port " + serverPort);
}
}
public void sendMessage(String message) throws IOException {
buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(
buffer,
buffer.length,
serverAddress,
serverPort
);
socket.send(packet);
System.out.println("Sent: " + message);
buffer = new byte[256];
packet = new DatagramPacket(buffer, buffer.length);
socket.setSoTimeout(5000);
try {
socket.receive(packet);
String response = new String(packet.getData(), 0, packet.getLength());
System.out.println("Received: " + response);
} catch (SocketTimeoutException e) {
System.out.println("No response received (timeout)");
if (!serverAddress.isLoopbackAddress()) {
System.out.println("TIP: If connecting to external server, check firewall settings.");
}
}
}
public void close() {
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
public static void main(String[] args) {
String serverHost = "localhost";
int serverPort = 5000;
if (args.length >= 1) {
serverHost = args[0];
}
if (args.length >= 2) {
try {
serverPort = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
System.err.println("Invalid port number. Using default port 5000.");
}
}
System.out.println("\n=== UDP Echo Client ===");
System.out.println("For learning, it's recommended to use 'localhost' or '127.0.0.1'");
System.out.println("To connect to external servers, use their IP address or hostname");
System.out.println("Current target: " + serverHost + ":" + serverPort + "\n");
try {
UDPClient client = new UDPClient(serverHost, serverPort);
Scanner scanner = new Scanner(System.in);
System.out.println("Enter messages to send (type 'quit' to exit):");
while (true) {
System.out.print("> ");
String message = scanner.nextLine();
if ("quit".equalsIgnoreCase(message)) {
break;
}
try {
client.sendMessage(message);
} catch (IOException e) {
System.err.println("Error sending message: " + e.getMessage());
}
}
scanner.close();
client.close();
System.out.println("Client shutdown.");
} catch (SocketException e) {
System.err.println("Could not create client: " + e.getMessage());
} catch (UnknownHostException e) {
System.err.println("Unknown host: " + e.getMessage());
System.err.println("TIP: Use 'localhost' or '127.0.0.1' for local testing.");
}
}
}
Key points about the client:
No connection establishment: Unlike TCP, we don't "connect" to the server. We just send packets to its address.
Timeout handling: UDP doesn't guarantee delivery, so we set a timeout when waiting for responses.
Port assignment: The client doesn't specify a port for itself; the OS assigns one automatically.
Advanced Example: UDP Chat Application
Let's create a more sophisticated example - a multi-user chat application using UDP. This will demonstrate broadcasting and more complex packet handling:
import java.net.*;
import java.io.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class UDPChatServer {
private DatagramSocket socket;
private boolean running;
private byte[] buffer = new byte[1024];
private Map<String, ClientInfo> clients = new HashMap<>();
private static final long CLIENT_TIMEOUT = 30000;
private static class ClientInfo {
InetAddress address;
int port;
String username;
long lastSeen;
ClientInfo(InetAddress address, int port, String username) {
this.address = address;
this.port = port;
this.username = username;
this.lastSeen = System.currentTimeMillis();
}
void updateLastSeen() {
this.lastSeen = System.currentTimeMillis();
}
boolean isActive() {
return (System.currentTimeMillis() - lastSeen) < CLIENT_TIMEOUT;
}
}
private class CleanupThread extends Thread {
public void run() {
while (running) {
try {
Thread.sleep(10000);
removeInactiveClients();
} catch (InterruptedException e) {
break;
}
}
}
}
public UDPChatServer(int port) throws SocketException, UnknownHostException {
InetAddress localhost = InetAddress.getByName("127.0.0.1");
socket = new DatagramSocket(port, localhost);
System.out.println("UDP Chat Server started on " + localhost.getHostAddress() + ":" + port);
System.out.println("IMPORTANT: Server is bound to localhost only.");
System.out.println("Only local clients can connect. For network-wide access, modify binding address.");
System.out.println("----------------------------------------");
startCleanupThread();
}
private void startCleanupThread() {
CleanupThread cleanupThread = new CleanupThread();
cleanupThread.setDaemon(true);
cleanupThread.start();
}
private synchronized void removeInactiveClients() {
Iterator<Map.Entry<String, ClientInfo>> iterator = clients.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ClientInfo> entry = iterator.next();
if (!entry.getValue().isActive()) {
String message = "SYSTEM: " + entry.getValue().username + " has left the chat (timeout)";
broadcastMessage(message, null);
iterator.remove();
}
}
}
public void run() {
running = true;
while (running) {
try {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
InetAddress clientAddress = packet.getAddress();
int clientPort = packet.getPort();
String received = new String(packet.getData(), 0, packet.getLength());
processMessage(received, clientAddress, clientPort);
buffer = new byte[1024];
} catch (IOException e) {
if (running) {
System.err.println("Error in server: " + e.getMessage());
}
}
}
}
private synchronized void processMessage(String message, InetAddress address, int port) {
String clientKey = address.toString() + ":" + port;
if (message.startsWith("JOIN:")) {
handleJoin(message.substring(5), address, port, clientKey);
} else if (message.startsWith("MSG:")) {
handleMessage(message.substring(4), clientKey);
} else if (message.equals("LEAVE")) {
handleLeave(clientKey);
} else if (message.equals("LIST")) {
handleList(address, port);
} else if (message.equals("PING")) {
handlePing(clientKey);
}
}
private void handleJoin(String username, InetAddress address, int port, String clientKey) {
ClientInfo client = new ClientInfo(address, port, username);
clients.put(clientKey, client);
String joinMessage = "SYSTEM: " + username + " has joined the chat!";
broadcastMessage(joinMessage, clientKey);
sendToClient("SYSTEM: Welcome to the chat, " + username + "! Type 'LIST' to see online users.", address, port);
String connectionInfo = address.isLoopbackAddress() ? " (local connection)" : " (external connection)";
System.out.println("Client joined: " + username + " from " + address + ":" + port + connectionInfo);
}
private void handleMessage(String message, String senderKey) {
ClientInfo sender = clients.get(senderKey);
if (sender != null) {
sender.updateLastSeen();
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
String formattedMessage = "[" + timestamp + "] " + sender.username + ": " + message;
broadcastMessage(formattedMessage, null);
System.out.println(formattedMessage);
}
}
private void handleLeave(String clientKey) {
ClientInfo client = clients.remove(clientKey);
if (client != null) {
String leaveMessage = "SYSTEM: " + client.username + " has left the chat.";
broadcastMessage(leaveMessage, clientKey);
System.out.println("Client left: " + client.username);
}
}
private void handleList(InetAddress address, int port) {
StringBuilder sb = new StringBuilder("SYSTEM: Online users:\n");
for (ClientInfo client : clients.values()) {
if (client.isActive()) {
sb.append(" - ").append(client.username).append("\n");
}
}
sendToClient(sb.toString(), address, port);
}
private void handlePing(String clientKey) {
ClientInfo client = clients.get(clientKey);
if (client != null) {
client.updateLastSeen();
sendToClient("PONG", client.address, client.port);
}
}
private synchronized void broadcastMessage(String message, String excludeKey) {
byte[] messageData = message.getBytes();
for (Map.Entry<String, ClientInfo> entry : clients.entrySet()) {
if (!entry.getKey().equals(excludeKey)) {
ClientInfo client = entry.getValue();
sendToClient(message, client.address, client.port);
}
}
}
private void sendToClient(String message, InetAddress address, int port) {
try {
byte[] messageData = message.getBytes();
DatagramPacket packet = new DatagramPacket(
messageData,
messageData.length,
address,
port
);
socket.send(packet);
} catch (IOException e) {
System.err.println("Error sending to client: " + e.getMessage());
}
}
public void stop() {
running = false;
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
public static void main(String[] args) {
int port = 5001;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Invalid port number. Using default port 5001.");
}
}
System.out.println("\n=== UDP Chat Server ===");
System.out.println("Starting server on localhost for safe learning environment...");
System.out.println("To allow connections from other machines:");
System.out.println("1. Modify the code to bind to 0.0.0.0 or your public IP");
System.out.println("2. Configure your firewall to allow UDP port " + port);
System.out.println("3. Share your public IP with clients\n");
try {
UDPChatServer server = new UDPChatServer(port);
System.out.println("Server is ready for connections!");
System.out.println("Press Ctrl+C to stop the server.\n");
server.run();
} catch (SocketException e) {
System.err.println("Could not start server: " + e.getMessage());
System.err.println("TIP: Make sure port " + port + " is not already in use.");
} catch (UnknownHostException e) {
System.err.println("Could not bind to localhost: " + e.getMessage());
}
}
}
import java.net.*;
import java.io.*;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
public class UDPChatClient {
private DatagramSocket socket;
private InetAddress serverAddress;
private int serverPort;
private String username;
private AtomicBoolean running = new AtomicBoolean(false);
private Thread receiveThread;
private Thread pingThread;
private class ReceiveThread extends Thread {
public void run() {
byte[] buffer = new byte[1024];
while (running.get()) {
try {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
String message = new String(packet.getData(), 0, packet.getLength());
if (!message.equals("PONG")) {
System.out.println(message);
}
} catch (SocketTimeoutException e) {
} catch (IOException e) {
if (running.get()) {
System.err.println("Error receiving message: " + e.getMessage());
}
}
}
}
}
private class PingThread extends Thread {
public void run() {
while (running.get()) {
try {
Thread.sleep(15000);
if (running.get()) {
sendMessage("PING");
}
} catch (InterruptedException e) {
break;
} catch (IOException e) {
System.err.println("Error sending ping: " + e.getMessage());
}
}
}
}
public UDPChatClient(String serverHost, int serverPort, String username)
throws SocketException, UnknownHostException {
this.socket = new DatagramSocket();
this.serverAddress = InetAddress.getByName(serverHost);
this.serverPort = serverPort;
this.username = username;
socket.setSoTimeout(1000);
if (this.serverAddress.isLoopbackAddress()) {
System.out.println("Connecting to local server (localhost) - perfect for learning!");
} else {
System.out.println("Connecting to remote server at " + serverHost);
System.out.println("Make sure the server allows external connections and firewall permits UDP port " + serverPort);
}
}
public void connect() throws IOException {
String joinMessage = "JOIN:" + username;
sendMessage(joinMessage);
running.set(true);
startReceiveThread();
startPingThread();
System.out.println("Connected to chat server as: " + username);
System.out.println("Server: " + serverAddress.getHostAddress() + ":" + serverPort);
System.out.println("\nCommands: /list (show users), /quit (exit), or just type to chat");
}
private void startReceiveThread() {
receiveThread = new ReceiveThread();
receiveThread.start();
}
private void startPingThread() {
pingThread = new PingThread();
pingThread.setDaemon(true);
pingThread.start();
}
private void sendMessage(String message) throws IOException {
byte[] buffer = message.getBytes();
DatagramPacket packet = new DatagramPacket(
buffer,
buffer.length,
serverAddress,
serverPort
);
socket.send(packet);
}
public void sendChatMessage(String message) throws IOException {
if (message.trim().isEmpty()) {
return;
}
if (message.equalsIgnoreCase("/list")) {
sendMessage("LIST");
} else if (message.equalsIgnoreCase("/quit")) {
disconnect();
} else {
sendMessage("MSG:" + message);
}
}
public void disconnect() {
if (running.get()) {
running.set(false);
try {
sendMessage("LEAVE");
} catch (IOException e) {
}
try {
if (receiveThread != null) {
receiveThread.join(2000);
}
if (pingThread != null) {
pingThread.interrupt();
pingThread.join(1000);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
}
public static void main(String[] args) {
String serverHost = "localhost";
int serverPort = 5001;
Scanner scanner = new Scanner(System.in);
if (args.length >= 1) {
serverHost = args[0];
}
if (args.length >= 2) {
try {
serverPort = Integer.parseInt(args[1]);
} catch (NumberFormatException e) {
System.err.println("Invalid port number. Using default port 5001.");
}
}
System.out.println("\n=== UDP Chat Client ===");
System.out.println("Default server: " + serverHost + ":" + serverPort);
System.out.println("\nNOTE: For learning, connect to 'localhost' or '127.0.0.1'");
System.out.println(" For network chat, use the server's IP address");
System.out.println(" Example: java UDPChatClient 192.168.1.100 5001\n");
System.out.print("Enter your username: ");
String username = scanner.nextLine().trim();
if (username.isEmpty()) {
System.err.println("Username cannot be empty!");
scanner.close();
return;
}
try {
UDPChatClient client = new UDPChatClient(serverHost, serverPort, username);
client.connect();
while (true) {
String message = scanner.nextLine();
if (message.equalsIgnoreCase("/quit")) {
break;
}
try {
client.sendChatMessage(message);
} catch (IOException e) {
System.err.println("Error sending message: " + e.getMessage());
if (!serverHost.equals("localhost") && !serverHost.equals("127.0.0.1")) {
System.err.println("TIP: Check if the server is reachable and firewall allows UDP traffic.");
}
}
}
client.disconnect();
} catch (SocketException e) {
System.err.println("Could not create client: " + e.getMessage());
} catch (UnknownHostException e) {
System.err.println("Unknown host: " + e.getMessage());
System.err.println("TIP: Use 'localhost' for local testing or verify the server address.");
} catch (IOException e) {
System.err.println("Connection error: " + e.getMessage());
System.err.println("TIP: Make sure the server is running on " + serverHost + ":" + serverPort);
} finally {
scanner.close();
}
System.out.println("Chat client closed.");
}
}
This chat application demonstrates several advanced concepts:
Protocol Design: We've created a simple protocol with message types (JOIN, MSG, LEAVE, LIST, PING).
Client Management: The server tracks active clients and removes inactive ones.
Concurrent Operations: Both client and server handle multiple operations simultaneously using threads.
Keep-Alive Mechanism: Clients send periodic PING messages to stay active.
Broadcasting: The server sends messages to all connected clients.
UDP File Transfer Example
Let's create one more example - a simple file transfer application using UDP. This demonstrates how to handle larger data and implement basic reliability:
import java.net.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class UDPFileTransfer {
private static final byte TYPE_FILE_INFO = 1;
private static final byte TYPE_DATA = 2;
private static final byte TYPE_ACK = 3;
private static final byte TYPE_COMPLETE = 4;
private static final byte TYPE_ERROR = 5;
private static final int MAX_PACKET_SIZE = 1400;
private static final int HEADER_SIZE = 9;
private static final int MAX_DATA_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
private static final int TIMEOUT_MS = 1000;
private static final int MAX_RETRIES = 3;
public static class FileSender {
private DatagramSocket socket;
private InetAddress receiverAddress;
private int receiverPort;
public FileSender() throws SocketException {
this.socket = new DatagramSocket();
socket.setSoTimeout(TIMEOUT_MS);
}
public void sendFile(String filePath, String receiverHost, int receiverPort)
throws IOException, NoSuchAlgorithmException {
this.receiverAddress = InetAddress.getByName(receiverHost);
this.receiverPort = receiverPort;
if (this.receiverAddress.isLoopbackAddress()) {
System.out.println("Sending to localhost - ideal for testing and learning!");
} else {
System.out.println("Sending to remote host: " + receiverHost);
System.out.println("Ensure receiver is listening and firewall allows UDP port " + receiverPort);
}
File file = new File(filePath);
if (!file.exists() || !file.isFile()) {
throw new FileNotFoundException("File not found: " + filePath);
}
String fileHash = calculateFileHash(file);
sendFileInfo(file.getName(), file.length(), fileHash);
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] fileData = new byte[(int) file.length()];
fis.read(fileData);
int totalPackets = (int) Math.ceil((double) fileData.length / MAX_DATA_SIZE);
System.out.println("Sending file: " + file.getName());
System.out.println("Size: " + file.length() + " bytes");
System.out.println("Packets: " + totalPackets);
System.out.println("Destination: " + receiverAddress.getHostAddress() + ":" + receiverPort);
for (int i = 0; i < totalPackets; i++) {
int offset = i * MAX_DATA_SIZE;
int length = Math.min(MAX_DATA_SIZE, fileData.length - offset);
byte[] packetData = new byte[length];
System.arraycopy(fileData, offset, packetData, 0, length);
sendDataPacket(i, totalPackets, packetData);
int progress = (int) (((i + 1) / (double) totalPackets) * 100);
System.out.print("\rProgress: " + progress + "%");
}
System.out.println("\nFile sent successfully!");
sendCompletionPacket();
} finally {
if (fis != null) {
fis.close();
}
}
}
private void sendFileInfo(String fileName, long fileSize, String hash)
throws IOException {
String info = fileName + "|" + fileSize + "|" + hash;
byte[] infoBytes = info.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(1 + infoBytes.length);
buffer.put(TYPE_FILE_INFO);
buffer.put(infoBytes);
sendPacketWithRetry(buffer.array());
}
private void sendDataPacket(int sequence, int total, byte[] data)
throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE + data.length);
buffer.put(TYPE_DATA);
buffer.putInt(sequence);
buffer.putInt(total);
buffer.put(data);
sendPacketWithRetry(buffer.array());
}
private void sendCompletionPacket() throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1);
buffer.put(TYPE_COMPLETE);
sendPacketWithRetry(buffer.array());
}
private void sendPacketWithRetry(byte[] data) throws IOException {
DatagramPacket packet = new DatagramPacket(
data, data.length, receiverAddress, receiverPort
);
for (int retry = 0; retry < MAX_RETRIES; retry++) {
socket.send(packet);
byte[] ackBuffer = new byte[1];
DatagramPacket ackPacket = new DatagramPacket(ackBuffer, ackBuffer.length);
try {
socket.receive(ackPacket);
if (ackBuffer[0] == TYPE_ACK) {
return;
}
} catch (SocketTimeoutException e) {
if (retry == MAX_RETRIES - 1) {
throw new IOException("Failed to receive ACK after " + MAX_RETRIES + " retries");
}
System.out.print(".");
}
}
}
private String calculateFileHash(File file) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
} finally {
if (fis != null) {
fis.close();
}
}
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public void close() {
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
}
public static class FileReceiver {
private DatagramSocket socket;
private Map<Integer, byte[]> receivedPackets = new HashMap<>();
private String expectedFileName;
private long expectedFileSize;
private String expectedHash;
private int totalExpectedPackets;
public FileReceiver(int port) throws SocketException, UnknownHostException {
InetAddress localhost = InetAddress.getByName("127.0.0.1");
this.socket = new DatagramSocket(port, localhost);
System.out.println("File receiver listening on " + localhost.getHostAddress() + ":" + port);
System.out.println("NOTE: Only accepting connections from localhost.");
System.out.println("For network transfers, modify code to bind to all interfaces.\n");
}
public void receiveFile(String outputDirectory) throws IOException, NoSuchAlgorithmException {
byte[] buffer = new byte[MAX_PACKET_SIZE];
boolean receiving = true;
System.out.println("Waiting for file transfer...");
while (receiving) {
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
InetAddress senderAddress = packet.getAddress();
int senderPort = packet.getPort();
ByteBuffer data = ByteBuffer.wrap(packet.getData(), 0, packet.getLength());
byte packetType = data.get();
switch (packetType) {
case TYPE_FILE_INFO:
handleFileInfo(data, senderAddress, senderPort);
break;
case TYPE_DATA:
handleDataPacket(data, senderAddress, senderPort);
break;
case TYPE_COMPLETE:
receiving = false;
sendAck(senderAddress, senderPort);
saveFile(outputDirectory);
break;
default:
System.err.println("Unknown packet type: " + packetType);
}
}
}
private void handleFileInfo(ByteBuffer data, InetAddress sender, int port)
throws IOException {
byte[] infoBytes = new byte[data.remaining()];
data.get(infoBytes);
String info = new String(infoBytes);
String[] parts = info.split("\\|");
expectedFileName = parts[0];
expectedFileSize = Long.parseLong(parts[1]);
expectedHash = parts[2];
System.out.println("Receiving file: " + expectedFileName);
System.out.println("Expected size: " + expectedFileSize + " bytes");
System.out.println("From: " + sender.getHostAddress() + ":" + port);
if (sender.isLoopbackAddress()) {
System.out.println("Source: Local transfer");
} else {
System.out.println("Source: Network transfer from " + sender.getHostAddress());
}
sendAck(sender, port);
}
private void handleDataPacket(ByteBuffer data, InetAddress sender, int port)
throws IOException {
int sequence = data.getInt();
int total = data.getInt();
if (totalExpectedPackets == 0) {
totalExpectedPackets = total;
}
byte[] packetData = new byte[data.remaining()];
data.get(packetData);
receivedPackets.put(sequence, packetData);
int progress = (int) ((receivedPackets.size() / (double) totalExpectedPackets) * 100);
System.out.print("\rProgress: " + progress + "%");
sendAck(sender, port);
}
private void sendAck(InetAddress address, int port) throws IOException {
byte[] ack = {TYPE_ACK};
DatagramPacket packet = new DatagramPacket(ack, ack.length, address, port);
socket.send(packet);
}
private void saveFile(String outputDirectory) throws IOException, NoSuchAlgorithmException {
System.out.println("\nSaving file...");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < totalExpectedPackets; i++) {
byte[] packetData = receivedPackets.get(i);
if (packetData == null) {
throw new IOException("Missing packet: " + i);
}
baos.write(packetData);
}
byte[] fileData = baos.toByteArray();
String receivedHash = calculateHash(fileData);
if (!receivedHash.equals(expectedHash)) {
throw new IOException("File integrity check failed!");
}
File outputDir = new File(outputDirectory);
if (!outputDir.exists()) {
outputDir.mkdirs();
}
File outputFile = new File(outputDir, expectedFileName);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(outputFile);
fos.write(fileData);
} finally {
if (fos != null) {
fos.close();
}
}
System.out.println("File saved: " + outputFile.getAbsolutePath());
System.out.println("File integrity verified!");
}
private String calculateHash(byte[] data) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(data);
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public void close() {
if (socket != null && !socket.isClosed()) {
socket.close();
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
printUsage();
return;
}
String mode = args[0].toLowerCase();
try {
if (mode.equals("send")) {
if (args.length < 4) {
printUsage();
return;
}
String filePath = args[1];
String receiverHost = args[2];
int receiverPort = Integer.parseInt(args[3]);
System.out.println("\n=== UDP File Transfer - Sender Mode ===");
System.out.println("For local testing: use 'localhost' or '127.0.0.1' as receiver host");
System.out.println("For network transfer: use receiver's IP address and ensure firewall allows UDP\n");
FileSender sender = new FileSender();
sender.sendFile(filePath, receiverHost, receiverPort);
sender.close();
} else if (mode.equals("receive")) {
if (args.length < 3) {
printUsage();
return;
}
int port = Integer.parseInt(args[1]);
String outputDir = args[2];
System.out.println("\n=== UDP File Transfer - Receiver Mode ===");
System.out.println("Receiver is bound to localhost for security.");
System.out.println("Only local senders can transfer files to this receiver.");
System.out.println("For network transfers, modify the code to bind to all interfaces.\n");
FileReceiver receiver = new FileReceiver(port);
receiver.receiveFile(outputDir);
receiver.close();
} else {
printUsage();
}
} catch (SocketException e) {
System.err.println("Socket error: " + e.getMessage());
if (e.getMessage() != null && e.getMessage().contains("already in use")) {
System.err.println("TIP: Port is already in use. Try a different port number.");
}
} catch (UnknownHostException e) {
System.err.println("Unknown host: " + e.getMessage());
} catch (IOException e) {
System.err.println("IO error: " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
System.err.println("Algorithm error: " + e.getMessage());
} catch (NumberFormatException e) {
System.err.println("Invalid port number: " + e.getMessage());
} catch (Exception e) {
System.err.println("Unexpected error: " + e.getMessage());
e.printStackTrace();
}
}
private static void printUsage() {
System.out.println("Usage:");
System.out.println(" Send mode: java UDPFileTransfer send <file_path> <receiver_host> <receiver_port>");
System.out.println(" Receive mode: java UDPFileTransfer receive <listen_port> <output_directory>");
System.out.println();
System.out.println("Example (local transfer - recommended for learning):");
System.out.println(" Terminal 1: java UDPFileTransfer receive 5002 ./received/");
System.out.println(" Terminal 2: java UDPFileTransfer send myfile.pdf localhost 5002");
System.out.println();
System.out.println("Example (network transfer):");
System.out.println(" Receiver: java UDPFileTransfer receive 5002 ./received/");
System.out.println(" Sender: java UDPFileTransfer send myfile.pdf 192.168.1.100 5002");
}
}
This file transfer application demonstrates several important concepts:
Packet Structure: We define a protocol with different packet types and headers containing metadata.
Reliability over UDP: We implement acknowledgments and retransmissions to ensure reliable delivery.
File Integrity: We calculate and verify MD5 hashes to ensure the file wasn't corrupted.
Chunking: Large files are split into smaller packets that fit within UDP's size limits.
Progress Tracking: Both sender and receiver show transfer progress.
Best Practices and Considerations
When working with UDP sockets in Java, keep these important points in mind:
1. Packet Size Limitations UDP packets have a theoretical maximum size of 65,535 bytes, but in practice, you should keep packets much smaller. The recommended maximum is around 1,400 bytes to avoid fragmentation on most networks.
2. Message Ordering UDP doesn't guarantee packet order. If order matters, include sequence numbers in your protocol (as we did in the file transfer example).
3. Packet Loss Handling Always design your application to handle packet loss gracefully. This might mean:
Implementing acknowledgments and retransmissions for critical data
Using timeouts when waiting for responses
Designing protocols that can tolerate some data loss
4. Security Considerations UDP has no built-in security. Consider:
Implementing authentication mechanisms
Encrypting sensitive data before transmission
Validating all incoming data to prevent injection attacks
5. Resource Management
Always close sockets when done to free system resources
Use try-with-resources or finally blocks to ensure cleanup
Be mindful of thread safety when multiple threads access the same socket
6. Testing and Debugging
Test with realistic network conditions (latency, packet loss)
Use tools like Wireshark to inspect UDP traffic
Implement logging for debugging distributed systems
UDP socket programming in Java provides a powerful foundation for building high-performance networked applications. While it requires more careful design than TCP to handle reliability concerns, its low overhead and simplicity make it ideal for many use cases.
The examples we've covered - from basic echo servers to chat applications and file transfer systems - demonstrate the flexibility of UDP. By understanding these patterns and best practices, you can build robust applications that leverage UDP's strengths while mitigating its limitations.
Remember that the choice between TCP and UDP isn't about which is "better" - it's about which fits your specific requirements. UDP shines in scenarios where low latency matters more than guaranteed delivery, where you're multicasting to multiple recipients, or where you want fine-grained control over reliability mechanisms.
May your packets always find their destination!