Java Networking
Java Networking Fundamentals
Section titled “Java Networking Fundamentals”Network programming involves writing applications that communicate across different devices connected via a network.
The java.net package provides core classes and interfaces for network communication in Java, handling low-level details so you can focus on application logic.
Key protocols supported by java.net:
- TCP (Transmission Control Protocol): Provides reliable, ordered, connection-oriented communication between applications. Commonly used with IP (Internet Protocol), known as TCP/IP. Ideal for applications requiring guaranteed delivery, like web browsing (HTTP), file transfer (FTP), and email (SMTP).
- UDP (User Datagram Protocol): Provides connectionless, unreliable communication using datagram packets. Faster than TCP but doesn’t guarantee delivery or order. Suitable for applications where speed is critical and occasional data loss is acceptable, like streaming media or online gaming.
This tutorial focuses on:
- Socket Programming: The foundation for TCP-based client-server communication.
- URL Processing: Working with Uniform Resource Locators (URLs) to access web resources (covered separately).
Socket Programming (TCP)
Section titled “Socket Programming (TCP)”Sockets provide the endpoints for communication between two machines using TCP. A client application creates a Socket and attempts to connect to a server’s ServerSocket listening on a specific IP address and port.
Once connected, the server accepts the connection, creating its own Socket. Client and server can then exchange data using Input/Output (I/O) streams associated with their respective sockets.
Key classes:
java.net.Socket: Represents one end of a TCP connection (used by both client and server after connection).java.net.ServerSocket: Used by the server to listen for incoming client connection requests on a specific port.java.net.InetAddress: Represents an IP address.java.net.InetSocketAddress: Represents an IP address and port number.
Establishing a TCP Connection:
- Server: Creates a
ServerSocketinstance, binding it to a specific port number. - Server: Calls the
accept()method on theServerSocket. This method blocks (waits) until a client requests a connection. - Client: Creates a
Socketinstance, specifying the server’s hostname/IP address and port number. - Client: The
Socketconstructor attempts to establish a connection to the server. If successful, the client has aSocketready for communication. - Server: When a connection is established,
accept()returns a newSocketobject representing the server’s end of the specific connection with that client. The server typically handles this client connection in a separate thread to allow theServerSocketto listen for other clients.
After connection, each Socket provides an InputStream to receive data and an OutputStream to send data. The client’s OutputStream is connected to the server’s InputStream, and vice versa, enabling two-way communication.
Try-with-Resources for Sockets and Streams
Section titled “Try-with-Resources for Sockets and Streams”Sockets and their associated streams are resources that must be closed properly to release system resources and avoid leaks. The try-with-resources statement (Java 7+) ensures that resources declared within it are automatically closed, even if exceptions occur.
// Example using try-with-resources for a client socket and streamstry (Socket clientSocket = new Socket(serverName, port); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
// Communicate using 'out' and 'in' out.println("Hello Server!"); String response = in.readLine(); System.out.println("Server says: " + response);
} catch (IOException e) { System.err.println("Communication error: " + e.getMessage());}// clientSocket, out, and in are automatically closed hereServerSocket Class Methods
Section titled “ServerSocket Class Methods”Used by servers to listen for client connections.
Common Constructors:
| Constructor | Description |
|---|---|
public ServerSocket(int port) | Creates a server socket bound to the specified port. Throws IOException if the port is already in use. |
public ServerSocket(int port, int backlog) | Similar to above, but backlog suggests a queue length for incoming connections waiting to be accepted. |
public ServerSocket(int port, int backlog, InetAddress bindAddr) | Binds to a specific local IP address (bindAddr) and port. Useful for multi-homed servers. |
Common Methods:
| Method | Description |
|---|---|
public Socket accept() | Listens for a connection request and accepts it. Blocks until a connection is made. Returns a Socket object for communicating with the connected client. Crucially, this returned Socket should be handled (e.g., in a new thread) so the server can call accept() again for other clients. |
public void bind(SocketAddress endpoint) | Binds the ServerSocket to a specific address (IP and port) if created unbound. |
public int getLocalPort() | Returns the port number this server socket is listening on. |
public void setSoTimeout(int timeout) | Sets a timeout (in milliseconds) for the accept() method. If no connection occurs within the timeout, a SocketTimeoutException is thrown. |
public void close() | Closes the server socket. Should typically be handled by try-with-resources or a finally block. |
Socket Class Methods
Section titled “Socket Class Methods”Represents a single endpoint of a TCP connection.
Common Constructors (Used by Clients):
| Constructor | Description |
|---|---|
public Socket(String host, int port) | Creates a socket and connects it to the specified remote host and port. Throws UnknownHostException or IOException on failure. |
public Socket(InetAddress address, int port) | Connects to the specified InetAddress and port. |
public Socket() | Creates an unconnected socket. Use connect() later to establish a connection. |
Common Methods (Used by Clients and Servers):
| Method | Description |
|---|---|
public void connect(SocketAddress endpoint, int timeout) | Connects this socket to the server specified by endpoint with a connection timeout. |
public InputStream getInputStream() | Returns an InputStream to read data coming from the other end of the connection. |
public OutputStream getOutputStream() | Returns an OutputStream to write data to the other end of the connection. |
public InetAddress getInetAddress() | Gets the InetAddress of the remote endpoint. |
public int getPort() | Gets the port number of the remote endpoint. |
public SocketAddress getRemoteSocketAddress() | Gets the SocketAddress (IP and port) of the remote endpoint. |
public SocketAddress getLocalSocketAddress() | Gets the SocketAddress (IP and port) of the local endpoint. |
public boolean isConnected() | Returns true if the socket is connected. |
public boolean isClosed() | Returns true if the socket has been closed. |
public void close() | Closes the socket and its streams. Should typically be handled by try-with-resources. |
InetAddress Class Methods
Section titled “InetAddress Class Methods”Represents an IP address (both IPv4 and IPv6).
| Method | Description |
|---|---|
static InetAddress getByName(String host) | Determines the IP address of a host, given the host’s name (performs DNS lookup). |
static InetAddress getLocalHost() | Returns the InetAddress object representing the local host. |
static InetAddress[] getAllByName(String host) | Returns an array of all IP addresses associated with a host name. |
String getHostName() | Gets the host name for this IP address (may trigger reverse DNS lookup). |
String getHostAddress() | Returns the IP address string in textual presentation. |
Modern Client Example (Using Try-with-Resources and Character Streams)
Section titled “Modern Client Example (Using Try-with-Resources and Character Streams)”This client connects, sends a message, and reads the response using PrintWriter and BufferedReader for text-based communication.
// File: SimpleClient.javaimport java.net.*;import java.io.*;
public class SimpleClient { public static void main(String[] args) { if (args.length < 2) { System.out.println("Usage: java SimpleClient <server-host> <port>"); return; } String serverName = args[0]; int port = Integer.parseInt(args[1]);
System.out.println("Connecting to " + serverName + " on port " + port);
try (Socket clientSocket = new Socket(serverName, port); // Auto-flush enabled for PrintWriter PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {
System.out.println("Connected to " + clientSocket.getRemoteSocketAddress());
String message = "Hello from " + clientSocket.getLocalSocketAddress(); System.out.println("Sending: " + message); out.println(message); // Send message to server
String response = in.readLine(); // Read response from server System.out.println("Server says: " + response);
} catch (UnknownHostException e) { System.err.println("Don't know about host " + serverName); } catch (IOException e) { System.err.println("Couldn't get I/O for the connection to " + serverName + ": " + e.getMessage()); } // Sockets and streams are automatically closed here by try-with-resources System.out.println("Connection closed."); }}Modern Server Example (Using Try-with-Resources and Threads)
Section titled “Modern Server Example (Using Try-with-Resources and Threads)”This server listens for connections. For each client, it creates a new thread to handle communication, allowing the main thread to continue listening for new clients. It uses BufferedReader and PrintWriter.
// File: SimpleServer.javaimport java.net.*;import java.io.*;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class SimpleServer { public static void main(String[] args) { if (args.length < 1) { System.out.println("Usage: java SimpleServer <port>"); return; } int port = Integer.parseInt(args[0]); // Using a fixed thread pool for handling clients is often better than creating unbounded threads ExecutorService clientHandlerPool = Executors.newFixedThreadPool(10);
// Use try-with-resources for the ServerSocket try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server listening on port " + port + "...");
while (true) { // Keep listening indefinitely try { Socket clientSocket = serverSocket.accept(); // Wait for a client connection System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress());
// Handle client connection in a separate thread from the pool clientHandlerPool.submit(new ClientHandler(clientSocket));
} catch (IOException e) { System.err.println("Error accepting client connection: " + e.getMessage()); // Depending on the error, might want to break or continue } } } catch (IOException e) { System.err.println("Could not listen on port " + port + ": " + e.getMessage()); } finally { clientHandlerPool.shutdown(); // Shut down the thread pool when server exits } }}
// Runnable task to handle a single client connectionclass ClientHandler implements Runnable { private final Socket clientSocket;
public ClientHandler(Socket socket) { this.clientSocket = socket; }
@Override public void run() { // Use try-with-resources for client socket streams try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
String inputLine; if ((inputLine = in.readLine()) != null) { // Read message from client System.out.println("Received from " + clientSocket.getRemoteSocketAddress() + ": " + inputLine); // Send response back to client out.println("Thank you for connecting to " + clientSocket.getLocalSocketAddress() + " Goodbye!"); } } catch (IOException e) { System.err.println("Error handling client " + clientSocket.getRemoteSocketAddress() + ": " + e.getMessage()); } finally { try { clientSocket.close(); // Ensure client socket is closed System.out.println("Closed connection from " + clientSocket.getRemoteSocketAddress()); } catch (IOException e) { System.err.println("Error closing client socket: " + e.getMessage()); } } }}Running the Examples:
- Compile both
SimpleServer.javaandSimpleClient.java. - Start the server:
java SimpleServer 6066 - Start the client:
java SimpleClient localhost 6066
Expected Server Output (approximate):
Server listening on port 6066...Accepted connection from /127.0.0.1:xxxxxReceived from /127.0.0.1:xxxxx: Hello from /127.0.0.1:yyyyyClosed connection from /127.0.0.1:xxxxxExpected Client Output:
Connecting to localhost on port 6066Connected to localhost/127.0.0.1:6066Sending: Hello from /127.0.0.1:yyyyyServer says: Thank you for connecting to /127.0.0.1:6066 Goodbye!Connection closed.Note: For more demanding applications requiring non-blocking I/O and better scalability, explore the java.nio (New I/O) package, which provides features like Channels, Buffers, and Selectors.