javanetworkingsockets

Network Programming with Java Sockets: A Practical Walkthrough

Java's socket API was the first time network programming felt accessible. Here is a complete walkthrough of what we built at Motorola.

·5 min read

Network Programming with Java Sockets: A Practical Walkthrough

Before Java, network programming was POSIX sockets in C. It worked, but it required managing buffers, handling platform differences, and dealing with raw file descriptors. Java's java.net package wrapped all of this in an object-oriented API that was genuinely easier to use. When I first wrote a working TCP server in Java in 1996, it took a quarter of the code I had written in C.

At Motorola we used Java sockets extensively for device communication, inter-service messaging, and the heartbeat protocol described elsewhere. This is the walkthrough I wish I had had.

TCP Client

The simplest useful thing: connect to a server, send a request, read the response.

import java.io.*;
import java.net.*;

public class NmsClient {
    private final String host;
    private final int    port;

    public NmsClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public String sendCommand(String command) throws IOException {
        try (Socket        socket = new Socket(host, port);
             PrintWriter   out    = new PrintWriter(socket.getOutputStream(), true);
             BufferedReader in    = new BufferedReader(
                                       new InputStreamReader(socket.getInputStream()))) {

            out.println(command);
            return in.readLine(); // read one line of response
        }
    }

    public static void main(String[] args) throws IOException {
        NmsClient client = new NmsClient("192.168.1.1", 9000);
        String response = client.sendCommand("GET_STATUS");
        System.out.println("Response: " + response);
    }
}

The try-with-resources pattern (which did not exist in 1997 — we used finally blocks) ensures the socket closes even on exception. PrintWriter with autoFlush=true sends the line immediately.

TCP Server: Single-Threaded

A server that handles one connection at a time:

import java.io.*;
import java.net.*;

public class SingleThreadedServer {
    private final int port;

    public SingleThreadedServer(int port) {
        this.port = port;
    }

    public void start() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Listening on port " + port);
            while (true) {
                Socket client = serverSocket.accept(); // blocks until connection
                handleConnection(client);              // handles, then loops
            }
        }
    }

    private void handleConnection(Socket client) throws IOException {
        try (BufferedReader in  = new BufferedReader(
                                      new InputStreamReader(client.getInputStream()));
             PrintWriter    out = new PrintWriter(client.getOutputStream(), true)) {

            String line;
            while ((line = in.readLine()) != null) {
                String response = processCommand(line);
                out.println(response);
            }
        }
    }

    private String processCommand(String command) {
        if ("GET_STATUS".equals(command)) return "OK";
        if ("PING".equals(command))       return "PONG";
        return "UNKNOWN_COMMAND";
    }
}

The problem is obvious: while handling one connection, all others wait. For a monitoring system with many concurrent connections this is unusable.

TCP Server: Multi-Threaded

Spawn a thread per connection. Each client gets its own thread and executes concurrently:

public class MultiThreadedServer {
    private final int port;

    public MultiThreadedServer(int port) {
        this.port = port;
    }

    public void start() throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Listening on port " + port);
            while (true) {
                Socket client = serverSocket.accept();
                new Thread(() -> handleConnection(client)).start();
            }
        }
    }

    private void handleConnection(Socket client) {
        try (BufferedReader in  = new BufferedReader(
                                      new InputStreamReader(client.getInputStream()));
             PrintWriter    out = new PrintWriter(client.getOutputStream(), true)) {

            String line;
            while ((line = in.readLine()) != null) {
                out.println(processCommand(line));
            }
        } catch (IOException e) {
            // client disconnected — normal
        }
    }

    private String processCommand(String cmd) {
        return "OK:" + cmd;
    }
}

This worked for our scale. A thread-per-connection model breaks down above a few thousand concurrent connections, but in 1997 that was not our problem.

UDP for SNMP Traps

SNMP traps use UDP — fire and forget. No connection, no acknowledgement. Java's DatagramSocket handles this:

public class TrapReceiver {
    private final DatagramSocket socket;
    private final int            bufferSize = 65535;

    public TrapReceiver(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }

    public void start() {
        byte[] buf = new byte[bufferSize];
        System.out.println("Waiting for SNMP traps...");
        while (true) {
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            try {
                socket.receive(packet);
                String  src  = packet.getAddress().getHostAddress();
                byte[]  data = packet.getData();
                int     len  = packet.getLength();
                processTrap(src, data, len);
            } catch (IOException e) {
                System.err.println("Error receiving trap: " + e.getMessage());
            }
        }
    }

    private void processTrap(String source, byte[] data, int length) {
        // Parse SNMP PDU from data[0..length]
        System.out.printf("Trap from %s (%d bytes)%n", source, length);
    }
}

Timeouts

Always set a socket timeout. Without one, a read blocks forever if the remote side stops sending:

Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000); // 5s connect timeout
socket.setSoTimeout(10000); // 10s read timeout

// Now reads will throw SocketTimeoutException after 10 seconds

We learnt this the hard way. A device that crashed mid-response left our polling thread blocked indefinitely until someone noticed and restarted the JVM.

What Java Sockets Gave Us

The java.net API eliminated the error-prone buffer management that made C socket code fragile. Streams handled character encoding. The exception model made error handling explicit. The cost was abstraction — when something went wrong at the network layer, the Java exception was less informative than the raw errno.

For application-level networking — which is most of what developers need — Java sockets were a genuine improvement.